@pantoia/ui 0.1.0 → 0.2.0

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.
Files changed (63) hide show
  1. package/accordion/AGENTS.md +5 -5
  2. package/accordion/index.tsx +17 -9
  3. package/alert-dialog/AGENTS.md +7 -7
  4. package/alert-dialog/index.tsx +20 -10
  5. package/badge/AGENTS.md +5 -5
  6. package/badge/index.tsx +7 -7
  7. package/button/AGENTS.md +7 -7
  8. package/button/index.tsx +36 -36
  9. package/callout/AGENTS.md +4 -5
  10. package/callout/index.tsx +13 -14
  11. package/checkbox/AGENTS.md +5 -5
  12. package/checkbox/index.tsx +15 -13
  13. package/combobox/AGENTS.md +13 -13
  14. package/combobox/index.tsx +82 -38
  15. package/command-palette/AGENTS.md +3 -2
  16. package/command-palette/index.tsx +4 -2
  17. package/dialog/AGENTS.md +6 -6
  18. package/dialog/index.tsx +11 -5
  19. package/dist/index.js +63 -34
  20. package/drawer/AGENTS.md +5 -5
  21. package/drawer/index.tsx +8 -4
  22. package/dropdown-menu/AGENTS.md +12 -12
  23. package/dropdown-menu/index.tsx +23 -11
  24. package/field/AGENTS.md +8 -8
  25. package/field/index.tsx +27 -9
  26. package/input/AGENTS.md +4 -4
  27. package/input/index.tsx +8 -8
  28. package/kbd/AGENTS.md +1 -1
  29. package/kbd/index.tsx +6 -2
  30. package/label/AGENTS.md +1 -1
  31. package/label/index.tsx +3 -1
  32. package/number-field/AGENTS.md +6 -6
  33. package/number-field/index.tsx +22 -16
  34. package/package.json +3 -6
  35. package/popover/AGENTS.md +4 -4
  36. package/popover/index.tsx +14 -4
  37. package/press.css +7 -7
  38. package/radio-group/AGENTS.md +8 -8
  39. package/radio-group/index.tsx +20 -16
  40. package/registry.json +3 -51
  41. package/select/AGENTS.md +10 -10
  42. package/select/index.tsx +41 -23
  43. package/styles.css +291 -139
  44. package/switch/AGENTS.md +4 -4
  45. package/switch/index.tsx +13 -13
  46. package/table/AGENTS.md +9 -9
  47. package/table/index.tsx +17 -7
  48. package/tabs/AGENTS.md +4 -4
  49. package/tabs/index.module.css +1 -1
  50. package/tabs/index.tsx +17 -9
  51. package/textarea/AGENTS.md +4 -4
  52. package/textarea/index.tsx +8 -8
  53. package/toast/AGENTS.md +5 -13
  54. package/toast/index.tsx +31 -37
  55. package/toggle/AGENTS.md +4 -4
  56. package/toggle/index.tsx +10 -9
  57. package/tooltip/AGENTS.md +2 -2
  58. package/tooltip/index.tsx +14 -4
  59. package/code-block/AGENTS.md +0 -60
  60. package/code-block/index.module.css +0 -46
  61. package/code-block/index.tsx +0 -127
  62. package/copy-button/AGENTS.md +0 -43
  63. package/copy-button/index.tsx +0 -119
package/dist/index.js CHANGED
@@ -6,12 +6,12 @@ import { createInterface } from 'node:readline/promises';
6
6
  const HERE = join(import.meta.dirname, '..');
7
7
  const CONFIG = 'components.json';
8
8
  const DEFAULT_PATH = 'components/ui';
9
- const USAGE = `pantoia-ui <명령>
9
+ const USAGE = `pantoia-ui <명령어>
10
10
 
