@conscience-technology/ctds 0.4.1 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,75 @@
1
+ /* Hint ──────────────────────────────────────────────────────
2
+ 흐름 속 참고 한 줄~두 줄. 배경·테두리 없음, 아이콘 + muted 텍스트.
3
+
4
+ 두 줄 넘어가면 둘째 줄 이후가 아이콘 폭만큼 들여쓰기 되도록
5
+ hanging indent 패턴 사용:
6
+ padding-left: 아이콘 폭 + gap
7
+ text-indent: -(아이콘 폭 + gap) → 첫 줄만 아이콘 자리로 되돌림
8
+ 아이콘은 inline-flex + vertical-align으로 첫 줄 line-box 안에 걸침. */
9
+ .root {
10
+ display: block;
11
+ color: var(--hint-fg, var(--fg-muted));
12
+ font-family: var(--font-sans);
13
+ line-height: 1.6;
14
+ /* 저자가 넣은 "\n" 또는 JSX 안 줄 바꿈을 실제 줄 바꿈으로 보존 —
15
+ 긴 문장이 어중간한 자리에서 자동 줄 바꿈되지 않도록 저자 호흡을 존중. */
16
+ white-space: pre-line;
17
+ }
18
+
19
+ .icon {
20
+ display: inline-flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ /* 아이콘 중앙을 x-height 중앙에 맞춤 — text-bottom은 디센더 라인 기준이라
24
+ 살짝 아래로 처짐. -0.125em 보정으로 캡 하이트 중앙에 정렬. */
25
+ vertical-align: middle;
26
+ transform: translateY(-0.08em);
27
+ color: var(--hint-accent, var(--fg-muted));
28
+ margin-inline-end: var(--space-1_5);
29
+ }
30
+
31
+ /* Size ─────────────────────────────────────────────────────── */
32
+ .sm {
33
+ font-size: var(--text-sm);
34
+ padding-inline-start: calc(var(--icon-size-sm) + var(--space-1_5));
35
+ text-indent: calc(-1 * (var(--icon-size-sm) + var(--space-1_5)));
36
+ }
37
+ .sm .icon > svg {
38
+ width: var(--icon-size-sm);
39
+ height: var(--icon-size-sm);
40
+ }
41
+
42
+ .md {
43
+ font-size: var(--text-base);
44
+ padding-inline-start: calc(var(--icon-size-md) + var(--space-1_5));
45
+ text-indent: calc(-1 * (var(--icon-size-md) + var(--space-1_5)));
46
+ }
47
+ .md .icon > svg {
48
+ width: var(--icon-size-md);
49
+ height: var(--icon-size-md);
50
+ }
51
+
52
+ /* Tones ────────────────────────────────────────────────────
53
+ neutral: 아이콘·텍스트 모두 fg-muted (기본).
54
+ 그 외 tone: 아이콘만 톤 색, 텍스트는 muted 유지 — Callout보다 더 조용함.
55
+ 필요하면 Callout으로 승격. */
56
+ .neutral {
57
+ --hint-fg: var(--fg-muted);
58
+ --hint-accent: var(--fg-muted);
59
+ }
60
+ .brand {
61
+ --hint-fg: var(--fg-muted);
62
+ --hint-accent: var(--primary);
63
+ }
64
+ .success {
65
+ --hint-fg: var(--fg-muted);
66
+ --hint-accent: var(--success);
67
+ }
68
+ .warning {
69
+ --hint-fg: var(--fg-muted);
70
+ --hint-accent: var(--warning);
71
+ }
72
+ .destructive {
73
+ --hint-fg: var(--fg-muted);
74
+ --hint-accent: var(--destructive);
75
+ }
@@ -0,0 +1,75 @@
1
+ "use client";
2
+
3
+ import { forwardRef, type HTMLAttributes, type ReactNode } from "react";
4
+
5
+ import { Icon, type IconName } from "../Icon";
6
+
7
+ import styles from "./Hint.module.css";
8
+
9
+ /**
10
+ * Hint — 흐름 속에 스며드는 참고 한 줄~두 줄.
11
+ * 아이콘 하나 + muted 텍스트. 액션·배경·테두리 없음.
12
+ *
13
+ * 아이콘 수직 정렬은 flex가 아니라 line-box에 흘려넣고,
14
+ * 둘째 줄은 hanging indent로 아이콘 폭만큼 들여쓰기 — Hint.module.css 참고.
15
+ *
16
+ * **아이콘 + 텍스트 한 줄짜리 안내는 반드시 이 컴포넌트 경유.**
17
+ * `<Icon /> + <span>` 수제 조립 금지 — [[feedback_no_hand_rolled_components]].
18
+ */
19
+ export type HintSize = "sm" | "md";
20
+ export type HintTone =
21
+ | "neutral"
22
+ | "brand"
23
+ | "success"
24
+ | "warning"
25
+ | "destructive";
26
+
27
+ export interface HintProps extends HTMLAttributes<HTMLSpanElement> {
28
+ icon?: IconName;
29
+ size?: HintSize;
30
+ /** 아이콘·텍스트 공통 톤. 기본 neutral(muted). 톤 색이 필요하면 brand/success/warning/destructive. */
31
+ tone?: HintTone;
32
+ children?: ReactNode;
33
+ }
34
+
35
+ const sizeClass: Record<HintSize, string> = {
36
+ sm: styles.sm,
37
+ md: styles.md,
38
+ };
39
+
40
+ const toneClass: Record<HintTone, string> = {
41
+ neutral: styles.neutral,
42
+ brand: styles.brand,
43
+ success: styles.success,
44
+ warning: styles.warning,
45
+ destructive: styles.destructive,
46
+ };
47
+
48
+ const Hint = forwardRef<HTMLSpanElement, HintProps>(
49
+ (
50
+ { className, icon = "info", size = "sm", tone = "neutral", children, ...rest },
51
+ ref,
52
+ ) => {
53
+ const rootClass = [
54
+ styles.root,
55
+ sizeClass[size],
56
+ toneClass[tone],
57
+ className ?? "",
58
+ ]
59
+ .filter(Boolean)
60
+ .join(" ");
61
+
62
+ return (
63
+ <span ref={ref} className={rootClass} {...rest}>
64
+ <span className={styles.icon} aria-hidden>
65
+ <Icon name={icon} />
66
+ </span>
67
+ {children}
68
+ </span>
69
+ );
70
+ },
71
+ );
72
+
73
+ Hint.displayName = "Hint";
74
+
75
+ export { Hint };
@@ -0,0 +1,2 @@
1
+ export { Hint } from "./Hint";
2
+ export type { HintProps, HintSize, HintTone } from "./Hint";
@@ -559,6 +559,7 @@ export const componentSlugs: Record<string, string> = {
559
559
  "Dropdown Menu": "overlay/dropdown-menu",
560
560
  Disclosure: "overlay/disclosure",
561
561
  Callout: "feedback/callout",
562
+ Hint: "feedback/hint",
562
563
  Modal: "feedback/modal",
563
564
  Drawer: "feedback/drawer",
564
565
  Toast: "feedback/toast",
@@ -640,7 +641,7 @@ export const components: { id: string; title: string; items: string[] }[] = [
640
641
  title: "Feedback",
641
642
  items: [
642
643
  "Modal", "Alert Dialog", "Drawer", "Popover", "Hover Card",
643
- "Toast", "Callout", "Progress Bar", "Spinner",
644
+ "Toast", "Callout", "Hint", "Progress Bar", "Spinner",
644
645
  ],
645
646
  },
646
647
  {
@@ -34,6 +34,7 @@ export * from "./FileIcon";
34
34
  export * from "./FilterButton";
35
35
  export * from "./GalleryItem";
36
36
  export * from "./GeneratedImage";
37
+ export * from "./Hint";
37
38
  export * from "./Icon";
38
39
  export * from "./IconButton";
39
40
  export * from "./IdentityButton";
@@ -253,7 +253,7 @@
253
253
  --fg: oklch(0.22 var(--tone-chroma) var(--tone-hue));
254
254
  /* muted/subtle/disabled은 fg 톤의 알파 — 색 있는 배경 위에서도 자연스럽게 섞임.
255
255
  라이트: 어두운 잉크(≈0.18)를 낮은 알파로 얹음. */
256
- --fg-muted: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.60);
256
+ --fg-muted: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.82);
257
257
  --fg-subtle: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.42);
