@pantoia/ui 0.2.0 → 0.3.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 (46) hide show
  1. package/accordion/AGENTS.md +1 -1
  2. package/accordion/index.tsx +10 -8
  3. package/alert-dialog/index.tsx +3 -3
  4. package/badge/index.tsx +2 -2
  5. package/button/index.tsx +4 -4
  6. package/callout/AGENTS.md +0 -4
  7. package/callout/index.tsx +29 -8
  8. package/checkbox/AGENTS.md +1 -1
  9. package/checkbox/index.tsx +4 -3
  10. package/combobox/AGENTS.md +1 -1
  11. package/combobox/index.tsx +24 -20
  12. package/command-palette/AGENTS.md +0 -4
  13. package/command-palette/index.tsx +4 -2
  14. package/dialog/index.tsx +3 -3
  15. package/dist/index.js +153 -25
  16. package/drawer/index.tsx +3 -3
  17. package/dropdown-menu/AGENTS.md +1 -1
  18. package/dropdown-menu/index.tsx +10 -7
  19. package/field/AGENTS.md +0 -4
  20. package/field/index.tsx +16 -8
  21. package/input/index.tsx +6 -6
  22. package/kbd/index.tsx +1 -1
  23. package/label/index.tsx +1 -1
  24. package/number-field/AGENTS.md +0 -4
  25. package/number-field/index.tsx +14 -10
  26. package/package.json +11 -10
  27. package/palette.css +114 -0
  28. package/popover/index.tsx +2 -2
  29. package/radio-group/index.tsx +1 -1
  30. package/registry.json +6 -20
  31. package/select/AGENTS.md +1 -1
  32. package/select/index.tsx +26 -19
  33. package/switch/index.tsx +1 -1
  34. package/table/index.tsx +4 -4
  35. package/tabs/AGENTS.md +1 -0
  36. package/tabs/index.module.css +3 -1
  37. package/tabs/index.tsx +9 -6
  38. package/tailwind.css +3 -0
  39. package/textarea/index.tsx +6 -6
  40. package/toast/index.tsx +5 -5
  41. package/toggle/index.tsx +4 -4
  42. package/{styles.css → tokens.css} +76 -303
  43. package/tooltip/index.tsx +2 -2
  44. package/tv.ts +0 -1
  45. package/utilities.css +119 -0
  46. package/press.css +0 -37
package/dist/index.js CHANGED
@@ -1,17 +1,45 @@
1
1
  #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
2
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
4
  import { dirname, join } from 'node:path';
4
- import { argv, cwd, exit, stdin, stdout } from 'node:process';
5
+ import { argv, cwd, exit, platform, stdin, stdout } from 'node:process';
5
6
  import { createInterface } from 'node:readline/promises';
6
7
  const HERE = join(import.meta.dirname, '..');
7
8
  const CONFIG = 'components.json';
8
- const DEFAULT_PATH = 'components/ui';
9
- const USAGE = `pantoia-ui <명령어>
9
+ const STYLES = '@import "@pantoia/ui/tailwind.css";';
10
+ const TAILWIND = /^@import\s+["']tailwindcss["'][^;]*;\s*$/;
11
+ const IMPORTED = /@import\s+["']@pantoia\/ui\//;
12
+ const FONTS = ['pretendard@^1.3.9', '@fontsource/jetbrains-mono@^5.3.0'];
13
+ const FONT_IMPORT = '@import "pretendard/dist/web/variable/pretendardvariable-dynamic-subset.css";';
14
+ const MONO = '"JetBrains Mono"';
15
+ const MONO_FACE = [
16
+ '@font-face {',
17
+ ` font-family: ${MONO};`,
18
+ ' src: url("@fontsource/jetbrains-mono/files/jetbrains-mono-latin-400-normal.woff2") format("woff2");',
19
+ ' font-weight: 400;',
20
+ ' font-display: swap;',
21
+ ' unicode-range: U+0000-00FF, U+2000-206F;',
22
+ '}',
23
+ ];
24
+ const MANAGERS = [
25
+ { lock: 'pnpm-lock.yaml', command: 'pnpm', verb: 'add' },
26
+ { lock: 'yarn.lock', command: 'yarn', verb: 'add' },
27
+ { lock: 'bun.lock', command: 'bun', verb: 'add' },
28
+ { lock: 'bun.lockb', command: 'bun', verb: 'add' },
29
+ { lock: 'package-lock.json', command: 'npm', verb: 'install' },
30
+ ];
31
+ const LAYOUTS = [
32
+ { css: 'app/globals.css', path: 'components/ui' },
33
+ { css: 'src/app/globals.css', path: 'src/components/ui' },
34
+ { css: 'app/app.css', path: 'app/components/ui' },
35
+ { css: 'src/index.css', path: 'src/components/ui' },
36
+ ];
37
+ const USAGE = `npx @pantoia/ui <명령어>
10
38
 
