ColorPicker#
다양한 형식으로 색상을 선택하고 편집할 수 있는 색상 선택기 컴포넌트입니다. Flutter 와 Web 모두 CoColorPicker 로 통일된 API (color
/ onChanged / showAlpha / showLabel / showHexInput
/ pickerMode 등) 를 제공하며, 공통 CoreColorPickerTheme / CoreColorPickerTokensData
토큰을 공유합니다. trigger 는 양쪽 모두 [swatch][hex input?] 행으로 동일한 사이즈(40px swatch · 96px hex input · 4px swatch gap) 를 사용합니다.
값 표현은 플랫폼 컨벤션을 따릅니다 — Flutter 는 Color (Color(0xFF3B82F6)), Web 은 #RRGGBB
형식의 hex 문자열. 내부 색상 연산은 양쪽 모두 hex 기반 (core_color_math.dart) 으로 통일되어 있어 정확도와 동작이 동일합니다.
Live Preview#
class ColorPickerDefaultExample extends StatefulComponent {
const ColorPickerDefaultExample({super.key});
@override
State<ColorPickerDefaultExample> createState() =>
_ColorPickerDefaultExampleState();
}
class _ColorPickerDefaultExampleState
extends State<ColorPickerDefaultExample> {
String _color = '#3B82F6';
@override
Component build(BuildContext context) {
return CoColorPicker(
color: _color,
showHexInput: true,
onChanged: (value) {
setState(() => _color = value);
},
);
}
}
class ColorPickerDefaultExample extends StatefulWidget {
const ColorPickerDefaultExample({super.key});
@override
State<ColorPickerDefaultExample> createState() =>
_ColorPickerDefaultExampleState();
}
class _ColorPickerDefaultExampleState
extends State<ColorPickerDefaultExample> {
Color _color = const Color(0xFF3B82F6);
@override
Widget build(BuildContext context) {
return CoColorPicker(
color: _color,
showHexInput: true,
onChanged: (value) {
setState(() => _color = value);
},
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 사용자가 브랜드 색상, 테마 색상 등 임의의 색상을 직접 지정해야 하는 경우
- 디자인 도구, 에디터 등에서 색상 팔레트를 제공해야 하는 경우
- HEX 값이나 RGB / HSL / HSV 중 특정 색공간으로 값을 입력받아야 하는 경우
대신 다른 컴포넌트를 사용하세요:
Select: 고정된 색상 목록에서만 선택해야 하는 경우TextField: 색상 코드를 순수 텍스트로만 입력받는 경우
기본 사용법 (Basic Usage)#
// Color 값 기반 — Flutter 컨벤션
CoColorPicker(
color: Color(0xFF3B82F6),
onChanged: (color) => setState(() => _color = color),
showHexInput: true,
)
// RGB / HSL / HSV 기본 모드 + 알파 슬라이더 + 화면 색 추출
CoColorPicker(
color: _color,
onChanged: handleColorChanged,
showAlpha: true,
showLabel: true,
pickerMode: CoreColorPickerMode.rgb,
enableEyeDropper: true,
)
// hex 문자열 기반 — CSS 컨벤션
CoColorPicker(
color: '#3B82F6',
onChanged: (hex) => setState(() => _color = hex),
showHexInput: true,
)
CoColorPicker(
color: _color,
onChanged: handleColorChanged,
showAlpha: true,
showLabel: true,
pickerMode: CoreColorPickerMode.rgb,
enableEyeDropper: true,
)
Props / Parameters#
공통 Contract 필드 (CoreColorPickerContract):
| 속성 | Flutter | Web | 기본값 | 설명 |
|---|---|---|---|---|
color |
Color |
String |
필수 | 현재 선택된 색상 (Flutter Color, Web #RRGGBB) |
onChanged |
ValueChanged<Color>? |
CoreValueChanged<String>? |
null |
Save 커밋 시 호출되는 콜백 (live-preview 에는 호출되지 않음) |
enabled |
bool? |
bool? |
true |
인터랙션 가능 여부 |
showAlpha |
bool? |
bool? |
false |
알파 슬라이더 표시 |
showLabel |
bool? |
bool? |
false |
swatch 옆 hex 라벨 표시 |
showHexInput |
bool? |
bool? |
false |
swatch 옆 hex 텍스트 입력 표시 |
pickerMode |
CoreColorPickerMode? |
CoreColorPickerMode? |
rgb |
RGB / HSL / HSV 색공간 |
enableEyeDropper |
bool? |
bool? |
true |
EyeDropper 버튼 표시 (Web 은 Chromium 95+ / Flutter 는 web target 에서 동작) |
promptMode |
CoreColorPickerPromptMode? |
CoreColorPickerPromptMode? |
popover |
트리거 클릭 시 popover/dialog 중 어떤 surface로 열릴지 |
dialogTitle |
Widget? |
Component? |
null |
팝오버 / 다이얼로그 상단 타이틀 |
placeholder |
Widget? |
Component? |
null |
미선택 상태 placeholder |
controller |
Object? |
Object? |
null |
외부 imperative controller |
colorPickerStyle |
CoreColorPickerStyle<Color, List<BoxShadow>>? |
CoreColorPickerStyle<String, String>? |
null |
인스턴스 스타일 (Style 시스템 참조) |
스타일 시스템 (Style System)#
ColorPicker 의 모든 chrome / dimensional / nested-slot 오버라이드는 CoreColorPickerStyle<Clr, Shadow>
단일 슬롯으로 흐릅니다 (Epic #1302 원칙 6/7/8). 평면 chrome 필드 (popoverAlignment / popoverAnchorAlignment
/ popoverPadding) 는 모두 제거되었습니다.
시맨틱 vs 스타일#
-
시맨틱 enum / behaviour: 위젯/컴포넌트 파라미터로 직접 (
color,onChanged,enabled,showAlpha,showLabel,showHexInput,pickerMode,enableEyeDropper,promptMode,dialogTitle,placeholder,controller) -
chrome / dimensional / 슬롯 스타일:
CoreColorPickerStyle한 곳으로 (popoverStyle/dialogStyle/swatchButtonStyle/hexInputStyle/labelStyle/descriptionStyle/errorStyle) -
변형(variant) 교체: asChild —
dialogTitle/placeholder슬롯 등에 직접 위젯 주입
Resolve chain#
design system default for color picker
→ CoreColorPickerTheme.style // 프로젝트 공통
→ parent component slot override
→ widget.colorPickerStyle // 인스턴스별
각 nested 슬롯 스타일 (popoverStyle / dialogStyle / swatchButtonStyle
/ hexInputStyle) 은 자기 컴포넌트의 자체 resolve chain 으로 다시 한 번 머지됩니다. 예: popoverStyle.triggerStyle
의 chrome 은 → CoreButtonTheme → variant 룩업 → 부모 슬롯 → widget.colorPickerStyle.popoverStyle.triggerStyle
순.
슬롯 매핑 (CoreColorPickerStyle 7 필드)#
| 필드 | 타입 (Flutter) | 적용 영역 |
|---|---|---|
popoverStyle |
CorePopoverStyle<Color, List<BoxShadow>>? |
popover-mode 트리거 + 패널 chrome |
dialogStyle |
CoreDialogStyle<Color>? |
dialog-mode 모달 chrome |
swatchButtonStyle |
CoreButtonStyle<Color>? |
preset / history color swatch 버튼 |
hexInputStyle |
CoreTextFieldStyle<Color>? |
트리거 옆 hex 입력 필드 |
labelStyle |
CoreTextStyle<Color>? |
트리거 위 라벨 텍스트 |
descriptionStyle |
CoreTextStyle<Color>? |
도움말 / 설명 텍스트 |
errorStyle | CoreTextStyle<Color>? | 에러 메시지 텍스트 |
Migration — 옛 평면 필드 → 새 위치 매핑#
| 옛 chrome 필드 | 새 위치 |
|---|---|
popoverAlignment |
popoverStyle.placement (또는 CoPopover 의 placement 직접 지정) |
popoverAnchorAlignment | popoverStyle.placement |
popoverPadding | popoverStyle.panelPadding |
사용 예 (Flutter)#
CoColorPicker(
color: const Color(0xFF3B82F6),
showHexInput: true,
onChanged: handleColorChanged,
colorPickerStyle: CoreColorPickerStyle(
popoverStyle: CorePopoverStyle(
panelBorderRadius: 12,
panelPadding: 16,
gap: 8,
),
swatchButtonStyle: CoreButtonStyle(
borderRadius: 4,
),
hexInputStyle: CoreTextFieldStyle(
borderColor: Color(0xFF3B82F6),
),
labelStyle: CoreTextStyle(fontWeight: 600),
),
)
사용 예 (Web)#
CoColorPicker(
color: '#3B82F6',
showHexInput: true,
onChanged: handleColorChanged,
colorPickerStyle: CoreColorPickerStyle<String, String>(
popoverStyle: CorePopoverStyle<String, String>(
panelBorderRadius: 12,
panelPadding: 16,
),
hexInputStyle: CoreTextFieldStyle<String>(
borderColor: 'primary',
),
),
)
변형 (Variants)#
swatch + hex 텍스트 입력#
CoColorPicker(
color: const Color(0xFF3B82F6),
showHexInput: true,
onChanged: handleChanged,
)
EyeDropper#
CoColorPicker(
color: _color,
enableEyeDropper: true,
onChanged: handleChanged,
)
enableEyeDropper: true + 호스트가 window.EyeDropper API 를 지원하면 액션 행에 스포이드 버튼 표시. Chromium 95+ / Flutter Web target 에서 동작. 미지원 환경 (Firefox · Safari · 네이티브 Flutter) 에서는 버튼이 자동 숨김.
최근 색 (Recent Colors)#
CoColorPicker 는 같은 화면 안에서 commit 한 색상을 RecentColorsScope 를 통해 공유합니다. picker 호스팅 화면을 scope 로 wrap 하면 그 안의 모든 picker 가 동일 history 를 보고, Save 액션이 새 색을 push 하면 다른 picker 의 그리드에 즉시 반영됩니다.
// Flutter
RecentColorsScope(
initialRecentColors: [
CoreColor.fromHex('#3B82F6'),
CoreColor.fromHex('#E11D48'),
],
maxRecentColors: 10,
onRecentColorsChanged: (colors) => persist(colors),
child: ColorPaletteEditor(...),
)
// Web — 동일 시그니처
RecentColorsScope(
initialRecentColors: [
CoreColor.fromHex('#3B82F6'),
CoreColor.fromHex('#E11D48'),
],
maxRecentColors: 10,
onRecentColorsChanged: (colors) => persist(colors),
child: ColorPaletteEditor(...),
)
scope 바깥에서 단독으로 쓰는 picker (예: 스토리북, 단일 다이얼로그) 는 history 그리드 영역이 자동으로 숨겨지고 onChanged Save 콜백만 발사됩니다. App-level prop 이 아닌 picker 호스팅 화면 단위로 wrap 하는 패턴이 정공법 — root 가 아닌 scope 가 필요한 곳에서만 적용됩니다.
그리드 토큰#
그리드 swatch 사이즈와 컬럼 수는 CoreColorPickerTokensData 의 recentSwatchSize / recentSwatchGap
/ recentColumns 에서 결정됩니다 (기본 24px swatch · 4px gap · 10 columns). 양 플랫폼 grid 가 같은 토큰 source 를 참조합니다.
동작 스펙 (Behavior)#
인터랙션#
- swatch 클릭: 통일된
CoPopover팝오버 열림 (양 플랫폼 동일 인프라) - hex 입력:
#RRGGBB형식 검증 후_commit발사 -
mode tab (RGB / HSL / HSV):
CoButton(.primary | .ghost)사용 — hover / focus / disabled 인터랙션 통일 컴포넌트가 처리 - SV (Saturation / Value) 영역: 마우스 드래그로 saturation / value 동시 조정 (cursor: precise / crosshair)
- hue 슬라이더: 좌우 드래그로 hue 조정 (cursor: ew-resize)
- EyeDropper: 지원 환경에서 화면 픽셀 샘플링
- Outside tap / ESC: Save 안 누른 상태로 닫히면 draft 가 마지막 commit 색으로 자동 롤백
상태 전환#
default→picking: swatch 클릭 시 (CoPopover open)-
picking→committed: Save 버튼 클릭.onChanged호출 +RecentColorsScope에addHistory -
picking→cancelled: Cancel · outside-tap · ESC. swatch / hex input 이 마지막 committed 색으로 롤백 disabled: 트리거 row 비활성, 팝오버 mount 안 됨 (aria-disabled="true")
색 commit 흐름#
swatch click → popover open → live preview drag/type → Save → _commit(hex)
↓
widget.onChanged(color/hex)
↓
storage.addHistory(CoreColor)
onChanged 는 Save 시점에만 호출되어 의도하지 않은 미커밋 값이 외부 상태로 새지 않습니다.
Theme 오버라이드#
CoreComponentTheme.colorPicker 를 통해 프로젝트 레벨의 기본 스타일 (CoreColorPickerStyle) + behaviour 기본값 (showAlpha
/ showLabel / showHexInput / pickerMode / enableEyeDropper
/ promptMode) 을 지정할 수 있습니다:
CoreComponentTheme(
colorPicker: CoreColorPickerTheme(
style: CoreColorPickerStyle(
popoverStyle: CorePopoverStyle(
panelBorderRadius: 8,
panelPadding: 16,
),
),
showAlpha: true,
showHexInput: true,
),
)
접근성 (Accessibility)#
- 트리거 wrapper:
role="group"/aria-label="Color picker" -
swatch:
<button>— 클릭 / 키보드 / 포커스 인터랙션은CoPopover의 trigger handler 가 담당, swatch 자체에는aria-haspopup="dialog"/aria-expanded가 popover wrapper 측에서 emit - 팝오버 패널:
role="dialog"/aria-label="Color picker" - mode tab:
<button>—CoButton의 표준 포커스 / hover affordance - 비활성화:
aria-disabled="true"+disabled
크로스 플랫폼 차이점 (Platform Differences)#
API 와 시각 패리티는 통일됨. 플랫폼 간 차이는 구현 매체 와 값 표현 만 다르며 기능 동등.
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | CoColorPicker | CoColorPicker |
| 색상 타입 | Color | String (hex) |
| Style 제네릭 | CoreColorPickerStyle<Color, List<BoxShadow>> |
CoreColorPickerStyle<String, String> |
| 색공간 선택 | CoreColorPickerMode | CoreColorPickerMode |
| 팝오버 인프라 | CoPopover (overlay-driven) |
CoPopover (CoOverlayHost portal) |
| EyeDropper | Web target 에서 window.EyeDropper API 호출 | 동일 |
| 외부 클릭 / ESC 닫기 | CoPopover 의 dismissOnOutsideTap / dismissOnEscape |
동일 |
| RecentColorsScope | Data<CoreColorHistoryStorage> InheritedWidget |
_RecentColorsInherited InheritedComponent |