11
- init [--path <디렉터리>] ${CONFIG} 을 만든다
12
- add <이름...> [--force] 소스를 복사한다
13
- list 받을 수 있는 것을 늘어놓는다
14
- diff [이름] 복사본과 패키지의 지금 것을 견준다`;
11
+ init [--path <경로>] 컴포넌트를 저장할 위치를 정해요
12
+ add <이름...> [--force] 컴포넌트를 추가해요
13
+ list 추가할 수 있는 컴포넌트를 확인해요
14
+ diff [이름] 추가한 파일과 최신 파일을 비교해요`;
15
15
  function fail(message) {
16
16
  console.error(message);
17
17
  exit(1);
@@ -27,7 +27,7 @@ function registry() {
27
27
  function find(name) {
28
28
  const item = registry().find(candidate => candidate.name === name);
29
29
  if (!item)
30
- fail(`${name} 은 목록에 없다. pantoia-ui list 로 이름을 본다`);
30
+ fail(`목록에 없는 컴포넌트예요: ${name}\npantoia-ui list 로 이름을 확인하세요.`);
31
31
  return item;
32
32
  }
33
33
  function configPath() {
@@ -59,6 +59,14 @@ function resolve(names) {
59
59
  }
60
60
  return [...seen.values()];
61
61
  }
62
+ async function ask(question, fallback) {
63
+ if (!stdin.isTTY)
64
+ return fallback;
65
+ const rl = createInterface({ input: stdin, output: stdout });
66
+ const answer = await rl.question(`${question} (${fallback}) `);
67
+ rl.close();
68
+ return answer.trim() || fallback;
69
+ }
62
70
  async function confirm(question) {
63
71
  if (!stdin.isTTY)
64
72
  return false;
@@ -67,59 +75,78 @@ async function confirm(question) {
67
75
  rl.close();
68
76
  return answer.trim().toLowerCase() === 'y';
69
77
  }
70
- async function copy(item, root, force) {
78
+ async function copy(item, base, force) {
79
+ let skipped = false;
71
80
  for (const file of item.files ?? []) {
72
81
  const source = readFileSync(join(HERE, file.path), 'utf8');
73
- const target = join(root, file.target ?? file.path);
82
+ const shown = join(base, file.target ?? file.path);
83
+ const target = join(cwd(), shown);
74
84
  if (existsSync(target) && readFileSync(target, 'utf8') !== source) {
75
- if (!force && !await confirm(`${file.path} 이 이미 있고 내용이 다르다. 덮어쓰나?`)) {
76
- console.log(` 건너뛴다 ${file.path} — 덮어쓰려면 --force`);
85
+ if (!force && !await confirm(`${shown} 파일이 이미 있고 내용이 달라요. 덮어쓸까요?`)) {
86
+ console.log(` ${shown} (건너뜀)`);
87
+ skipped = true;
77
88
  continue;
78
89
  }
79
90
  }
80
91
  mkdirSync(dirname(target), { recursive: true });
81
92
  writeFileSync(target, source);
82
- console.log(` 썼다 ${file.path}`);
93
+ console.log(` ${shown}`);
83
94
  }
95
+ return skipped;
84
96
  }
85
97
  async function commandInit(args) {
86
98
  const existing = readConfig();
87
99
  if (existing)
88
- fail(`${CONFIG} 이 이미 있다`);
89
- const path = option(args, '--path') ?? DEFAULT_PATH;
100
+ fail(`${CONFIG} 이 이미 있어요.`);
101
+ const path = option(args, '--path')
102
+ ?? await ask('컴포넌트를 저장할 위치를 입력하세요.', DEFAULT_PATH);
90
103
  writeConfig({ path, components: {} });
91
- console.log(`${CONFIG} 을 만들었다 — 소스는 ${path} 아래로 간다`);
92
- console.log('스타일을 한 줄 넣는다 — @import "@pantoia/ui/styles.css";');
104
+ console.log(`${CONFIG} 을 만들었어요. 컴포넌트는 ${path} 에 저장돼요.`);
105
+ console.log('전역 CSS 에 아래 한 줄을 추가하세요.');
106
+ console.log('');
107
+ console.log(' @import "@pantoia/ui/styles.css";');
93
108
  }
94
109
  async function commandAdd(args) {
95
110
  const names = args.filter(arg => !arg.startsWith('--'));
96
111
  if (names.length === 0)
97
- fail('받을 이름을 적는다 — pantoia-ui add button');
112
+ fail('추가할 컴포넌트 이름을 입력하세요. 예) pantoia-ui add button');
98
113
  const force = args.includes('--force');
99
- let config = readConfig();
100
- if (!config) {
101
- config = { path: option(args, '--path') ?? DEFAULT_PATH, components: {} };
102
- console.log(`${CONFIG} 이 없어 만들었다 — 소스는 ${config.path} 아래로 간다`);
103
- }
114
+ const config = readConfig();
115
+ if (!config)
116
+ fail(`${CONFIG} 이 없어요. 먼저 pantoia-ui init 을 실행하세요.`);
104
117
  const items = resolve(names);
105
- const root = join(cwd(), config.path);
118
+ const asked = items.filter(item => names.includes(item.name));
106
119
  const extra = items.filter(item => !names.includes(item.name));
107
- if (extra.length > 0)
108
- console.log(`함께 받는다 — ${extra.map(item => item.name).join(' · ')}`);
109
- for (const item of items) {
120
+ let skipped = false;
121
+ let first = true;
122
+ for (const item of asked) {
123
+ if (!first)
124
+ console.log('');
125
+ first = false;
110
126
  console.log(item.name);
111
- await copy(item, root, force);
127
+ skipped = await copy(item, config.path, force) || skipped;
112
128
  config.components[item.name] = version();
113
129
  }
130
+ if (extra.length > 0) {
131
+ console.log('');
132
+ console.log('함께 필요한 파일도 추가했어요.');
133
+ for (const item of extra) {
134
+ skipped = await copy(item, config.path, force) || skipped;
135
+ config.components[item.name] = version();
136
+ }
137
+ }
114
138
  writeConfig(config);
115
- const dependencies = [...new Set(items.flatMap(item => item.dependencies ?? []))].sort();
116
- if (dependencies.length > 0)
117
- console.log(`\nnpm 의존을 적는다 — ${dependencies.join(' · ')}`);
139
+ if (skipped) {
140
+ console.log('');
141
+ console.log('덮어쓰려면 --force 옵션을 붙이세요.');
142
+ }
118
143
  }
119
144
  function commandList() {
145
+ console.log(`${'이름'.padEnd(16)}${'종류'.padEnd(7)}같이 추가되는 컴포넌트`);
120
146
  for (const item of registry()) {
121
- const together = (item.registryDependencies ?? []).join(' · ');
122
- console.log(`${item.name.padEnd(18)}${(item.meta?.layer ?? '').padEnd(9)}${together}`);
147
+ const layer = item.meta?.layer ?? (item.type === 'registry:lib' ? 'lib' : '');
148
+ const together = (item.registryDependencies ?? []).join(', ');
149
+ console.log(`${item.name.padEnd(18)}${layer.padEnd(9)}${together}`.trimEnd());
123
150
  }
124
151
  }
125
152
  function lcs(a, b) {
@@ -154,7 +181,7 @@ function difference(mine, theirs) {
154
181
  async function commandDiff(args) {
155
182
  const config = readConfig();
156
183
  if (!config)
157
- fail(`${CONFIG} 이 없다 — pantoia-ui init 을 먼저 돌린다`);
184
+ fail(`${CONFIG} 이 없어요. 먼저 pantoia-ui add 로 컴포넌트를 추가하세요.`);
158
185
  const names = args.filter(arg => !arg.startsWith('--'));
159
186
  const wanted = names.length > 0 ? names : Object.keys(config.components);
160
187
  let changed = 0;
@@ -167,14 +194,16 @@ async function commandDiff(args) {
167
194
  const theirs = readFileSync(join(HERE, file.path), 'utf8');
168
195
  if (mine === theirs)
169
196
  continue;
197
+ if (changed === 0)
198
+ console.log('- 는 내 파일, + 는 최신 파일이에요.');
170
199
  changed++;
171
- console.log(`\n${file.path}`);
200
+ console.log(`\n${join(config.path, file.target ?? file.path)}`);
172
201
  for (const line of difference(mine.split('\n'), theirs.split('\n')))
173
202
  console.log(line);
174
203
  }
175
204
  }
176
205
  if (changed === 0)
177
- console.log('갈린 것이 없다');
206
+ console.log('달라진 파일이 없어요.');
178
207
  }
179
208
  const [command, ...args] = argv.slice(2);
180
209
  if (command === 'init')
package/drawer/AGENTS.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ```
4
4
  받기 npx @pantoia/ui add drawer
5
5
  층 ui
6
- 파일 drawer/index.tsx · drawer/index.module.css · drawer/AGENTS.md
6
+ 파일 drawer/index.tsx, drawer/index.module.css, drawer/AGENTS.md
7
7
  ```
8
8
 
9
9
  ## 부위
@@ -11,8 +11,8 @@
11
11
  | 이름 | props |
12
12
  |---|---|
13
13
  | `Drawer` | `DrawerProps` |
14
- | `DrawerTrigger` | — |
15
- | `DrawerClose` | — |
14
+ | `DrawerTrigger` | - |
15
+ | `DrawerClose` | - |
16
16
  | `DrawerContent` | `ComponentProps<typeof Dialog.Content>` |
17
17
  | `DrawerHeader` | `DrawerHeaderProps` |
18
18
  | `DrawerTitle` | `ComponentProps<typeof Dialog.Title>` |
@@ -51,11 +51,11 @@
51
51
  | `className` | `string` | |
52
52
  | `children` | `ReactNode` | |
53
53
 
54
- ## 변형 — `drawerContent`
54
+ ## 변형 - `drawerContent`
55
55
 
56
56
  | 축 | 값 | 기본 |
57
57
  |---|---|---|
58
- | `side` | `left` · `right` | — |
58
+ | `side` | `left`, `right` | - |
59
59
 
60
60
  ## 함께 받는 것
61
61
 
package/drawer/index.tsx CHANGED
@@ -117,7 +117,7 @@ export function DrawerClose({ children }: { children: ReactNode }) {
117
117
  }
118
118
 
119
119
  export const drawerContent = tv({
120
- base: 'bg-float text-fg z-overlay fixed inset-y-0'
120
+ base: 'sheet text-foreground z-overlay fixed inset-y-0'
121
121
  + ' flex w-full flex-col gap-stack overflow-y-auto p-4 sm:w-120'
122
122
  + ' focus-visible:outline-none',
123
123
  variants: {
@@ -137,7 +137,7 @@ export function DrawerContent({
137
137
 
138
138
  return (
139
139
  <Dialog.Portal>
140
- <Dialog.Overlay className="bg-overlay z-overlay fixed inset-0 open:animate-fade-in closed:animate-fade-out" />
140
+ <Dialog.Overlay className="bg-background-overlay z-overlay fixed inset-0 open:animate-fade-in closed:animate-fade-out" />
141
141
 
142
142
  <Dialog.Content
143
143
  {...rest}
@@ -187,12 +187,16 @@ export function DrawerHeader({ lead, trail, className, children }: DrawerHeaderP
187
187
  )
188
188
  }
189
189
 
190
+ const drawerTitle = tv({ base: 'text-section min-w-0' })
191
+
190
192
  export function DrawerTitle({ className, ...rest }: ComponentProps<typeof Dialog.Title>) {
191
- return <Dialog.Title {...rest} className={tv({ base: 'text-section min-w-0' })({ className })} />
193
+ return <Dialog.Title {...rest} className={drawerTitle({ className })} />
192
194
  }
193
195
 
196
+ const drawerDescription = tv({ base: 'text-prose text-foreground-muted' })
197
+
194
198
  export function DrawerDescription({ className, ...rest }: ComponentProps<typeof Dialog.Description>) {
195
199
  return (
196
- <Dialog.Description {...rest} className={tv({ base: 'text-prose text-fg-muted' })({ className })} />
200
+ <Dialog.Description {...rest} className={drawerDescription({ className })} />
197
201
  )
198
202
  }
@@ -3,7 +3,7 @@
3
3
  ```
4
4
  받기 npx @pantoia/ui add dropdown-menu
5
5
  층 ui
6
- 파일 dropdown-menu/index.tsx · dropdown-menu/AGENTS.md
6
+ 파일 dropdown-menu/index.tsx, dropdown-menu/AGENTS.md
7
7
  ```
8
8
 
9
9
  ## 부위
@@ -11,16 +11,16 @@
11
11
  | 이름 | props |
12
12
  |---|---|
13
13
  | `DropdownMenu` | `DropdownMenuProps` |
14
- | `DropdownMenuTrigger` | — |
14
+ | `DropdownMenuTrigger` | - |
15
15
  | `DropdownMenuContent` | `DropdownMenuContentProps` |
16
- | `DropdownMenuGroup` | — |
16
+ | `DropdownMenuGroup` | - |
17
17
  | `DropdownMenuItem` | `DropdownMenuItemProps` |
18
- | `DropdownMenuCheckboxItem` | — |
19
- | `DropdownMenuRadioGroup` | — |
20
- | `DropdownMenuRadioItem` | — |
21
- | `DropdownMenuSeparator` | — |
22
- | `DropdownMenuSub` | — |
23
- | `DropdownMenuSubTrigger` | — |
18
+ | `DropdownMenuCheckboxItem` | - |
19
+ | `DropdownMenuRadioGroup` | - |
20
+ | `DropdownMenuRadioItem` | - |
21
+ | `DropdownMenuSeparator` | - |
22
+ | `DropdownMenuSub` | - |
23
+ | `DropdownMenuSubTrigger` | - |
24
24
  | `DropdownMenuSubContent` | `ComponentProps<typeof MenuPrimitive.SubContent>` |
25
25
 
26
26
  ### `DropdownMenuTrigger`
@@ -118,11 +118,11 @@
118
118
  | `lead` | `ReactNode` | |
119
119
  | `trail` | `ReactNode` | |
120
120
 
121
- ## 변형 — `menuItem`
121
+ ## 변형 - `menuItem`
122
122
 
123
123
  | 축 | 값 | 기본 |
124
124
  |---|---|---|
125
- | `color` | `neutral` · `danger` | `neutral` |
125
+ | `color` | `neutral`, `danger` | `neutral` |
126
126
 
127
127
  ## 함께 받는 것
128
128
 
@@ -130,4 +130,4 @@
130
130
 
131
131
  ## npm 의존
132
132
 
133
- `lucide-react` · `radix-ui`
133
+ `lucide-react`, `radix-ui`
@@ -14,15 +14,15 @@ export const menuItem = tv({
14
14
 
15
15
  variants: {
16
16
  color: {
17
- neutral: 'text-fg data-highlighted:bg-hover data-disabled:text-fg-disabled',
18
- danger: 'text-danger data-highlighted:bg-danger-weak data-disabled:text-danger/40',
17
+ neutral: 'text-foreground data-highlighted:bg-bg-neutral-hover data-disabled:text-foreground-disabled',
18
+ danger: 'text-fg-danger data-highlighted:bg-bg-danger-weak data-disabled:text-fg-danger/40',
19
19
  },
20
20
  },
21
21
 
22
22
  defaultVariants: { color: 'neutral' },
23
23
  })
24
24
 
25
- const PANEL = 'menu bg-float text-fg rounded-float shadow-s3 z-overlay'
25
+ const PANEL = 'menu float text-foreground rounded-float z-overlay'
26
26
  + ' flex min-w-44 flex-col gap-px p-1'
27
27
  + ' open:animate-enter closed:animate-exit'
28
28
  + ' focus-visible:outline-none'
@@ -51,6 +51,8 @@ export interface DropdownMenuContentProps extends Omit<ComponentProps<typeof Men
51
51
  arrow?: boolean
52
52
  }
53
53
 
54
+ const menuPanel = tv({ base: PANEL })
55
+
54
56
  export function DropdownMenuContent({
55
57
  align = 'start',
56
58
  arrow = true,
@@ -65,25 +67,33 @@ export function DropdownMenuContent({
65
67
  align={align}
66
68
  sideOffset={8}
67
69
  arrowPadding={16}
68
- className={tv({ base: PANEL })({ className })}
70
+ className={menuPanel({ className })}
69
71
  >
70
72
  {children}
71
73
 
72
- {arrow && <MenuPrimitive.Arrow className="fill-float" width={12} height={6} />}
74
+ {arrow && (
75
+ <MenuPrimitive.Arrow asChild width={12} height={6}>
76
+ <svg viewBox="0 0 12 6" preserveAspectRatio="none" className="fill-background-panel -translate-y-px">
77
+ <path d="M0 0L4.58579 4.58579C5.36683 5.36683 6.63316 5.36684 7.41421 4.58579L12 0" className="stroke-line stroke-1" />
78
+ </svg>
79
+ </MenuPrimitive.Arrow>
80
+ )}
73
81
  </MenuPrimitive.Content>
74
82
  </MenuPrimitive.Portal>
75
83
  )
76
84
  }
77
85
 
86
+ const menuGroup = tv({ base: 'flex flex-col gap-px' })
87
+
78
88
  export function DropdownMenuGroup({ label, className, children }: {
79
89
  label?: string
80
90
  className?: string
81
91
  children?: ReactNode
82
92
  }) {
83
93
  return (
84
- <MenuPrimitive.Group className={tv({ base: 'flex flex-col gap-px' })({ className })}>
94
+ <MenuPrimitive.Group className={menuGroup({ className })}>
85
95
  {label && (
86
- <MenuPrimitive.Label className="text-caption text-fg-subtle px-2 pb-1 pt-2">
96
+ <MenuPrimitive.Label className="text-caption text-foreground-subtle px-2 pb-1 pt-2">
87
97
  {label}
88
98
  </MenuPrimitive.Label>
89
99
  )}
@@ -161,7 +171,7 @@ export function DropdownMenuRadioItem({ value, disabled, className, children }:
161
171
  <MenuPrimitive.RadioItem value={value} disabled={disabled} className={menuItem({ className })}>
162
172
  <span className="menu-lead grid size-4 shrink-0 place-items-center">
163
173
  <MenuPrimitive.ItemIndicator>
164
- <span className="bg-control-on in-disabled:bg-control-on-disabled size-2 rounded-full" />
174
+ <span className="bg-bg-neutral-solid in-disabled:bg-bg-neutral-solid-checked-disabled size-2 rounded-full" />
165
175
  </MenuPrimitive.ItemIndicator>
166
176
  </span>
167
177
 
@@ -170,8 +180,10 @@ export function DropdownMenuRadioItem({ value, disabled, className, children }:
170
180
  )
171
181
  }
172
182
 
183
+ const menuSeparator = tv({ base: 'bg-line -mx-1 my-1 h-px' })
184
+
173
185
  export function DropdownMenuSeparator({ className }: { className?: string }) {
174
- return <MenuPrimitive.Separator className={tv({ base: 'bg-line -mx-1 my-1 h-px' })({ className })} />
186
+ return <MenuPrimitive.Separator className={menuSeparator({ className })} />
175
187
  }
176
188
 
177
189
  export function DropdownMenuSub({ open, defaultOpen, onOpenChange, children }: {
@@ -203,7 +215,7 @@ export function DropdownMenuSubTrigger({ disabled, lead, className, children }:
203
215
 
204
216
  <span className="min-w-0 grow">{children}</span>
205
217
 
206
- <ChevronRight className="text-fg-subtle size-4 shrink-0" />
218
+ <ChevronRight className="text-foreground-subtle size-4 shrink-0" />
207
219
  </MenuPrimitive.SubTrigger>
208
220
  )
209
221
  }
@@ -214,7 +226,7 @@ export function DropdownMenuSubContent({ className, children, ...rest }: Compone
214
226
  <MenuPrimitive.SubContent
215
227
  {...rest}
216
228
  sideOffset={4}
217
- className={tv({ base: PANEL })({ className })}
229
+ className={menuPanel({ className })}
218
230
  >
219
231
  {children}
220
232
  </MenuPrimitive.SubContent>
package/field/AGENTS.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ```
4
4
  받기 npx @pantoia/ui add field
5
5
  층 ui
6
- 파일 field/index.tsx · field/AGENTS.md
6
+ 파일 field/index.tsx, field/AGENTS.md
7
7
  ```
8
8
 
9
9
  ## 부위
@@ -11,11 +11,11 @@
11
11
  | 이름 | props |
12
12
  |---|---|
13
13
  | `Field` | `FieldProps` |
14
- | `FieldLabel` | — |
15
- | `FieldHelp` | — |
14
+ | `FieldLabel` | - |
15
+ | `FieldHelp` | - |
16
16
  | `FieldError` | `FieldErrorProps` |
17
- | `FieldPending` | — |
18
- | `FieldSuccess` | — |
17
+ | `FieldPending` | - |
18
+ | `FieldSuccess` | - |
19
19
 
20
20
  ### `FieldLabel`
21
21
 
@@ -81,15 +81,15 @@
81
81
  | `className` | `string` | |
82
82
  | `children` | `ReactNode` | |
83
83
 
84
- ## 변형 — `field`
84
+ ## 변형 - `field`
85
85
 
86
86
  | 축 | 값 | 기본 |
87
87
  |---|---|---|
88
- | `layout` | `stack` · `inline` · `split` | `stack` |
88
+ | `layout` | `stack`, `inline`, `split` | `stack` |
89
89
 
90
90
  ## 함께 받는 것
91
91
 
92
- `label` · `tv`
92
+ `label`, `tv`
93
93
 
94
94
  ## npm 의존
95
95
 
package/field/index.tsx CHANGED
@@ -66,6 +66,8 @@ export function Field({ invalid = false, layout, className, children }: FieldPro
66
66
  )
67
67
  }
68
68
 
69
+ const fieldLabel = tv({ base: 'group-has-disabled:text-foreground-disabled' })
70
+
69
71
  export function FieldLabel({ className, children }: { className?: string, children?: ReactNode }) {
70
72
  const ctx = use(Context)
71
73
 
@@ -73,21 +75,25 @@ export function FieldLabel({ className, children }: { className?: string, childr
73
75
  <Label
74
76
  id={ctx?.labelId}
75
77
  htmlFor={ctx?.id}
76
- className={tv({ base: 'group-has-disabled:text-fg-disabled' })({ className })}
78
+ className={fieldLabel({ className })}
77
79
  >
78
80
  {children}
79
81
  </Label>
80
82
  )
81
83
  }
82
84
 
85
+ const fieldHelp = tv({
86
+ base: 'basis-full text-caption text-foreground-subtle'
87
+ + ' group-has-disabled:text-foreground-disabled',
88
+ })
89
+
83
90
  export function FieldHelp({ className, children }: { className?: string, children?: ReactNode }) {
84
91
  const ctx = use(Context)
85
92
 
86
93
  return (
87
94
  <p
88
95
  id={ctx?.messageId}
89
- className={tv({ base: 'basis-full text-caption text-fg-subtle'
90
- + ' group-has-disabled:text-fg-disabled' })({ className })}
96
+ className={fieldHelp({ className })}
91
97
  >
92
98
  {children}
93
99
  </p>
@@ -100,6 +106,11 @@ export interface FieldErrorProps {
100
106
  children?: ReactNode
101
107
  }
102
108
 
109
+ const fieldError = tv({
110
+ base: 'basis-full text-caption text-fg-danger'
111
+ + ' flex items-start gap-tight',
112
+ })
113
+
103
114
  export function FieldError({ items, className, children }: FieldErrorProps) {
104
115
  const ctx = use(Context)
105
116
  const list = items == null ? [] : Array.isArray(items) ? items : [items]
@@ -107,8 +118,7 @@ export function FieldError({ items, className, children }: FieldErrorProps) {
107
118
  return (
108
119
  <p
109
120
  id={ctx?.messageId}
110
- className={tv({ base: 'basis-full text-caption text-danger'
111
- + ' flex items-start gap-tight' })({ className })}
121
+ className={fieldError({ className })}
112
122
  >
113
123
  <CircleAlert className="mt-0.5 size-4 shrink-0" />
114
124
  <span className="flex min-w-0 flex-col gap-0.5">
@@ -118,14 +128,18 @@ export function FieldError({ items, className, children }: FieldErrorProps) {
118
128
  )
119
129
  }
120
130
 
131
+ const fieldPending = tv({
132
+ base: 'basis-full text-caption text-foreground-subtle'
133
+ + ' flex items-start gap-tight',
134
+ })
135
+
121
136
  export function FieldPending({ className, children }: { className?: string, children?: ReactNode }) {
122
137
  const ctx = use(Context)
123
138
 
124
139
  return (
125
140
  <p
126
141
  id={ctx?.messageId}
127
- className={tv({ base: 'basis-full text-caption text-fg-subtle'
128
- + ' flex items-start gap-tight' })({ className })}
142
+ className={fieldPending({ className })}
129
143
  >
130
144
  <LoaderCircle className="mt-0.5 size-4 shrink-0 animate-spin motion-reduce:animate-none" />
131
145
  <span className="min-w-0">{children}</span>
@@ -133,14 +147,18 @@ export function FieldPending({ className, children }: { className?: string, chil
133
147
  )
134
148
  }
135
149
 
150
+ const fieldSuccess = tv({
151
+ base: 'basis-full text-caption text-fg-success'
152
+ + ' flex items-start gap-tight',
153
+ })
154
+
136
155
  export function FieldSuccess({ className, children }: { className?: string, children?: ReactNode }) {
137
156
  const ctx = use(Context)
138
157
 
139
158
  return (
140
159
  <p
141
160
  id={ctx?.messageId}
142
- className={tv({ base: 'basis-full text-caption text-success'
143
- + ' flex items-start gap-tight' })({ className })}
161
+ className={fieldSuccess({ className })}
144
162
  >
145
163
  <CircleCheck className="mt-0.5 size-4 shrink-0" />
146
164
  <span className="min-w-0">{children}</span>
package/input/AGENTS.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ```
4
4
  받기 npx @pantoia/ui add input
5
5
  층 ui
6
- 파일 input/index.tsx · input/AGENTS.md
6
+ 파일 input/index.tsx, input/AGENTS.md
7
7
  ```
8
8
 
9
9
  ## 부위
@@ -23,12 +23,12 @@
23
23
  | `surface` | `'bare' \| 'well'` | |
24
24
  | `size` | `'sm' \| 'md' \| 'lg'` | |
25
25
 
26
- ## 변형 — `input`
26
+ ## 변형 - `input`
27
27
 
28
28
  | 축 | 값 | 기본 |
29
29
  |---|---|---|
30
- | `surface` | `bare` · `well` | `bare` |
31
- | `size` | `sm` · `md` · `lg` | `md` |
30
+ | `surface` | `bare`, `well` | `bare` |
31
+ | `size` | `sm`, `md`, `lg` | `md` |
32
32
 
33
33
  ## 함께 받는 것
34
34
 
package/input/index.tsx CHANGED
@@ -3,18 +3,18 @@ import type { InputHTMLAttributes } from 'react'
3
3
  import { tv } from '../tv'
4
4
 
5
5
  export const input = tv({
6
- base: 'w-full rounded-r8 text-value text-fg'
7
- + ' placeholder:text-fg-placeholder'
8
- + ' invalid:ring-2 invalid:ring-danger'
9
- + ' read-only:text-fg-muted'
6
+ base: 'w-full rounded-r8 text-value text-foreground'
7
+ + ' placeholder:text-foreground-placeholder'
8
+ + ' invalid:ring-2 invalid:ring-line-danger'
9
+ + ' read-only:text-foreground-muted'
10
10
  + ' transition-state'
11
- + ' focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
12
- + ' disabled:bg-disabled disabled:text-fg-disabled disabled:cursor-not-allowed',
11
+ + ' focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-line-focus-ring'
12
+ + ' disabled:bg-bg-neutral-disabled disabled:text-foreground-disabled disabled:cursor-not-allowed',
13
13
 
14
14
  variants: {
15
15
  surface: {
16
- bare: 'bg-transparent ring-1 ring-inset ring-line-strong read-only:bg-well',
17
- well: 'bg-well',
16
+ bare: 'bg-transparent ring-1 ring-inset ring-line-strong read-only:bg-background-well',
17
+ well: 'bg-background-well',
18
18
  },
19
19
  size: {
20
20
  sm: 'h-8 px-3',
package/kbd/AGENTS.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ```
4
4
  받기 npx @pantoia/ui add kbd
5
5
  층 ui
6
- 파일 kbd/index.tsx · kbd/AGENTS.md
6
+ 파일 kbd/index.tsx, kbd/AGENTS.md
7
7
  ```
8
8
 
9
9
  ## 부위
package/kbd/index.tsx CHANGED
@@ -2,12 +2,16 @@ import type { HTMLAttributes } from 'react'
2
2
 
3
3
  import { tv } from '../tv'
4
4
 
5
+ const kbd = tv({
6
+ base: 'bg-background-well text-foreground-muted text-micro rounded-mark inline-flex items-center'
7
+ + ' border border-line px-1.5 py-0.5 font-mono',
8
+ })
9
+
5
10
  export function Kbd({ className, ...rest }: HTMLAttributes<HTMLElement>) {
6
11
  return (
7
12
  <kbd
8
13
  {...rest}
9
- className={tv({ base: 'bg-well text-fg-muted text-micro rounded-mark inline-flex items-center'
10
- + ' border border-line px-1.5 py-0.5 font-mono' })({ className })}
14
+ className={kbd({ className })}
11
15
  />
12
16
  )
13
17
  }
package/label/AGENTS.md CHANGED
@@ -3,7 +3,7 @@
3
3
  ```
4
4
  받기 npx @pantoia/ui add label
5
5
  층 ui
6
- 파일 label/index.tsx · label/AGENTS.md
6
+ 파일 label/index.tsx, label/AGENTS.md
7
7
  ```
8
8
 
9
9
  ## 부위
package/label/index.tsx CHANGED
@@ -3,11 +3,13 @@ import type { ComponentProps } from 'react'
3
3
 
4
4
  import { tv } from '../tv'
5
5
 
6
+ const labelText = tv({ base: 'text-label text-foreground' })
7
+
6
8
  export function Label({ className, ...rest }: ComponentProps<typeof LabelPrimitive.Root>) {
7
9
  return (
8
10
  <LabelPrimitive.Root
9
11
  {...rest}
10
- className={tv({ base: 'text-label text-fg' })({ className })}
12
+ className={labelText({ className })}
11
13
  />
12
14
  )
13
15
  }