11
- init [--path <경로>] 컴포넌트를 저장할 위치를 정해요
12
- add <이름...> [--force] 컴포넌트를 추가해요
13
- list 추가할 수 있는 컴포넌트를 확인해요
14
- diff [이름] 추가한 파일과 최신 파일을 비교해요`;
39
+ init [--path <경로>] [--css <경로>] 컴포넌트를 저장할 위치와 전역 CSS 파일을 정해요
40
+ add <이름...> [--force] 컴포넌트를 추가해요
41
+ list 추가할 수 있는 컴포넌트를 확인해요
42
+ diff [이름] 추가한 파일과 최신 파일을 비교해요`;
15
43
  function fail(message) {
16
44
  console.error(message);
17
45
  exit(1);
@@ -27,7 +55,7 @@ function registry() {
27
55
  function find(name) {
28
56
  const item = registry().find(candidate => candidate.name === name);
29
57
  if (!item)
30
- fail(`목록에 없는 컴포넌트예요: ${name}\npantoia-ui list 로 이름을 확인하세요.`);
58
+ fail(`목록에 없는 컴포넌트예요: ${name}\nnpx @pantoia/ui list로 이름을 확인하세요.`);
31
59
  return item;
32
60
  }
33
61
  function configPath() {
@@ -67,13 +95,13 @@ async function ask(question, fallback) {
67
95
  rl.close();
68
96
  return answer.trim() || fallback;
69
97
  }
70
- async function confirm(question) {
98
+ async function confirm(question, fallback = false) {
71
99
  if (!stdin.isTTY)
72
- return false;
100
+ return fallback;
73
101
  const rl = createInterface({ input: stdin, output: stdout });
74
- const answer = await rl.question(`${question} (y/N) `);
102
+ const answer = (await rl.question(`${question} ${fallback ? '(Y/n)' : '(y/N)'} `)).trim().toLowerCase();
75
103
  rl.close();
76
- return answer.trim().toLowerCase() === 'y';
104
+ return answer === '' ? fallback : answer === 'y';
77
105
  }
78
106
  async function copy(item, base, force) {
79
107
  let skipped = false;
@@ -94,26 +122,126 @@ async function copy(item, base, force) {
94
122
  }
95
123
  return skipped;
96
124
  }
97
- async function commandInit(args) {
98
- const existing = readConfig();
99
- if (existing)
100
- fail(`${CONFIG} 이 이미 있어요.`);
101
- const path = option(args, '--path')
102
- ?? await ask('컴포넌트를 저장할 위치를 입력하세요.', DEFAULT_PATH);
103
- writeConfig({ path, components: {} });
104
- console.log(`${CONFIG} 을 만들었어요. 컴포넌트는 ${path} 에 저장돼요.`);
105
- console.log('전역 CSS 에 아래 한 줄을 추가하세요.');
125
+ function detect() {
126
+ return LAYOUTS.find(layout => existsSync(join(cwd(), layout.css))) ?? LAYOUTS[0];
127
+ }
128
+ const packageName = (spec) => spec.slice(0, spec.lastIndexOf('@'));
129
+ function installed() {
130
+ const file = join(cwd(), 'package.json');
131
+ if (!existsSync(file))
132
+ return new Set();
133
+ const pkg = JSON.parse(readFileSync(file, 'utf8'));
134
+ return new Set(Object.keys({ ...pkg.dependencies, ...pkg.devDependencies }));
135
+ }
136
+ async function installPackages() {
137
+ const have = installed();
138
+ const specs = have.has('@pantoia/ui') ? [] : [`@pantoia/ui@${version()}`];
139
+ let fonts = FONTS.every(spec => have.has(packageName(spec)));
140
+ if (!fonts) {
141
+ console.log('');
142
+ fonts = await confirm(`글꼴 패키지를 설치할까요? ${FONTS.map(packageName).join(', ')}`, true);
143
+ if (fonts)
144
+ specs.push(...FONTS);
145
+ }
146
+ const manager = MANAGERS.find(m => existsSync(join(cwd(), m.lock))) ?? MANAGERS.at(-1);
147
+ if (!fonts && !FONTS.every(spec => have.has(packageName(spec)))) {
148
+ console.log('글꼴을 설치하지 않았어요. 쓰려면 이 명령으로 설치하세요.');
149
+ console.log('');
150
+ console.log(` ${[manager.command, manager.verb, ...FONTS].join(' ')}`);
151
+ }
152
+ if (specs.length === 0)
153
+ return fonts;
154
+ const done = spawnSync(manager.command, [manager.verb, ...specs], { stdio: 'inherit', shell: platform === 'win32' });
155
+ if (done.status === 0)
156
+ return fonts;
157
+ console.log('패키지를 설치하지 못했어요. 이 명령으로 직접 설치하세요.');
158
+ console.log('');
159
+ console.log(` ${[manager.command, manager.verb, ...specs].join(' ')}`);
160
+ return false;
161
+ }
162
+ function additions(text, fonts) {
163
+ const head = IMPORTED.test(text) ? [] : [STYLES];
164
+ if (fonts && !text.includes(FONT_IMPORT))
165
+ head.push(FONT_IMPORT);
166
+ const tail = fonts && !text.includes(MONO) ? MONO_FACE : [];
167
+ return { head, tail };
168
+ }
169
+ async function importStyles(css, head, tail) {
170
+ const file = join(cwd(), css);
171
+ const text = readFileSync(file, 'utf8');
172
+ const ending = text.endsWith('\n') ? '\n' : '';
173
+ const lines = text.slice(0, text.length - ending.length).split('\n');
174
+ const ours = lines.findIndex(line => IMPORTED.test(line));
175
+ const at = ours === -1 ? lines.findIndex(line => TAILWIND.test(line)) : ours;
106
176
  console.log('');
107
- console.log(' @import "@pantoia/ui/styles.css";');
177
+ console.log(`${css}를 이렇게 수정해요.`);
178
+ console.log('');
179
+ for (const line of lines.slice(Math.max(0, at - 2), at + 1))
180
+ console.log(` ${line}`);
181
+ for (const line of head)
182
+ console.log(`+ ${line}`);
183
+ for (const line of lines.slice(at + 1, at + 3))
184
+ console.log(` ${line}`);
185
+ if (tail.length > 0) {
186
+ if (lines.length > at + 3)
187
+ console.log(' ...');
188
+ for (const line of tail)
189
+ console.log(`+ ${line}`);
190
+ }
191
+ console.log('');
192
+ if (!await confirm('수정할까요?', true))
193
+ return false;
194
+ const body = [...lines.slice(0, at + 1), ...head, ...lines.slice(at + 1)];
195
+ if (tail.length > 0)
196
+ body.push('', ...tail);
197
+ writeFileSync(file, body.join('\n') + ending);
198
+ return true;
199
+ }
200
+ async function commandInit(args) {
201
+ if (readConfig())
202
+ fail(`${CONFIG}이 이미 있어요.`);
203
+ const detected = detect();
204
+ const path = option(args, '--path') ?? await ask('컴포넌트를 저장할 위치를 입력하세요.', detected.path);
205
+ const css = option(args, '--css') ?? await ask('전역 CSS 파일 위치를 입력하세요.', detected.css);
206
+ if (!existsSync(join(cwd(), css)))
207
+ fail(`${css} 파일이 없어요. 전역 CSS 파일 위치를 확인하고 다시 실행하세요.`);
208
+ const text = readFileSync(join(cwd(), css), 'utf8');
209
+ if (!IMPORTED.test(text) && !text.split('\n').some(line => TAILWIND.test(line)))
210
+ fail(`${css}에 @import "tailwindcss";가 없어요. Tailwind CSS를 먼저 설치하세요.`);
211
+ const fonts = await installPackages();
212
+ const { head, tail } = additions(text, fonts);
213
+ const needed = head.length > 0 || tail.length > 0;
214
+ const added = needed && await importStyles(css, head, tail);
215
+ writeConfig({ path, css, components: {} });
216
+ if (stdin.isTTY)
217
+ console.log('');
218
+ console.log(`${CONFIG}을 만들었어요. 컴포넌트는 ${path}에 저장돼요.`);
219
+ if (!needed)
220
+ console.log(`${css}에 스타일이 이미 있어요.`);
221
+ else if (added)
222
+ console.log(`${css}에 스타일을 추가했어요.`);
223
+ else {
224
+ console.log(`${css}를 수정하지 않았어요. @import "tailwindcss"; 바로 아래에 이 줄을 직접 추가하세요.`);
225
+ console.log('');
226
+ for (const line of head)
227
+ console.log(` ${line}`);
228
+ if (tail.length > 0) {
229
+ console.log('');
230
+ console.log('파일 끝에는 이 블록을 추가하세요.');
231
+ console.log('');
232
+ for (const line of tail)
233
+ console.log(` ${line}`);
234
+ }
235
+ }
108
236
  }
109
237
  async function commandAdd(args) {
110
238
  const names = args.filter(arg => !arg.startsWith('--'));
111
239
  if (names.length === 0)
112
- fail('추가할 컴포넌트 이름을 입력하세요. 예) pantoia-ui add button');
240
+ fail('추가할 컴포넌트 이름을 입력하세요. 예) npx @pantoia/ui add button');
113
241
  const force = args.includes('--force');
114
242
  const config = readConfig();
115
243
  if (!config)
116
- fail(`${CONFIG} 이 없어요. 먼저 pantoia-ui init 을 실행하세요.`);
244
+ fail(`${CONFIG}이 없어요. 먼저 npx @pantoia/ui init을 실행하세요.`);
117
245
  const items = resolve(names);
118
246
  const asked = items.filter(item => names.includes(item.name));
119
247
  const extra = items.filter(item => !names.includes(item.name));
@@ -181,7 +309,7 @@ function difference(mine, theirs) {
181
309
  async function commandDiff(args) {
182
310
  const config = readConfig();
183
311
  if (!config)
184
- fail(`${CONFIG} 이 없어요. 먼저 pantoia-ui add 로 컴포넌트를 추가하세요.`);
312
+ fail(`${CONFIG}이 없어요. 먼저 npx @pantoia/ui add로 컴포넌트를 추가하세요.`);
185
313
  const names = args.filter(arg => !arg.startsWith('--'));
186
314
  const wanted = names.length > 0 ? names : Object.keys(config.components);
187
315
  let changed = 0;
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: 'sheet text-foreground z-overlay fixed inset-y-0'
120
+ base: 'sheet text-fg 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-background-overlay z-overlay fixed inset-0 open:animate-fade-in closed:animate-fade-out" />
140
+ <Dialog.Overlay className="bg-bg-overlay z-overlay fixed inset-0 open:animate-fade-in closed:animate-fade-out" />
141
141
 
142
142
  <Dialog.Content
143
143
  {...rest}
@@ -193,7 +193,7 @@ export function DrawerTitle({ className, ...rest }: ComponentProps<typeof Dialog
193
193
  return <Dialog.Title {...rest} className={drawerTitle({ className })} />
194
194
  }
195
195
 
196
- const drawerDescription = tv({ base: 'text-prose text-foreground-muted' })
196
+ const drawerDescription = tv({ base: 'text-prose text-fg-muted' })
197
197
 
198
198
  export function DrawerDescription({ className, ...rest }: ComponentProps<typeof Dialog.Description>) {
199
199
  return (
@@ -130,4 +130,4 @@
130
130
 
131
131
  ## npm 의존
132
132
 
133
- `lucide-react`, `radix-ui`
133
+ `radix-ui`
@@ -1,6 +1,5 @@
1
1
  'use client'
2
2
 
3
- import { Check, ChevronRight } from 'lucide-react'
4
3
  import { DropdownMenu as MenuPrimitive } from 'radix-ui'
5
4
  import type { ComponentProps, ReactNode } from 'react'
6
5
 
@@ -14,7 +13,7 @@ export const menuItem = tv({
14
13
 
15
14
  variants: {
16
15
  color: {
17
- neutral: 'text-foreground data-highlighted:bg-bg-neutral-hover data-disabled:text-foreground-disabled',
16
+ neutral: 'text-fg data-highlighted:bg-bg-neutral-hover data-disabled:text-fg-disabled',
18
17
  danger: 'text-fg-danger data-highlighted:bg-bg-danger-weak data-disabled:text-fg-danger/40',
19
18
  },
20
19
  },
@@ -22,7 +21,7 @@ export const menuItem = tv({
22
21
  defaultVariants: { color: 'neutral' },
23
22
  })
24
23
 
25
- const PANEL = 'menu float text-foreground rounded-float z-overlay'
24
+ const PANEL = 'menu float text-fg rounded-r12 z-overlay'
26
25
  + ' flex min-w-44 flex-col gap-px p-1'
27
26
  + ' open:animate-enter closed:animate-exit'
28
27
  + ' focus-visible:outline-none'
@@ -73,7 +72,7 @@ export function DropdownMenuContent({
73
72
 
74
73
  {arrow && (
75
74
  <MenuPrimitive.Arrow asChild width={12} height={6}>
76
- <svg viewBox="0 0 12 6" preserveAspectRatio="none" className="fill-background-panel -translate-y-px">
75
+ <svg viewBox="0 0 12 6" preserveAspectRatio="none" className="fill-bg-panel -translate-y-px">
77
76
  <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
77
  </svg>
79
78
  </MenuPrimitive.Arrow>
@@ -93,7 +92,7 @@ export function DropdownMenuGroup({ label, className, children }: {
93
92
  return (
94
93
  <MenuPrimitive.Group className={menuGroup({ className })}>
95
94
  {label && (
96
- <MenuPrimitive.Label className="text-caption text-foreground-subtle px-2 pb-1 pt-2">
95
+ <MenuPrimitive.Label className="text-caption text-fg-subtle px-2 pb-1 pt-2">
97
96
  {label}
98
97
  </MenuPrimitive.Label>
99
98
  )}
@@ -140,7 +139,9 @@ export function DropdownMenuCheckboxItem({ checked, onCheckedChange, disabled, c
140
139
  >
141
140
  <span className="menu-lead grid size-4 shrink-0 place-items-center">
142
141
  <MenuPrimitive.ItemIndicator>
143
- <Check className="size-4" />
142
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
143
+ <path d="M20 6 9 17l-5-5" />
144
+ </svg>
144
145
  </MenuPrimitive.ItemIndicator>
145
146
  </span>
146
147
 
@@ -215,7 +216,9 @@ export function DropdownMenuSubTrigger({ disabled, lead, className, children }:
215
216
 
216
217
  <span className="min-w-0 grow">{children}</span>
217
218
 
218
- <ChevronRight className="text-foreground-subtle size-4 shrink-0" />
219
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="text-fg-subtle size-4 shrink-0">
220
+ <path d="m9 18 6-6-6-6" />
221
+ </svg>
219
222
  </MenuPrimitive.SubTrigger>
220
223
  )
221
224
  }
package/field/AGENTS.md CHANGED
@@ -90,7 +90,3 @@
90
90
  ## 함께 받는 것
91
91
 
92
92
  `label`, `tv`
93
-
94
- ## npm 의존
95
-
96
- `lucide-react`
package/field/index.tsx CHANGED
@@ -1,6 +1,5 @@
1
1
  'use client'
2
2
 
3
- import { CircleAlert, CircleCheck, LoaderCircle } from 'lucide-react'
4
3
  import { createContext, use, useId, type ReactNode } from 'react'
5
4
 
6
5
  import { Label } from '../label'
@@ -66,7 +65,7 @@ export function Field({ invalid = false, layout, className, children }: FieldPro
66
65
  )
67
66
  }
68
67
 
69
- const fieldLabel = tv({ base: 'group-has-disabled:text-foreground-disabled' })
68
+ const fieldLabel = tv({ base: 'group-has-disabled:text-fg-disabled' })
70
69
 
71
70
  export function FieldLabel({ className, children }: { className?: string, children?: ReactNode }) {
72
71
  const ctx = use(Context)
@@ -83,8 +82,8 @@ export function FieldLabel({ className, children }: { className?: string, childr
83
82
  }
84
83
 
85
84
  const fieldHelp = tv({
86
- base: 'basis-full text-caption text-foreground-subtle'
87
- + ' group-has-disabled:text-foreground-disabled',
85
+ base: 'basis-full text-caption text-fg-subtle'
86
+ + ' group-has-disabled:text-fg-disabled',
88
87
  })
89
88
 
90
89
  export function FieldHelp({ className, children }: { className?: string, children?: ReactNode }) {
@@ -120,7 +119,11 @@ export function FieldError({ items, className, children }: FieldErrorProps) {
120
119
  id={ctx?.messageId}
121
120
  className={fieldError({ className })}
122
121
  >
123
- <CircleAlert className="mt-0.5 size-4 shrink-0" />
122
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="mt-0.5 size-4 shrink-0">
123
+ <circle cx="12" cy="12" r="10" />
124
+ <line x1="12" x2="12" y1="8" y2="12" />
125
+ <line x1="12" x2="12.01" y1="16" y2="16" />
126
+ </svg>
124
127
  <span className="flex min-w-0 flex-col gap-0.5">
125
128
  {children ?? list.map(t => <span key={t}>{t}</span>)}
126
129
  </span>
@@ -129,7 +132,7 @@ export function FieldError({ items, className, children }: FieldErrorProps) {
129
132
  }
130
133
 
131
134
  const fieldPending = tv({
132
- base: 'basis-full text-caption text-foreground-subtle'
135
+ base: 'basis-full text-caption text-fg-subtle'
133
136
  + ' flex items-start gap-tight',
134
137
  })
135
138
 
@@ -141,7 +144,9 @@ export function FieldPending({ className, children }: { className?: string, chil
141
144
  id={ctx?.messageId}
142
145
  className={fieldPending({ className })}
143
146
  >
144
- <LoaderCircle className="mt-0.5 size-4 shrink-0 animate-spin motion-reduce:animate-none" />
147
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="mt-0.5 size-4 shrink-0 animate-spin motion-reduce:animate-none">
148
+ <path d="M21 12a9 9 0 1 1-6.219-8.56" />
149
+ </svg>
145
150
  <span className="min-w-0">{children}</span>
146
151
  </p>
147
152
  )
@@ -160,7 +165,10 @@ export function FieldSuccess({ className, children }: { className?: string, chil
160
165
  id={ctx?.messageId}
161
166
  className={fieldSuccess({ className })}
162
167
  >
163
- <CircleCheck className="mt-0.5 size-4 shrink-0" />
168
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="mt-0.5 size-4 shrink-0">
169
+ <circle cx="12" cy="12" r="10" />
170
+ <path d="m16 9-5.5 5.5L8 12" />
171
+ </svg>
164
172
  <span className="min-w-0">{children}</span>
165
173
  </p>
166
174
  )
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-foreground'
7
- + ' placeholder:text-foreground-placeholder'
6
+ base: 'w-full rounded-r8 text-value text-fg'
7
+ + ' placeholder:text-fg-placeholder'
8
8
  + ' invalid:ring-2 invalid:ring-line-danger'
9
- + ' read-only:text-foreground-muted'
9
+ + ' read-only:text-fg-muted'
10
10
  + ' transition-state'
11
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',
12
+ + ' disabled:bg-bg-neutral-disabled disabled:text-fg-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-background-well',
17
- well: 'bg-background-well',
16
+ bare: 'bg-transparent ring-1 ring-inset ring-line-strong read-only:bg-bg-well',
17
+ well: 'bg-bg-well',
18
18
  },
19
19
  size: {
20
20
  sm: 'h-8 px-3',
package/kbd/index.tsx CHANGED
@@ -3,7 +3,7 @@ import type { HTMLAttributes } from 'react'
3
3
  import { tv } from '../tv'
4
4
 
5
5
  const kbd = tv({
6
- base: 'bg-background-well text-foreground-muted text-micro rounded-mark inline-flex items-center'
6
+ base: 'bg-bg-well text-fg-muted text-micro rounded-r4 inline-flex items-center'
7
7
  + ' border border-line px-1.5 py-0.5 font-mono',
8
8
  })
9
9
 
package/label/index.tsx CHANGED
@@ -3,7 +3,7 @@ import type { ComponentProps } from 'react'
3
3
 
4
4
  import { tv } from '../tv'
5
5
 
6
- const labelText = tv({ base: 'text-label text-foreground' })
6
+ const labelText = tv({ base: 'text-label text-fg' })
7
7
 
8
8
  export function Label({ className, ...rest }: ComponentProps<typeof LabelPrimitive.Root>) {
9
9
  return (
@@ -47,7 +47,3 @@
47
47
  ## 함께 받는 것
48
48
 
49
49
  `tv`
50
-
51
- ## npm 의존
52
-
53
- `lucide-react`
@@ -1,6 +1,5 @@
1
1
  'use client'
2
2
 
3
- import { Minus, Plus } from 'lucide-react'
4
3
  import { useEffect, useRef, useState, type InputHTMLAttributes, type PointerEvent as ReactPointerEvent, type KeyboardEvent as ReactKeyboardEvent } from 'react'
5
4
 
6
5
  import { tv } from '../tv'
@@ -13,18 +12,18 @@ export const numberField = tv({
13
12
  + ' has-disabled:bg-bg-neutral-disabled has-disabled:cursor-not-allowed'
14
13
  + ' has-focus-visible:outline-2 has-focus-visible:outline-offset-2'
15
14
  + ' has-focus-visible:outline-line-focus-ring',
16
- step: 'text-foreground-muted grid shrink-0 place-items-center rounded-r8'
15
+ step: 'text-fg-muted grid shrink-0 place-items-center rounded-r8'
17
16
  + ' transition-state press press-shrink enabled:not-aria-disabled:hover:bg-bg-neutral-hover'
18
- + ' disabled:text-foreground-disabled disabled:cursor-not-allowed'
19
- + ' aria-disabled:text-foreground-disabled aria-disabled:cursor-not-allowed',
20
- input: 'text-value text-foreground w-full min-w-0 bg-transparent text-center outline-none'
21
- + ' disabled:text-foreground-disabled disabled:cursor-not-allowed',
17
+ + ' disabled:text-fg-disabled disabled:cursor-not-allowed'
18
+ + ' aria-disabled:text-fg-disabled aria-disabled:cursor-not-allowed',
19
+ input: 'text-value text-fg w-full min-w-0 bg-transparent text-center outline-none'
20
+ + ' disabled:text-fg-disabled disabled:cursor-not-allowed',
22
21
  },
23
22
 
24
23
  variants: {
25
24
  surface: {
26
25
  bare: { root: 'bg-transparent ring-1 ring-inset ring-line-strong' },
27
- well: { root: 'bg-background-well' },
26
+ well: { root: 'bg-bg-well' },
28
27
  },
29
28
  size: {
30
29
  sm: { root: 'h-8', step: 'size-8' },
@@ -32,7 +31,7 @@ export const numberField = tv({
32
31
  lg: { root: 'h-10', step: 'size-10', input: 'text-t16' },
33
32
  },
34
33
  readonly: {
35
- true: { root: 'bg-background-well', input: 'text-foreground-muted' },
34
+ true: { root: 'bg-bg-well', input: 'text-fg-muted' },
36
35
  },
37
36
  },
38
37
 
@@ -193,7 +192,9 @@ export function NumberField(props: NumberFieldProps) {
193
192
  onPointerCancel={down.onPointerCancel}
194
193
  onLostPointerCapture={down.onLostPointerCapture}
195
194
  >
196
- <Minus className="size-4" />
195
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
196
+ <path d="M5 12h14" />
197
+ </svg>
197
198
  </button>
198
199
 
199
200
  <input
@@ -226,7 +227,10 @@ export function NumberField(props: NumberFieldProps) {
226
227
  onPointerCancel={up.onPointerCancel}
227
228
  onLostPointerCapture={up.onLostPointerCapture}
228
229
  >
229
- <Plus className="size-4" />
230
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
231
+ <path d="M5 12h14" />
232
+ <path d="M12 5v14" />
233
+ </svg>
230
234
  </button>
231
235
  </div>
232
236
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pantoia/ui",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Pantoia Design System",
5
5
  "homepage": "https://design.pantoia.com",
6
6
  "repository": {
@@ -20,14 +20,18 @@
20
20
  "dist",
21
21
  "tv.ts",
22
22
  "test-utils.ts",
23
- "styles.css",
24
- "press.css",
23
+ "tailwind.css",
24
+ "palette.css",
25
+ "tokens.css",
26
+ "utilities.css",
25
27
  "registry.json"
26
28
  ],
27
29
  "exports": {
28
30
  "./tv": "./tv.ts",
29
- "./styles.css": "./styles.css",
30
- "./press.css": "./press.css",
31
+ "./tailwind.css": "./tailwind.css",
32
+ "./palette.css": "./palette.css",
33
+ "./tokens.css": "./tokens.css",
34
+ "./utilities.css": "./utilities.css",
31
35
  "./registry.json": "./registry.json",
32
36
  "./test-utils": "./test-utils.ts",
33
37
  "./*": "./*/index.tsx"
@@ -39,16 +43,13 @@
39
43
  "access": "public"
40
44
  },
41
45
  "engines": {
42
- "node": ">=24"
46
+ "node": ">=20.11"
43
47
  },
44
48
  "scripts": {
45
49
  "prepack": "tsc cli/index.ts --ignoreConfig --outDir dist --target es2022 --module esnext --moduleResolution bundler --skipLibCheck --types node",
46
50
  "typecheck": "tsc --noEmit"
47
51
  },
48
52
  "dependencies": {
49
- "@fontsource/jetbrains-mono": "5.3.0",
50
- "lucide-react": "^1.39.0",
51
- "pretendard": "1.3.9",
52
53
  "radix-ui": "^1.6.7",
53
54
  "sonner": "^2.0.8",
54
55
  "tailwind-variants": "^3.3.1"
@@ -56,7 +57,7 @@
56
57
  "peerDependencies": {
57
58
  "react": "^19",
58
59
  "react-dom": "^19",
59
- "tailwindcss": "^4.3.3"
60
+ "tailwindcss": "^4.0.0"
60
61
  },
61
62
  "devDependencies": {
62
63
  "@pantoia/typescript-config": "*",