CodeSnippet | CoUI
LogoCoUI

CodeSnippet

코드를 구문 강조와 함께 표시하는 코드 블록 컴포넌트

CodeSnippet#

코드를 구문 강조(syntax highlighting)와 함께 표시하는 컴포넌트입니다. 줄 번호 표시, 복사 버튼, 다양한 테마를 지원합니다.

Live Preview#

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 문서, 튜토리얼, 도움말 페이지에서 코드 예시를 보여줄 때
  • API 응답이나 설정 파일 내용을 사용자에게 표시할 때
  • 코드 복사 기능이 필요한 개발자 도구나 대시보드를 구현할 때

대신 다른 컴포넌트를 사용하세요:

  • Kbd: 키보드 단축키나 단일 명령어를 인라인으로 표시할 때
  • Text: 단순 텍스트 정보를 표시할 때 (코드가 아닌 경우)

기본 사용법 (Basic Usage)#

// 기본 코드 블록
CodeSnippet(
  code: '''
void main() {
  print('Hello, CoUI!');
}''',
  mode: 'dart',
)

// 줄 번호와 복사 버튼 포함
CodeSnippet(
  code: '''
import 'package:flutter/material.dart';

class MyWidget extends StatelessWidget {
  const MyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return const Text('Hello');
  }
}''',
  mode: 'dart',
  showLineNumbers: true,
  showCopyButton: true,
)
// 기본 코드 블록
CodeSnippet(
  code: '''
void main() {
  print('Hello, CoUI!');
}''',
  mode: 'dart',
)

// 줄 번호 표시
CodeSnippet(
  code: '''
import 'package:jaspr/jaspr.dart';

void main() {
  runApp(App());
}''',
  mode: 'dart',
  showLineNumbers: true,
)

빠른 오버라이드 (Chain)#

이미 만든 CodeSnippet 인스턴스에 스타일을 빠르게 덧붙이고 싶다면 withStyle 체인을 쓸 수 있습니다. 생성자의 style 슬롯 인자와 동일하게 동작하지만, 이미 구성된 위젯 위에서 바로 이어 쓸 수 있습니다. .radius8처럼 Core 토큰 상수 이름과 똑같은 이름의 getter도 있습니다 — withStyle을 한 번 더 줄인 sugar로, 이름이 곧 값이라(radius8 == CoreRadius.radius8) 어느 컴포넌트에서 써도 뜻이 갈리지 않습니다.

class CodeSnippetChainExample extends StatefulWidget {
  const CodeSnippetChainExample({super.key});

  @override
  State<CodeSnippetChainExample> createState() => _CodeSnippetChainExampleState();
}

class _CodeSnippetChainExampleState extends State<CodeSnippetChainExample> {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisSize: MainAxisSize.min,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
        CodeSnippet(
          code: "void main() {\n  print('Hello, CoUI!');\n}",
          mode: 'dart',
          showLineNumbers: true,
          showCopyButton: true,
        ).radius16.primary,
        const Gap.space12(),
        // withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
        CodeSnippet(
          code: "void main() {\n  print('Hello, CoUI!');\n}",
          mode: 'dart',
          showLineNumbers: true,
          showCopyButton: true,
        ).withStyle(
          const CoreCodeSnippetStyle(
            backgroundColor: CoreColor.token(CoreColors.tertiary),
            borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
            borderWidth: CoreStrokeWidth.stroke2,
            padding: CoreEdgeInsets.symmetric(
              horizontal: CoreSpace.space24,
              vertical: CoreSpace.space8,
            ),
          ),
        ),
      ],
    );
  }
}
class CodeSnippetChainExample extends StatefulComponent {
  const CodeSnippetChainExample({super.key});

  @override
  State<CodeSnippetChainExample> createState() => _CodeSnippetChainExampleState();
}

class _CodeSnippetChainExampleState extends State<CodeSnippetChainExample> {
  @override
  Component build(BuildContext context) {
    return div(
      [
        // 토큰-정확 getter combo — 이름이 곧 값 (getter 이름 = Core 토큰 상수 1:1).
        CodeSnippet(
          code: "void main() {\n  print('Hello, CoUI!');\n}",
          mode: 'dart',
          showLineNumbers: true,
          showCopyButton: true,
        ).radius16.primary,
        const Gap.space12(),
        // withStyle full-control — getter 가 없는 필드(padding)까지 한 번에.
        CodeSnippet(
          code: "void main() {\n  print('Hello, CoUI!');\n}",
          mode: 'dart',
          showLineNumbers: true,
          showCopyButton: true,
        ).withStyle(
          const CoreCodeSnippetStyle(
            backgroundColor: CoreColor.token(CoreColors.tertiary),
            borderRadius: CoreBorderRadius.all(CoreRadius.radius24),
            borderWidth: CoreStrokeWidth.stroke2,
            padding: CoreEdgeInsets.symmetric(
              horizontal: CoreSpace.space24,
              vertical: CoreSpace.space8,
            ),
          ),
        ),
      ],
      classes: 'flex flex-col items-start',
    );
  }
}