258
258
  --fg-disabled: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.26);
259
259
  /* border는 배경 대비 알파 기반 hairline — 겹치면 자연스럽게 짙어짐.
@@ -597,7 +597,7 @@
597
597
  :root {
598
598
  --border: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.42);
599
599
  --border-strong: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.60);
600
- --fg-muted: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.82);
600
+ --fg-muted: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.92);
601
601
  --fg-subtle: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.66);
602
602
  }
603
603
  :root[data-mode="dark"] {
@@ -620,7 +620,7 @@
620
620
  --bg-elev-2: #ffffff;
621
621
  --fg: #1a1a1a;
622
622
  --fg-strong: #0a0a0a;
623
- --fg-muted: #666666;
623
+ --fg-muted: #444444;
624
624
  --fg-subtle: #999999;
625
625
  --fg-disabled: #c0c0c0;
626
626
  --border: rgba(0, 0, 0, 0.08);
@@ -635,7 +635,7 @@
635
635
  --bg-elev-2: #1f1f1f;
636
636
  --fg: #f0f0f0;
637
637
  --fg-strong: #ffffff;
638
- --fg-muted: #a0a0a0;
638
+ --fg-muted: #b8b8b8;
639
639
  --fg-subtle: #7a7a7a;
640
640
  --fg-disabled: #4a4a4a;
641
641
  --border: rgba(255, 255, 255, 0.08);
@@ -699,7 +699,7 @@
699
699
  fg-muted 알파 0.60은 다크 bg(0.20) 위에서 effective L ≈ 0.65, 콘트라스트 ≈ 3.5:1.
700
700
  WCAG AA "normal text" 4.5:1 임계 아래 — 보조 라벨·placeholder 전용.
701
701
  중요한 보조 텍스트에는 --fg를 쓸 것. prefers-contrast: more에서 자동 상향됨. */
