CodeSnippet#
코드를 구문 강조(syntax highlighting)와 함께 표시하는 컴포넌트입니다. 줄 번호 표시, 복사 버튼, 다양한 테마를 지원합니다.
Live Preview#
class CodeSnippetDefaultExample extends StatefulComponent {
const CodeSnippetDefaultExample({super.key});
@override
State<CodeSnippetDefaultExample> createState() =>
_CodeSnippetDefaultExampleState();
}
class _CodeSnippetDefaultExampleState
extends State<CodeSnippetDefaultExample> {
@override
Component build(BuildContext context) {
return CodeSnippet(
code: "void main() {\n print('Hello, CoUI!');\n}",
mode: 'dart',
showLineNumbers: true,
showCopyButton: true,
);
}
}
class CodeSnippetDefaultExample extends StatefulWidget {
const CodeSnippetDefaultExample({super.key});
@override
State<CodeSnippetDefaultExample> createState() =>
_CodeSnippetDefaultExampleState();
}
class _CodeSnippetDefaultExampleState
extends State<CodeSnippetDefaultExample> {
@override
Widget build(BuildContext context) {
return CodeSnippet(
code: "void main() {\n print('Hello, CoUI!');\n}",
mode: 'dart',
showLineNumbers: true,
showCopyButton: true,
);
}
}
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',
);
}
}
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,
),
),
),
],
);
}
}
사용 시기 (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_core 의 CoreCodeSnippetStyle 안 static 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)#
| 항목 | Flutter | Web |
|---|---|---|
| 구문 강조 엔진 | syntax_highlight 패키지 |
없음 (<pre><code> 렌더링) |
| 클립보드 복사 | Clipboard.setData + 토스트 | onCopy 콜백 |
| 수평 스크롤 | SingleChildScrollView | CSS 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(...) — 파라미터는 그대로이므로 클래스명만 바꾸면 됩니다.