@pantoia/ui 0.1.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 +5 -5
- package/accordion/index.tsx +21 -11
- package/alert-dialog/AGENTS.md +7 -7
- package/alert-dialog/index.tsx +20 -10
- package/badge/AGENTS.md +5 -5
- package/badge/index.tsx +8 -8
- package/button/AGENTS.md +7 -7
- package/button/index.tsx +34 -34
- package/callout/AGENTS.md +4 -9
- package/callout/index.tsx +42 -22
- package/checkbox/AGENTS.md +5 -5
- package/checkbox/index.tsx +19 -16
- package/combobox/AGENTS.md +13 -13
- package/combobox/index.tsx +90 -42
- package/command-palette/AGENTS.md +3 -6
- package/command-palette/index.tsx +7 -3
- package/dialog/AGENTS.md +6 -6
- package/dialog/index.tsx +11 -5
- package/dist/index.js +200 -43
- package/drawer/AGENTS.md +5 -5
- package/drawer/index.tsx +8 -4
- package/dropdown-menu/AGENTS.md +12 -12
- package/dropdown-menu/index.tsx +27 -12
- package/field/AGENTS.md +8 -12
- package/field/index.tsx +39 -13
- package/input/AGENTS.md +4 -4
- package/input/index.tsx +5 -5
- package/kbd/AGENTS.md +1 -1
- package/kbd/index.tsx +6 -2
- package/label/AGENTS.md +1 -1
- package/label/index.tsx +3 -1
- package/number-field/AGENTS.md +6 -10
- package/number-field/index.tsx +26 -16
- package/package.json +13 -15
- package/palette.css +114 -0
- package/popover/AGENTS.md +4 -4
- package/popover/index.tsx +14 -4
- package/radio-group/AGENTS.md +8 -8
- package/radio-group/index.tsx +20 -16
- package/registry.json +9 -71
- package/select/AGENTS.md +10 -10
- package/select/index.tsx +46 -21
- package/switch/AGENTS.md +4 -4
- package/switch/index.tsx +13 -13
- package/table/AGENTS.md +9 -9
- package/table/index.tsx +16 -6
- package/tabs/AGENTS.md +5 -4
- package/tabs/index.module.css +2 -0
- package/tabs/index.tsx +22 -11
- package/tailwind.css +3 -0
- package/textarea/AGENTS.md +4 -4
- package/textarea/index.tsx +5 -5
- package/toast/AGENTS.md +5 -13
- package/toast/index.tsx +27 -33
- package/toggle/AGENTS.md +4 -4
- package/toggle/index.tsx +8 -7
- package/tokens.css +272 -0
- package/tooltip/AGENTS.md +2 -2
- package/tooltip/index.tsx +14 -4
- package/tv.ts +0 -1
- package/utilities.css +119 -0
- package/code-block/AGENTS.md +0 -60
- package/code-block/index.module.css +0 -46
- package/code-block/index.tsx +0 -127
- package/copy-button/AGENTS.md +0 -43
- package/copy-button/index.tsx +0 -119
- package/press.css +0 -37
- package/styles.css +0 -347
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(
|
|
58
|
+
fail(`목록에 없는 컴포넌트예요: ${name}\nnpx @pantoia/ui list로 이름을 확인하세요.`);
|
|
31
59
|
return item;
|
|
32
60
|
}
|
|
33
61
|
function configPath() {
|
|
@@ -59,67 +87,194 @@ function resolve(names) {
|
|
|
59
87
|
}
|
|
60
88
|
return [...seen.values()];
|
|
61
89
|
}
|
|
62
|
-
async function
|
|
90
|
+
async function ask(question, fallback) {
|
|
63
91
|
if (!stdin.isTTY)
|
|
64
|
-
return
|
|
92
|
+
return fallback;
|
|
93
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
94
|
+
const answer = await rl.question(`${question} (${fallback}) `);
|
|
95
|
+
rl.close();
|
|
96
|
+
return answer.trim() || fallback;
|
|
97
|
+
}
|
|
98
|
+
async function confirm(question, fallback = false) {
|
|
99
|
+
if (!stdin.isTTY)
|
|
100
|
+
return fallback;
|
|
65
101
|
const rl = createInterface({ input: stdin, output: stdout });
|
|
66
|
-
const answer = await rl.question(`${question} (y/N) `);
|
|
102
|
+
const answer = (await rl.question(`${question} ${fallback ? '(Y/n)' : '(y/N)'} `)).trim().toLowerCase();
|
|
67
103
|
rl.close();
|
|
68
|
-
return answer
|
|
104
|
+
return answer === '' ? fallback : answer === 'y';
|
|
69
105
|
}
|
|
70
|
-
async function copy(item,
|
|
106
|
+
async function copy(item, base, force) {
|
|
107
|
+
let skipped = false;
|
|
71
108
|
for (const file of item.files ?? []) {
|
|
72
109
|
const source = readFileSync(join(HERE, file.path), 'utf8');
|
|
73
|
-
const
|
|
110
|
+
const shown = join(base, file.target ?? file.path);
|
|
111
|
+
const target = join(cwd(), shown);
|
|
74
112
|
if (existsSync(target) && readFileSync(target, 'utf8') !== source) {
|
|
75
|
-
if (!force && !await confirm(`${
|
|
76
|
-
console.log(`
|
|
113
|
+
if (!force && !await confirm(`${shown} 파일이 이미 있고 내용이 달라요. 덮어쓸까요?`)) {
|
|
114
|
+
console.log(` ${shown} (건너뜀)`);
|
|
115
|
+
skipped = true;
|
|
77
116
|
continue;
|
|
78
117
|
}
|
|
79
118
|
}
|
|
80
119
|
mkdirSync(dirname(target), { recursive: true });
|
|
81
120
|
writeFileSync(target, source);
|
|
82
|
-
console.log(`
|
|
121
|
+
console.log(` ${shown}`);
|
|
122
|
+
}
|
|
123
|
+
return skipped;
|
|
124
|
+
}
|
|
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;
|
|
176
|
+
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}`);
|
|
83
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;
|
|
84
199
|
}
|
|
85
200
|
async function commandInit(args) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const path = option(args, '--path') ??
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
+
}
|
|
93
236
|
}
|
|
94
237
|
async function commandAdd(args) {
|
|
95
238
|
const names = args.filter(arg => !arg.startsWith('--'));
|
|
96
239
|
if (names.length === 0)
|
|
97
|
-
fail('
|
|
240
|
+
fail('추가할 컴포넌트 이름을 입력하세요. 예) npx @pantoia/ui add button');
|
|
98
241
|
const force = args.includes('--force');
|
|
99
|
-
|
|
100
|
-
if (!config)
|
|
101
|
-
|
|
102
|
-
console.log(`${CONFIG} 이 없어 만들었다 — 소스는 ${config.path} 아래로 간다`);
|
|
103
|
-
}
|
|
242
|
+
const config = readConfig();
|
|
243
|
+
if (!config)
|
|
244
|
+
fail(`${CONFIG}이 없어요. 먼저 npx @pantoia/ui init을 실행하세요.`);
|
|
104
245
|
const items = resolve(names);
|
|
105
|
-
const
|
|
246
|
+
const asked = items.filter(item => names.includes(item.name));
|
|
106
247
|
const extra = items.filter(item => !names.includes(item.name));
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
for (const item of
|
|
248
|
+
let skipped = false;
|
|
249
|
+
let first = true;
|
|
250
|
+
for (const item of asked) {
|
|
251
|
+
if (!first)
|
|
252
|
+
console.log('');
|
|
253
|
+
first = false;
|
|
110
254
|
console.log(item.name);
|
|
111
|
-
await copy(item,
|
|
255
|
+
skipped = await copy(item, config.path, force) || skipped;
|
|
112
256
|
config.components[item.name] = version();
|
|
113
257
|
}
|
|
258
|
+
if (extra.length > 0) {
|
|
259
|
+
console.log('');
|
|
260
|
+
console.log('함께 필요한 파일도 추가했어요.');
|
|
261
|
+
for (const item of extra) {
|
|
262
|
+
skipped = await copy(item, config.path, force) || skipped;
|
|
263
|
+
config.components[item.name] = version();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
114
266
|
writeConfig(config);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
console.log(
|
|
267
|
+
if (skipped) {
|
|
268
|
+
console.log('');
|
|
269
|
+
console.log('덮어쓰려면 --force 옵션을 붙이세요.');
|
|
270
|
+
}
|
|
118
271
|
}
|
|
119
272
|
function commandList() {
|
|
273
|
+
console.log(`${'이름'.padEnd(16)}${'종류'.padEnd(7)}같이 추가되는 컴포넌트`);
|
|
120
274
|
for (const item of registry()) {
|
|
121
|
-
const
|
|
122
|
-
|
|
275
|
+
const layer = item.meta?.layer ?? (item.type === 'registry:lib' ? 'lib' : '');
|
|
276
|
+
const together = (item.registryDependencies ?? []).join(', ');
|
|
277
|
+
console.log(`${item.name.padEnd(18)}${layer.padEnd(9)}${together}`.trimEnd());
|
|
123
278
|
}
|
|
124
279
|
}
|
|
125
280
|
function lcs(a, b) {
|
|
@@ -154,7 +309,7 @@ function difference(mine, theirs) {
|
|
|
154
309
|
async function commandDiff(args) {
|
|
155
310
|
const config = readConfig();
|
|
156
311
|
if (!config)
|
|
157
|
-
fail(`${CONFIG}
|
|
312
|
+
fail(`${CONFIG}이 없어요. 먼저 npx @pantoia/ui add로 컴포넌트를 추가하세요.`);
|
|
158
313
|
const names = args.filter(arg => !arg.startsWith('--'));
|
|
159
314
|
const wanted = names.length > 0 ? names : Object.keys(config.components);
|
|
160
315
|
let changed = 0;
|
|
@@ -167,14 +322,16 @@ async function commandDiff(args) {
|
|
|
167
322
|
const theirs = readFileSync(join(HERE, file.path), 'utf8');
|
|
168
323
|
if (mine === theirs)
|
|
169
324
|
continue;
|
|
325
|
+
if (changed === 0)
|
|
326
|
+
console.log('- 는 내 파일, + 는 최신 파일이에요.');
|
|
170
327
|
changed++;
|
|
171
|
-
console.log(`\n${file.path}`);
|
|
328
|
+
console.log(`\n${join(config.path, file.target ?? file.path)}`);
|
|
172
329
|
for (const line of difference(mine.split('\n'), theirs.split('\n')))
|
|
173
330
|
console.log(line);
|
|
174
331
|
}
|
|
175
332
|
}
|
|
176
333
|
if (changed === 0)
|
|
177
|
-
console.log('
|
|
334
|
+
console.log('달라진 파일이 없어요.');
|
|
178
335
|
}
|
|
179
336
|
const [command, ...args] = argv.slice(2);
|
|
180
337
|
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
|
|
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
|
-
## 변형
|
|
54
|
+
## 변형 - `drawerContent`
|
|
55
55
|
|
|
56
56
|
| 축 | 값 | 기본 |
|
|
57
57
|
|---|---|---|
|
|
58
|
-
| `side` | `left
|
|
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: '
|
|
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-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}
|
|
@@ -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={
|
|
193
|
+
return <Dialog.Title {...rest} className={drawerTitle({ className })} />
|
|
192
194
|
}
|
|
193
195
|
|
|
196
|
+
const drawerDescription = tv({ base: 'text-prose text-fg-muted' })
|
|
197
|
+
|
|
194
198
|
export function DrawerDescription({ className, ...rest }: ComponentProps<typeof Dialog.Description>) {
|
|
195
199
|
return (
|
|
196
|
-
<Dialog.Description {...rest} className={
|
|
200
|
+
<Dialog.Description {...rest} className={drawerDescription({ className })} />
|
|
197
201
|
)
|
|
198
202
|
}
|
package/dropdown-menu/AGENTS.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
```
|
|
4
4
|
받기 npx @pantoia/ui add dropdown-menu
|
|
5
5
|
층 ui
|
|
6
|
-
파일 dropdown-menu/index.tsx
|
|
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
|
-
## 변형
|
|
121
|
+
## 변형 - `menuItem`
|
|
122
122
|
|
|
123
123
|
| 축 | 값 | 기본 |
|
|
124
124
|
|---|---|---|
|
|
125
|
-
| `color` | `neutral
|
|
125
|
+
| `color` | `neutral`, `danger` | `neutral` |
|
|
126
126
|
|
|
127
127
|
## 함께 받는 것
|
|
128
128
|
|
|
@@ -130,4 +130,4 @@
|
|
|
130
130
|
|
|
131
131
|
## npm 의존
|
|
132
132
|
|
|
133
|
-
`
|
|
133
|
+
`radix-ui`
|
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,15 +13,15 @@ export const menuItem = tv({
|
|
|
14
13
|
|
|
15
14
|
variants: {
|
|
16
15
|
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',
|
|
16
|
+
neutral: 'text-fg data-highlighted:bg-bg-neutral-hover data-disabled:text-fg-disabled',
|
|
17
|
+
danger: 'text-fg-danger data-highlighted:bg-bg-danger-weak data-disabled:text-fg-danger/40',
|
|
19
18
|
},
|
|
20
19
|
},
|
|
21
20
|
|
|
22
21
|
defaultVariants: { color: 'neutral' },
|
|
23
22
|
})
|
|
24
23
|
|
|
25
|
-
const PANEL = 'menu
|
|
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'
|
|
@@ -51,6 +50,8 @@ export interface DropdownMenuContentProps extends Omit<ComponentProps<typeof Men
|
|
|
51
50
|
arrow?: boolean
|
|
52
51
|
}
|
|
53
52
|
|
|
53
|
+
const menuPanel = tv({ base: PANEL })
|
|
54
|
+
|
|
54
55
|
export function DropdownMenuContent({
|
|
55
56
|
align = 'start',
|
|
56
57
|
arrow = true,
|
|
@@ -65,23 +66,31 @@ export function DropdownMenuContent({
|
|
|
65
66
|
align={align}
|
|
66
67
|
sideOffset={8}
|
|
67
68
|
arrowPadding={16}
|
|
68
|
-
className={
|
|
69
|
+
className={menuPanel({ className })}
|
|
69
70
|
>
|
|
70
71
|
{children}
|
|
71
72
|
|
|
72
|
-
{arrow &&
|
|
73
|
+
{arrow && (
|
|
74
|
+
<MenuPrimitive.Arrow asChild width={12} height={6}>
|
|
75
|
+
<svg viewBox="0 0 12 6" preserveAspectRatio="none" className="fill-bg-panel -translate-y-px">
|
|
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" />
|
|
77
|
+
</svg>
|
|
78
|
+
</MenuPrimitive.Arrow>
|
|
79
|
+
)}
|
|
73
80
|
</MenuPrimitive.Content>
|
|
74
81
|
</MenuPrimitive.Portal>
|
|
75
82
|
)
|
|
76
83
|
}
|
|
77
84
|
|
|
85
|
+
const menuGroup = tv({ base: 'flex flex-col gap-px' })
|
|
86
|
+
|
|
78
87
|
export function DropdownMenuGroup({ label, className, children }: {
|
|
79
88
|
label?: string
|
|
80
89
|
className?: string
|
|
81
90
|
children?: ReactNode
|
|
82
91
|
}) {
|
|
83
92
|
return (
|
|
84
|
-
<MenuPrimitive.Group className={
|
|
93
|
+
<MenuPrimitive.Group className={menuGroup({ className })}>
|
|
85
94
|
{label && (
|
|
86
95
|
<MenuPrimitive.Label className="text-caption text-fg-subtle px-2 pb-1 pt-2">
|
|
87
96
|
{label}
|
|
@@ -130,7 +139,9 @@ export function DropdownMenuCheckboxItem({ checked, onCheckedChange, disabled, c
|
|
|
130
139
|
>
|
|
131
140
|
<span className="menu-lead grid size-4 shrink-0 place-items-center">
|
|
132
141
|
<MenuPrimitive.ItemIndicator>
|
|
133
|
-
<
|
|
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>
|
|
134
145
|
</MenuPrimitive.ItemIndicator>
|
|
135
146
|
</span>
|
|
136
147
|
|
|
@@ -161,7 +172,7 @@ export function DropdownMenuRadioItem({ value, disabled, className, children }:
|
|
|
161
172
|
<MenuPrimitive.RadioItem value={value} disabled={disabled} className={menuItem({ className })}>
|
|
162
173
|
<span className="menu-lead grid size-4 shrink-0 place-items-center">
|
|
163
174
|
<MenuPrimitive.ItemIndicator>
|
|
164
|
-
<span className="bg-
|
|
175
|
+
<span className="bg-bg-neutral-solid in-disabled:bg-bg-neutral-solid-checked-disabled size-2 rounded-full" />
|
|
165
176
|
</MenuPrimitive.ItemIndicator>
|
|
166
177
|
</span>
|
|
167
178
|
|
|
@@ -170,8 +181,10 @@ export function DropdownMenuRadioItem({ value, disabled, className, children }:
|
|
|
170
181
|
)
|
|
171
182
|
}
|
|
172
183
|
|
|
184
|
+
const menuSeparator = tv({ base: 'bg-line -mx-1 my-1 h-px' })
|
|
185
|
+
|
|
173
186
|
export function DropdownMenuSeparator({ className }: { className?: string }) {
|
|
174
|
-
return <MenuPrimitive.Separator className={
|
|
187
|
+
return <MenuPrimitive.Separator className={menuSeparator({ className })} />
|
|
175
188
|
}
|
|
176
189
|
|
|
177
190
|
export function DropdownMenuSub({ open, defaultOpen, onOpenChange, children }: {
|
|
@@ -203,7 +216,9 @@ export function DropdownMenuSubTrigger({ disabled, lead, className, children }:
|
|
|
203
216
|
|
|
204
217
|
<span className="min-w-0 grow">{children}</span>
|
|
205
218
|
|
|
206
|
-
<
|
|
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>
|
|
207
222
|
</MenuPrimitive.SubTrigger>
|
|
208
223
|
)
|
|
209
224
|
}
|
|
@@ -214,7 +229,7 @@ export function DropdownMenuSubContent({ className, children, ...rest }: Compone
|
|
|
214
229
|
<MenuPrimitive.SubContent
|
|
215
230
|
{...rest}
|
|
216
231
|
sideOffset={4}
|
|
217
|
-
className={
|
|
232
|
+
className={menuPanel({ className })}
|
|
218
233
|
>
|
|
219
234
|
{children}
|
|
220
235
|
</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
|
|
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,16 +81,12 @@
|
|
|
81
81
|
| `className` | `string` | |
|
|
82
82
|
| `children` | `ReactNode` | |
|
|
83
83
|
|
|
84
|
-
## 변형
|
|
84
|
+
## 변형 - `field`
|
|
85
85
|
|
|
86
86
|
| 축 | 값 | 기본 |
|
|
87
87
|
|---|---|---|
|
|
88
|
-
| `layout` | `stack
|
|
88
|
+
| `layout` | `stack`, `inline`, `split` | `stack` |
|
|
89
89
|
|
|
90
90
|
## 함께 받는 것
|
|
91
91
|
|
|
92
|
-
`label
|
|
93
|
-
|
|
94
|
-
## npm 의존
|
|
95
|
-
|
|
96
|
-
`lucide-react`
|
|
92
|
+
`label`, `tv`
|