702
- --fg-muted: oklch(0.95 var(--tone-chroma) var(--tone-hue) / 0.60);
702
+ --fg-muted: oklch(0.95 var(--tone-chroma) var(--tone-hue) / 0.72);
703
703
  --fg-subtle: oklch(0.95 var(--tone-chroma) var(--tone-hue) / 0.40);
704
704
  --fg-disabled: oklch(0.95 var(--tone-chroma) var(--tone-hue) / 0.24);
705
705
  /* 다크 모드: fg 톤(밝은 잉크)을 낮은 알파로 얹음. */
@@ -744,7 +744,7 @@
744
744
  --bg-muted: oklch(0.955 var(--tone-chroma) var(--tone-hue));
745
745
  --fg-strong: oklch(0.12 var(--tone-chroma) var(--tone-hue));
746
746
  --fg: oklch(0.22 var(--tone-chroma) var(--tone-hue));
747
- --fg-muted: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.60);
747
+ --fg-muted: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.82);
748
748
  --fg-subtle: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.42);
749
749
  --fg-disabled: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.26);
750
750
  --border: oklch(0.18 var(--tone-chroma) var(--tone-hue) / 0.08);
@@ -1,42 +1,58 @@
1
1
  # Hint
2
2
 
3
3
  ### 원칙
4
- 흐름 속에 스며드는 **참고 한 줄~두 줄**. 아이콘 하나와 muted 텍스트로만 구성된 가장 가벼운 안내. 페이지 폭을 차지하지 않고, 톤도 신호도 띠지 않는다. 자리의 성격은 "이 자리에서 알아야 할 것"의 각주 — [[components/callout]]처럼 액션으로 유도하지 않고, [[recipes/page-banner]]처럼 상태를 외치지 않는다.
4
+ 흐름 속에 스며드는 **참고 한 줄~두 줄**. 아이콘 하나와 muted 텍스트로만 구성된 가장 가벼운 안내. 페이지 폭을 차지하지 않고, 자리의 성격은 "이 자리에서 알아야 할 것"의 각주 — [[components/callout]]처럼 액션으로 유도하지 않고, [[recipes/page-banner]]처럼 상태를 외치지 않는다.
5
+
6
+ **아이콘 + 텍스트 조합의 모든 안내는 반드시 이 컴포넌트 경유.** 와이어프레임·페이지에 `<Icon /> + <span>`을 수제로 조립해서 "ℹ️ 어쩌구" 한 줄을 만드는 패턴 영구 금지 — Hint 하나로 통일한다. 수제 조립은 size·color·vertical-align·둘째 줄 들여쓰기가 매번 어긋나서 리듬이 깨지는 반복 함정이다 ([[feedback_no_hand_rolled_components]], [[feedback_local_derivative_over_handrolled]]).
5
7
 
6
8
  ### Do
