@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.
- package/accordion/AGENTS.md +1 -1
- package/accordion/index.tsx +10 -8
- package/alert-dialog/index.tsx +3 -3
- package/badge/index.tsx +2 -2
- package/button/index.tsx +4 -4
- package/callout/AGENTS.md +0 -4
- package/callout/index.tsx +29 -8
- package/checkbox/AGENTS.md +1 -1
- package/checkbox/index.tsx +4 -3
- package/combobox/AGENTS.md +1 -1
- package/combobox/index.tsx +24 -20
- package/command-palette/AGENTS.md +0 -4
- package/command-palette/index.tsx +4 -2
- package/dialog/index.tsx +3 -3
- package/dist/index.js +153 -25
- package/drawer/index.tsx +3 -3
- package/dropdown-menu/AGENTS.md +1 -1
- package/dropdown-menu/index.tsx +10 -7
- package/field/AGENTS.md +0 -4
- package/field/index.tsx +16 -8
- package/input/index.tsx +6 -6
- package/kbd/index.tsx +1 -1
- package/label/index.tsx +1 -1
- package/number-field/AGENTS.md +0 -4
- package/number-field/index.tsx +14 -10
- package/package.json +11 -10
- package/palette.css +114 -0
- package/popover/index.tsx +2 -2
- package/radio-group/index.tsx +1 -1
- package/registry.json +6 -20
- package/select/AGENTS.md +1 -1
- package/select/index.tsx +26 -19
- package/switch/index.tsx +1 -1
- package/table/index.tsx +4 -4
- package/tabs/AGENTS.md +1 -0
- package/tabs/index.module.css +3 -1
- package/tabs/index.tsx +9 -6
- package/tailwind.css +3 -0
- package/textarea/index.tsx +6 -6
- package/toast/index.tsx +5 -5
- package/toggle/index.tsx +4 -4
- package/{styles.css → tokens.css} +76 -303
- package/tooltip/index.tsx +2 -2
- package/tv.ts +0 -1
- package/utilities.css +119 -0
- 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
|
|
9
|
-
const
|
|
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}\
|
|
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
|
|
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
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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(
|
|
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
|
|
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}
|
|
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}
|
|
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-
|
|
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-
|
|
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-
|
|
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 (
|
package/dropdown-menu/AGENTS.md
CHANGED
package/dropdown-menu/index.tsx
CHANGED
|
@@ -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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
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-
|
|
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-
|
|
87
|
-
+ ' group-has-disabled:text-
|
|
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
|
-
<
|
|
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-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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-
|
|
7
|
-
+ ' placeholder:text-
|
|
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-
|
|
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-
|
|
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-
|
|
17
|
-
well: 'bg-
|
|
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-
|
|
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-
|
|
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 (
|
package/number-field/AGENTS.md
CHANGED
package/number-field/index.tsx
CHANGED
|
@@ -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-
|
|
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-
|
|
19
|
-
+ ' aria-disabled:text-
|
|
20
|
-
input: 'text-value text-
|
|
21
|
-
+ ' disabled:text-
|
|
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-
|
|
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-
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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.
|
|
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
|
-
"
|
|
24
|
-
"
|
|
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
|
-
"./
|
|
30
|
-
"./
|
|
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": ">=
|
|
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.
|
|
60
|
+
"tailwindcss": "^4.0.0"
|
|
60
61
|
},
|
|
61
62
|
"devDependencies": {
|
|
62
63
|
"@pantoia/typescript-config": "*",
|