Props / Parameters#

속성타입기본값설명
code String 필수 표시할 코드 문자열
mode String 필수 구문 강조 언어 (dart, json, yaml, sql, js, ts)
showLineNumbers bool false 줄 번호 표시 여부
showCopyButton bool true 복사 버튼 표시 여부
actions List<Widget> / List<Component> [] 추가 액션 위젯
onCopy VoidCallback? / CoreVoidCallback? null 복사 후 추가 콜백
codeSnippetStyle CoreCodeSnippetStyle? null chrome + nested 슬롯 오버라이드 (아래 표 참고)

CoreCodeSnippetStyle 필드#

필드타입설명
backgroundColor CoreColor? Container background colour. null defers to [defaultBackgroundColor].
borderColor CoreColor? Outer border stroke colour. null defers to [defaultBorderColor].
borderWidth double? Outer border stroke width (logical px).
borderRadius CoreBorderRadius? Outer border radius. null defers to [defaultBorderRadius].
padding CoreEdgeInsets? Inner padding applied to the code area. null defers to [defaultPadding].
codeTypography CoreTextStyle? Code body typography role. null defers to [defaultCodeTypography].
codeTextStyle CoreTextStyle? Rendered code text style override (layered on [codeTypography]). Carries the code / copy-icon foreground colour via [CoreTextStyle.color]. null defers to [defaultCodeTextStyle].
lineNumberTextStyle CoreTextStyle? Line-number gutter text style override. No default* , and it must not get one: the gutter's resting style is derived rather than stated — the resolved code style with its foreground dimmed to [CoreOpacity.opacity40] — and no static const can carry it, because the colour being dimmed is whatever [codeTextStyle] resolved to. Both resolvers additionally branch on this slot being unset. Flutter reads lineNumberTextStyle?.color as "the caller stated a gutter colour" and takes scheme.resolve(override) instead of dimming; Web emits gutter inline CSS only when the slot carries something. A default would delete the dim branch on Flutter and pin a permanent inline color on the Web gutter.
preMargin double? <pre> wrapper outer margin (logical px). null defers to [defaultPreMargin].
lineNumberMargin double? Line-number gutter right margin (logical px). null defers to [defaultLineNumberMargin].
lineNumberAlpha double? Alpha applied to the derived line-number colour — the resolved code foreground dimmed by this much. null defers to [defaultLineNumberAlpha]. Ignored when [lineNumberTextStyle] states a colour of its own, which is the "caller said it" branch both resolvers already take. Stated here rather than reached for in each resolver. It was a resolver constant on both platforms and they had drifted into two forms of one decision — Flutter dimmed by CoreOpacity.opacity40 , Web emitted a literal opacity-40 class — so a designer changing the gutter's weight had two places to change and no way to know it. CoreCodeDiffStyle.lineNumberAlpha is the same value for the same gutter and was already a field; the two code components now say it the same way.
copyButtonOffset double? Copy button corner offset — top + right (logical px). null defers to [defaultCopyButtonOffset]. This is layout positioning (the absolute offset of the button within its corner wrapper), not button chrome — it stays flat alongside the nested [copyButtonStyle].
minWidth double? Minimum width of the code display area (logical px). null leaves the area unconstrained on that edge — there is no design-system default because "no size cap" is the design, so these four fields deliberately have no defaultX counterpart (same shape as CoreTextAreaStyle.maxHeight / maxWidth ). Flutter maps the four to a BoxConstraints around the scroll area; Web maps them to min-width / max-width / min-height / max-height on the <pre> scroll wrapper. A default on ANY of the four is a rendering change, because both platforms decide on their nullness rather than reading a value: · Flutter builds codeAreaConstraints as null exactly while all four are unset, and the widget passes that straight to Container(constraints:)null is a no-op wrapper. One non-null default makes the struct non-null, so every snippet starts being measured against caps instead of taking its intrinsic size. · Web emits each cap only when it is set ( _remOrNull yields nothing for null ), and [maxHeight] additionally gates overflow-y: auto , so a default there turns every snippet into a clamped scroller.
maxWidth double? Maximum width of the code display area (logical px). null leaves the area unconstrained on that edge. No default* — see [minWidth] for why a value here would change what every snippet renders.
minHeight double? Minimum height of the code display area (logical px). null leaves the area unconstrained on that edge. No default* — see [minWidth].
maxHeight double? Maximum height of the code display area (logical px). null leaves the area unconstrained on that edge. When set, the code area scrolls vertically past the cap on both platforms (Flutter's enclosing SingleChildScrollView , Web's overflow-y: auto ). No default* — and this is the most load-bearing of the four, since Web keys overflow-y: auto on it being non-null: a default would make every snippet a fixed-height scroll box. See [minWidth].
copyButtonStyle CoreButtonStyle? Nested [CoreButtonStyle] slot for the copy affordance — raw-forwarded to Button(variant:.plain, buttonStyle: …) on both platforms. Recursively merged onto [defaultCopyButtonStyle]; carries the copy button's size / radius / padding / foreground / transition chrome (child-component-composition).
copyIconStyle CoreIconStyle? Copy button icon style override. size defers to [defaultCopyIconStyle]. Colourless by default so the glyph inherits the [copyButtonStyle] foreground (Flutter IconTheme / Web currentColor ).
actionGapStyle CoreGapStyle? Gap slot between sibling action widgets in the top-right area. null defers to [defaultActionGapStyle].