7
- - 아이콘은 **1개 고정**, text는 1~2줄. 길어지면 Callout 또는 별도 블록으로.
8
- - 톤 색 금지 — `fg-muted` 하나만. tone·variant 축 없음.
9
- - 아이콘 수직 정렬은 **첫 줄 캡 하이트 중앙** — flex align-items 트릭 대신 `display: inline` + `vertical-align`로 line box 안에 흘려넣기 (내부 구현 책임).
10
- - 폼 필드 helper text는 [[components/form-field]]의 자체 helper를 쓴다. Hint는 **블록 단위 안내** — 폼 밖, 섹션 안 설명.
9
+ - **아이콘 + 텍스트 한 줄짜리 안내는 전부 Hint**. 폼 helper, 섹션 각주, 리스트 하단 주석 등.
10
+ - 아이콘은 **1개 고정**, 자유롭게 선택 (`info` `question` `brackets-square` `clock` `shield-check` 등 상황에 맞게).
11
+ - 텍스트는 1~2줄. 두 줄 넘어가면 둘째 줄은 아이콘 폭만큼 자동 들여쓰기 (hanging indent — 컴포넌트가 처리).
12
+ - **줄 바꿈이 애매하면 저자가 호흡 단위로 끊어준다.** 긴 문장이 어중간한 자리에서 자동 줄 바꿈되면 리듬이 깨짐. 문장이 두 호흡으로 나뉘면 `.` 바로 뒤에서 사람이 `\n`을 넣어 **문장 단위로** 끊어라 (JSX에서는 중괄호 안 템플릿 리터럴 또는 두 `<Hint>` 분리). 자동 줄 바꿈에 맡기면 "···하고 승인한 / 뒤 실행을" 같은 어색한 지점에서 꺾인다.
13
+ - 색은 **neutral이 기본**. 톤 신호가 꼭 필요할 때만 brand/success/warning/destructive — 아이콘 색만 바뀌고 텍스트는 muted 유지 ([[components/callout]]보다 조용).
14
+ - 아이콘 수직 정렬은 **첫 줄 캡 하이트 중앙** — flex align-items 트릭 대신 `vertical-align: text-bottom`으로 line box 안에 흘려넣기 (내부 구현 책임).
11
15
 
12
16
  ### Don't
17
+ - [HARD] **아이콘 + 텍스트를 수제 조립 금지** — `<span><Icon/> …</span>`, `<div className="hint"><Icon/>…</div>` 전부 금지. 반드시 `<Hint>` 사용.
13
18
  - [HARD] 액션 버튼을 넣지 않는다 — 액션이 필요하면 [[components/callout]].
14
- - [HARD] 톤 신호(warning·destructive·success 배경)를 띠지 않는다 — 신호가 필요하면 [[components/callout]] 또는 [[recipes/page-banner]].
15
- - [HARD] 배경 + 테두리 금지 ([[PRINCIPLES]] §5b). Hint는 **배경·테두리 둘 다 없음**이 기본.
19
+ - [HARD] 배경·테두리 둘 다 금지 ([[PRINCIPLES]] §5b). Hint는 **배경·테두리 둘 다 없음**이 기본.
20
+ - [HARD] 톤 신호를 띠는 **배경**은 금지 — 톤이 필요하면 아이콘 색만 (`tone="warning"`). 배경까지 가야 하면 [[components/callout]].
16
21
  - [SOFT] 3줄 이상 텍스트 금지 — 길어지면 섹션 자체가 "안내 블록"이 돼야 함. Hint는 각주 성격 유지.
17
22
  - [SOFT] 리스트·강조 같은 서식 금지. 평문만.
23
+ - [SOFT] 폼 필드 helper text는 [[components/form-field]]의 자체 helper를 쓴다. Hint는 **폼 밖, 섹션 안 설명** 전용.
18
24
 
19
25
  ### Fallback
20
- 아이콘이 애매하면 `info` 또는 `question`. 자리가 코드/기술 설명이면 `brackets-square`.
26
+ 아이콘이 애매하면 `info`. 질문·도움말이면 `question`, 코드·기술 설명이면 `brackets-square`.
21
27
 
22
28
  ### 관련
23
- - [[components/callout]] — 톤·액션 있는 안내 블록. Hint는 액션 없음.
29
+ - [[components/callout]] — 톤·액션·배경 있는 안내 블록. Hint는 액션 없음, 배경 없음.
24
30
  - [[recipes/page-banner]] — 페이지 폭 상태 바. Hint는 흐름 속 각주.
25
31
  - [[components/form-field]] — 폼 helper text 전용 자리.
26
- - [[tokens/color]] — fg-muted
32
+ - [[tokens/color]] — fg-muted, primary/success/warning/destructive
27
33
  - [[tokens/typography]] — text-sm 기본, line-height 1.6
28
34
 
29
35
  ### Props 시그니처
30
36
  ```tsx
31
37
  <Hint
32
- icon={IconName} // default: "info"
33
- size="sm | md" // default: "sm" (앱 리듬 기본)
38
+ icon={IconName} // default: "info" — 자유롭게
39
+ size="sm | md" // default: "sm"
40
+ tone="neutral | brand | success | warning | destructive" // default: "neutral"
34
41
  >{children}</Hint>
35
42
  ```
36
43
 
37
44
  ### Variant 매트릭스
38
- variant 축 **없음**. Hint는 "참고 각주" 성격 하나만 — 톤·강도 다 제거해서 가장 조용함.
45
+ tone은 **아이콘 색만** 스왑. 텍스트는 전 톤에서 fg-muted 고정 — Callout보다 조용한 성격을 유지한다.
46
+
47
+ | tone | 아이콘 색 | 자리 |
48
+ |---|---|---|
49
+ | `neutral` | `fg-muted` | 기본. 그냥 각주. |
50
+ | `brand` | `primary` | 자리의 핵심 기능을 가리키는 각주. |
51
+ | `success` | `success` | 완료·안전 상태 각주. |
52
+ | `warning` | `warning` | 주의 각주 (배경 띠까지 가면 Callout). |
53
+ | `destructive` | `destructive` | 되돌릴 수 없는 결과 각주. |
39
54
 
55
+ ### Size 가이드
40
56
  | size | font-size | line-height | icon size | 자리 |
41
57
  |---|---|---|---|---|
42
58
  | `sm` | `--text-sm` | 1.6 | 16 | 섹션 안 각주 (기본) |
@@ -44,14 +60,30 @@ variant 축 **없음**. Hint는 "참고 각주" 성격 하나만 — 톤·강도
44
60
 
45
61
  ### 조합 예시
46
62
  ```tsx
63
+ // 기본 각주
64
+ <Hint>최근 30일 활동만 표시됩니다.</Hint>
65
+
66
+ // 아이콘 자유 선택
67
+ <Hint icon="clock">업로드한 파일은 24시간 동안 임시 저장소에 남아 있습니다.</Hint>
68
+
69
+ // 긴 설명 — 저자가 문장 단위로 호흡을 끊어줌 (자동 줄 바꿈 금지)
47
70
  <Hint icon="brackets-square">
48
- 문서 등록은 추출·정리까지 자동 처리합니다. AI 작업 계획을 요청하고 승인한 뒤 실행을 요청하면,
49
- 연결된 실행기가 코드 변경안과 테스트 결과를 작성합니다.
71
+ {`문서 등록은 추출과 정리까지 자동 처리합니다.
72
+ AI 작업 계획을 요청하고 승인한 뒤 실행을 요청하면, 연결된 실행기가 코드 변경안과 테스트 결과를 작성합니다.`}
73
+ </Hint>
74
+
75
+ // 톤 신호 — 아이콘 색만
76
+ <Hint icon="shield-check" tone="success">
77
+ 2단계 인증이 활성화되어 있습니다.
78
+ </Hint>
79
+
80
+ <Hint icon="warning" tone="warning">
81
+ 이 작업은 되돌릴 수 없습니다.
50
82
  </Hint>
51
83
  ```
52
84
 
53
85
  ### 구현 노트 (벤더 코드에만 적용)
54
- 아이콘 수직 정렬이 **반복 함정**이라 명시:
55
- - flex `align-items: center/flex-start` + `margin-top` 보정 조합은 **금지** — line-height 바뀔 때마다 어긋남
56
- - 내부 구현은 **`display: inline` + `vertical-align: text-bottom`** (또는 유사 line-box 정렬) 조합으로 자동 정렬
57
- - line-height는 `1.6` 고정 (muted 다줄 가독성 — [[tokens/spacing]] "텍스트 gap·line-height" 기본 1.5보다 한 눈금 높음)
86
+ - 아이콘 수직 정렬은 **`display: inline-flex` + `vertical-align: text-bottom`** 조합으로 line box 안에 자동 정렬. flex `align-items` + `margin-top` 보정 조합은 **금지** (line-height 바뀔 때마다 어긋남).
87
+ - 둘째 줄 들여쓰기는 **hanging indent** 패턴: `padding-inline-start: icon + gap` + `text-indent: -(icon + gap)`.
88
+ - line-height는 `1.6` 고정 (muted 다줄 가독성).
89
+ - tone은 `--hint-accent` CSS 변수 하나만 스왑 — `--hint-fg`는 전 톤에서 `--fg-muted` 유지.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@conscience-technology/ctds",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "CTDS — 하이브리드 디자인 시스템 설치 · 온보딩 CLI (shadcn식 코드 생성기)",
5
5
  "type": "module",
6
6
  "sideEffects": [