동작 스펙 (Behavior)#

인터랙션#

  • 복사 버튼 클릭: 클립보드에 코드 복사 후 토스트 알림 표시 (Flutter) / Copy 버튼 클릭 이벤트 (Web)
  • 스크롤: 코드 영역 내에서 수직/수평 스크롤 가능
  • 호버 (복사 버튼): 버튼 강조 표시

토큰 기반 스타일링#

모든 chrome 기본값은 coui_coreCoreCodeSnippetStylestatic const default* 한 곳에서 나오고, Flutter 와 Web resolver 가 같은 값을 읽습니다. 인스턴스별로 바꿀 때는 codeSnippetStyle 슬롯에 필요한 필드만 넘깁니다 (위 필드 표 참고).

CodeSnippet(
  code: "print('hi');",
  mode: 'dart',
  codeSnippetStyle: CoreCodeSnippetStyle(
    padding: CoreEdgeInsets.all(CoreSpace.space24),
    borderRadius: CoreBorderRadius.all(CoreRadius.radius8),
  ),
)

프로젝트 전체 기본값은 CoreCodeSnippetTheme.style 로 주입합니다.

사용 가이드라인 (Usage Guidelines)#

Do#

언어 명시로 구문 강조 최적화

CodeSnippet(
  code: 'SELECT * FROM users WHERE active = true;',
  mode: 'sql',
  showCopyButton: true,
)

언어를 명시하면 코드를 더 쉽게 읽을 수 있고, 사용자가 코드의 종류를 즉시 파악할 수 있습니다.


Don't#

지나치게 긴 코드를 제한 없이 표시 금지

// 높이 제약은 Style 슬롯으로 — 양 플랫폼 동일
CodeSnippet(
  code: entireFileContent,
  mode: 'dart',
  codeSnippetStyle: CoreCodeSnippetStyle(maxHeight: 300),
)

긴 코드는 높이를 제한하여 스크롤 가능하게 해야 페이지 레이아웃이 유지됩니다.

접근성 (Accessibility)#

키보드 인터랙션#

동작
Tab복사 버튼으로 포커스 이동
Enter / Space복사 버튼 활성화

스크린 리더#

  • Flutter: Semantics(value: code) 적용
  • Web: <code> 태그 사용, 복사 버튼에 aria-label="Copy code" 적용

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
구문 강조 엔진 syntax_highlight 패키지 없음 (<pre><code> 렌더링)
클립보드 복사Clipboard.setData + 토스트onCopy 콜백
수평 스크롤SingleChildScrollViewCSS overflow-x: auto
크기 제약 codeSnippetStyle 의 min/max 필드 → BoxConstraints 같은 필드 → CSS min-* / max-*

레거시 vs 통일 비교 (Migration Notes)#

이전 버전의 Web CoUI 를 참조하는 코드에는 이 컴포넌트가 CodeBlock 이라는 이름이었을 수 있습니다. Flutter 쪽 이름(CodeSnippet)에 맞춰 Web 도 CodeSnippet 으로 개명되었습니다.

항목레거시 (구 Web CodeBlock)통일 CodeSnippet
API동일 named properties동일 named properties
이름만 변경Flutter 와 동일 이름으로 통일

마이그레이션: CodeBlock(...)CodeSnippet(...) — 파라미터는 그대로이므로 클래스명만 바꾸면 됩니다.

  • Kbd: 인라인 키보드 단축키 표시에 사용
  • Text: 코드가 아닌 일반 텍스트 표시에 사용