@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.
- package/accordion/AGENTS.md +5 -5
- package/accordion/index.tsx +17 -9
- package/alert-dialog/AGENTS.md +7 -7
- package/alert-dialog/index.tsx +20 -10
- package/badge/AGENTS.md +5 -5
- package/badge/index.tsx +7 -7
- package/button/AGENTS.md +7 -7
- package/button/index.tsx +36 -36
- package/callout/AGENTS.md +4 -5
- package/callout/index.tsx +13 -14
- package/checkbox/AGENTS.md +5 -5
- package/checkbox/index.tsx +15 -13
- package/combobox/AGENTS.md +13 -13
- package/combobox/index.tsx +82 -38
- package/command-palette/AGENTS.md +3 -2
- package/command-palette/index.tsx +4 -2
- package/dialog/AGENTS.md +6 -6
- package/dialog/index.tsx +11 -5
- package/dist/index.js +63 -34
- package/drawer/AGENTS.md +5 -5
- package/drawer/index.tsx +8 -4
- package/dropdown-menu/AGENTS.md +12 -12
- package/dropdown-menu/index.tsx +23 -11
- package/field/AGENTS.md +8 -8
- package/field/index.tsx +27 -9
- package/input/AGENTS.md +4 -4
- package/input/index.tsx +8 -8
- 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 -6
- package/number-field/index.tsx +22 -16
- package/package.json +3 -6
- package/popover/AGENTS.md +4 -4
- package/popover/index.tsx +14 -4
- package/press.css +7 -7
- package/radio-group/AGENTS.md +8 -8
- package/radio-group/index.tsx +20 -16
- package/registry.json +3 -51
- package/select/AGENTS.md +10 -10
- package/select/index.tsx +41 -23
- package/styles.css +291 -139
- package/switch/AGENTS.md +4 -4
- package/switch/index.tsx +13 -13
- package/table/AGENTS.md +9 -9
- package/table/index.tsx +17 -7
- package/tabs/AGENTS.md +4 -4
- package/tabs/index.module.css +1 -1
- package/tabs/index.tsx +17 -9
- package/textarea/AGENTS.md +4 -4
- package/textarea/index.tsx +8 -8
- package/toast/AGENTS.md +5 -13
- package/toast/index.tsx +31 -37
- package/toggle/AGENTS.md +4 -4
- package/toggle/index.tsx +10 -9
- package/tooltip/AGENTS.md +2 -2
- package/tooltip/index.tsx +14 -4
- 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
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
.code {
|
|
2
|
-
margin: 0;
|
|
3
|
-
padding: 1rem;
|
|
4
|
-
counter-reset: ln;
|
|
5
|
-
|
|
6
|
-
--code-block-keyword: var(--purple-700);
|
|
7
|
-
--code-block-name: var(--orange-800);
|
|
8
|
-
--code-block-string: var(--green-800);
|
|
9
|
-
--code-block-comment: var(--gray-500);
|
|
10
|
-
--code-block-value: var(--blue-700);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
.code-keyword { color: var(--code-block-keyword); }
|
|
14
|
-
.code-name { color: var(--code-block-name); }
|
|
15
|
-
.code-string { color: var(--code-block-string); }
|
|
16
|
-
.code-comment { color: var(--code-block-comment); }
|
|
17
|
-
.code-value { color: var(--code-block-value); }
|
|
18
|
-
.line { display: block; white-space: pre; min-height: 1lh; }
|
|
19
|
-
.wrap .line { white-space: pre-wrap; word-break: break-all; }
|
|
20
|
-
|
|
21
|
-
.numbers { padding-left: 0; }
|
|
22
|
-
.numbers .line::before {
|
|
23
|
-
counter-increment: ln;
|
|
24
|
-
content: counter(ln);
|
|
25
|
-
display: inline-block;
|
|
26
|
-
width: 3.4em;
|
|
27
|
-
padding-right: 0.9em;
|
|
28
|
-
text-align: right;
|
|
29
|
-
color: var(--color-fg-subtle);
|
|
30
|
-
user-select: none;
|
|
31
|
-
pointer-events: none;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
.numbers:not(.wrap) .line::before {
|
|
35
|
-
position: sticky;
|
|
36
|
-
left: 0;
|
|
37
|
-
background: var(--color-well);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
.numbers.wrap .line { padding-left: 3.4em; text-indent: -3.4em; }
|
|
41
|
-
|
|
42
|
-
.clip {
|
|
43
|
-
max-height: calc(var(--code-block-max-lines) * 1lh + 2rem);
|
|
44
|
-
overflow-y: hidden;
|
|
45
|
-
mask-image: linear-gradient(to bottom, #000 calc(100% - 2.5rem), transparent);
|
|
46
|
-
}
|
package/code-block/index.tsx
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { ChevronDown } from 'lucide-react'
|
|
4
|
-
import { useCallback, useState, useSyncExternalStore, type CSSProperties, type ReactNode } from 'react'
|
|
5
|
-
|
|
6
|
-
import { tv } from '../tv'
|
|
7
|
-
|
|
8
|
-
import s from './index.module.css'
|
|
9
|
-
|
|
10
|
-
export const codeToken = {
|
|
11
|
-
keyword: s['code-keyword'],
|
|
12
|
-
name: s['code-name'],
|
|
13
|
-
string: s['code-string'],
|
|
14
|
-
comment: s['code-comment'],
|
|
15
|
-
value: s['code-value'],
|
|
16
|
-
none: '',
|
|
17
|
-
} as const
|
|
18
|
-
|
|
19
|
-
export interface CodeBlockFold {
|
|
20
|
-
maxLines: number
|
|
21
|
-
more: string
|
|
22
|
-
moreCount: string
|
|
23
|
-
less: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const codeBlock = tv({
|
|
27
|
-
slots: {
|
|
28
|
-
root: 'bg-well rounded-well text-value font-mono overflow-hidden',
|
|
29
|
-
viewport: 'scroll-slim overflow-auto'
|
|
30
|
-
+ ' focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring',
|
|
31
|
-
pre: '',
|
|
32
|
-
chevron: 'size-4 transition-transform motion-reduce:transition-none',
|
|
33
|
-
},
|
|
34
|
-
|
|
35
|
-
variants: {
|
|
36
|
-
folded: { false: { chevron: 'rotate-180' } },
|
|
37
|
-
},
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
export interface CodeBlockProps {
|
|
41
|
-
code: string
|
|
42
|
-
wrap?: boolean
|
|
43
|
-
numbers?: boolean
|
|
44
|
-
fold?: CodeBlockFold
|
|
45
|
-
className?: string
|
|
46
|
-
header?: ReactNode
|
|
47
|
-
line?: (line: string, index: number) => ReactNode
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function CodeBlock({
|
|
51
|
-
code,
|
|
52
|
-
wrap = false,
|
|
53
|
-
numbers = false,
|
|
54
|
-
fold,
|
|
55
|
-
className,
|
|
56
|
-
header,
|
|
57
|
-
line,
|
|
58
|
-
}: CodeBlockProps) {
|
|
59
|
-
const [folded, setFolded] = useState(true)
|
|
60
|
-
const [box, setBox] = useState<HTMLDivElement | null>(null)
|
|
61
|
-
|
|
62
|
-
const subscribe = useCallback((onChange: () => void) => {
|
|
63
|
-
if (!box) return () => {}
|
|
64
|
-
const observer = new ResizeObserver(onChange)
|
|
65
|
-
observer.observe(box)
|
|
66
|
-
if (box.firstElementChild) observer.observe(box.firstElementChild)
|
|
67
|
-
return () => observer.disconnect()
|
|
68
|
-
}, [box])
|
|
69
|
-
|
|
70
|
-
const overflows = useSyncExternalStore(
|
|
71
|
-
subscribe,
|
|
72
|
-
() => box !== null && (box.scrollWidth > box.clientWidth || box.scrollHeight > box.clientHeight),
|
|
73
|
-
() => false,
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
const lines = code.split('\n')
|
|
77
|
-
const foldable = !!fold && lines.length > fold.maxLines
|
|
78
|
-
const rest = lines.length - (fold?.maxLines ?? 0)
|
|
79
|
-
|
|
80
|
-
const ui = codeBlock({ folded })
|
|
81
|
-
|
|
82
|
-
const pre = [s.code, wrap ? s.wrap : '', numbers ? s.numbers : '', ui.pre()]
|
|
83
|
-
.filter(Boolean).join(' ')
|
|
84
|
-
|
|
85
|
-
const viewport = [ui.viewport(), foldable && folded ? s.clip : '']
|
|
86
|
-
.filter(Boolean).join(' ')
|
|
87
|
-
|
|
88
|
-
return (
|
|
89
|
-
<div className={ui.root({ className })}>
|
|
90
|
-
{header}
|
|
91
|
-
|
|
92
|
-
<div
|
|
93
|
-
ref={setBox}
|
|
94
|
-
tabIndex={overflows ? 0 : undefined}
|
|
95
|
-
className={viewport}
|
|
96
|
-
style={{ '--code-block-max-lines': fold?.maxLines } as CSSProperties}
|
|
97
|
-
><pre className={pre}>{lines.map((text, i) => (
|
|
98
|
-
<span key={i} className={s.line}>{line ? line(text, i) : text}</span>
|
|
99
|
-
))}</pre></div>
|
|
100
|
-
|
|
101
|
-
{foldable && (
|
|
102
|
-
<button
|
|
103
|
-
type="button"
|
|
104
|
-
className="border-line text-caption text-fg-muted flex w-full items-center justify-center
|
|
105
|
-
gap-tight border-t py-2 transition-state press
|
|
106
|
-
enabled:hover:bg-hover
|
|
107
|
-
focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring"
|
|
108
|
-
onClick={() => setFolded(!folded)}
|
|
109
|
-
>
|
|
110
|
-
<ChevronDown className={ui.chevron()} />
|
|
111
|
-
{folded ? (wrap ? fold.more : fold.moreCount.replace('{count}', String(rest))) : fold.less}
|
|
112
|
-
</button>
|
|
113
|
-
)}
|
|
114
|
-
</div>
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
export function CodeBlockHeader({ className, children }: { className?: string, children?: ReactNode }) {
|
|
119
|
-
return (
|
|
120
|
-
<div
|
|
121
|
-
className={tv({ base: 'border-line text-caption text-fg-muted'
|
|
122
|
-
+ ' flex items-center gap-stack border-b px-4 py-2' })({ className })}
|
|
123
|
-
>
|
|
124
|
-
{children}
|
|
125
|
-
</div>
|
|
126
|
-
)
|
|
127
|
-
}
|
package/copy-button/AGENTS.md
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# CopyButton
|
|
2
|
-
|
|
3
|
-
```
|
|
4
|
-
받기 npx @pantoia/ui add copy-button
|
|
5
|
-
층 derived
|
|
6
|
-
파일 copy-button/index.tsx · copy-button/AGENTS.md
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
## 부위
|
|
10
|
-
|
|
11
|
-
| 이름 | props |
|
|
12
|
-
|---|---|
|
|
13
|
-
| `CopyButton` | `CopyButtonProps` |
|
|
14
|
-
|
|
15
|
-
## 도우미
|
|
16
|
-
|
|
17
|
-
`useCopy(resetAfter = 2000)`
|
|
18
|
-
|
|
19
|
-
## props
|
|
20
|
-
|
|
21
|
-
### `CopyState`
|
|
22
|
-
|
|
23
|
-
`'idle' | 'copied' | 'failed'`
|
|
24
|
-
|
|
25
|
-
### `CopyButtonProps`
|
|
26
|
-
|
|
27
|
-
| 이름 | 타입 | 필수 |
|
|
28
|
-
|---|---|---|
|
|
29
|
-
| `value` | `string` | 예 |
|
|
30
|
-
| `color` | `'primary' \| 'neutral' \| 'danger'` | |
|
|
31
|
-
| `emphasis` | `'solid' \| 'weak' \| 'outline' \| 'ghost'` | |
|
|
32
|
-
| `size` | `'sm' \| 'md' \| 'lg'` | |
|
|
33
|
-
| `shape` | `'rect' \| 'square' \| 'pill' \| 'circle'` | |
|
|
34
|
-
| `onCopied` | `(value: string) => void` | |
|
|
35
|
-
| `onFailed` | `() => void` | |
|
|
36
|
-
| `disabled` | `boolean` | |
|
|
37
|
-
| `className` | `string` | |
|
|
38
|
-
| `aria-label` | `string` | |
|
|
39
|
-
| `children` | `ReactNode` | |
|
|
40
|
-
|
|
41
|
-
## 함께 받는 것
|
|
42
|
-
|
|
43
|
-
`button`
|
package/copy-button/index.tsx
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { useEffect, useRef, useState, type ReactNode } from 'react'
|
|
4
|
-
|
|
5
|
-
import { Button } from '../button'
|
|
6
|
-
|
|
7
|
-
export type CopyState = 'idle' | 'copied' | 'failed'
|
|
8
|
-
|
|
9
|
-
function writeLegacy(value: string): boolean {
|
|
10
|
-
const ta = document.createElement('textarea')
|
|
11
|
-
ta.value = value
|
|
12
|
-
ta.setAttribute('readonly', '')
|
|
13
|
-
ta.style.cssText = 'position:fixed;top:0;left:0;opacity:0;pointer-events:none'
|
|
14
|
-
document.body.appendChild(ta)
|
|
15
|
-
ta.select()
|
|
16
|
-
|
|
17
|
-
let ok: boolean
|
|
18
|
-
try { ok = document.execCommand('copy') }
|
|
19
|
-
catch { ok = false }
|
|
20
|
-
|
|
21
|
-
ta.remove()
|
|
22
|
-
return ok
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function write(value: string): Promise<boolean> {
|
|
26
|
-
try {
|
|
27
|
-
await navigator.clipboard.writeText(value)
|
|
28
|
-
return true
|
|
29
|
-
}
|
|
30
|
-
catch {
|
|
31
|
-
return writeLegacy(value)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function useCopy(resetAfter = 2000) {
|
|
36
|
-
const [state, setState] = useState<CopyState>('idle')
|
|
37
|
-
const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
|
|
38
|
-
|
|
39
|
-
useEffect(() => () => clearTimeout(timer.current), [])
|
|
40
|
-
|
|
41
|
-
const copy = async (value: string) => {
|
|
42
|
-
clearTimeout(timer.current)
|
|
43
|
-
const next: CopyState = await write(value) ? 'copied' : 'failed'
|
|
44
|
-
setState(next)
|
|
45
|
-
timer.current = setTimeout(() => setState('idle'), resetAfter)
|
|
46
|
-
return next
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return { state, copy }
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface CopyButtonProps {
|
|
53
|
-
value: string
|
|
54
|
-
color?: 'primary' | 'neutral' | 'danger'
|
|
55
|
-
emphasis?: 'solid' | 'weak' | 'outline' | 'ghost'
|
|
56
|
-
size?: 'sm' | 'md' | 'lg'
|
|
57
|
-
shape?: 'rect' | 'square' | 'pill' | 'circle'
|
|
58
|
-
onCopied?: (value: string) => void
|
|
59
|
-
onFailed?: () => void
|
|
60
|
-
disabled?: boolean
|
|
61
|
-
className?: string
|
|
62
|
-
'aria-label'?: string
|
|
63
|
-
children?: ReactNode
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function CopyButton({
|
|
67
|
-
value,
|
|
68
|
-
color = 'neutral',
|
|
69
|
-
emphasis = 'outline',
|
|
70
|
-
size = 'md',
|
|
71
|
-
shape = 'rect',
|
|
72
|
-
onCopied,
|
|
73
|
-
onFailed,
|
|
74
|
-
children,
|
|
75
|
-
...rest
|
|
76
|
-
}: CopyButtonProps) {
|
|
77
|
-
const { state, copy } = useCopy()
|
|
78
|
-
|
|
79
|
-
const run = async () => {
|
|
80
|
-
const result = await copy(value)
|
|
81
|
-
if (result === 'copied') onCopied?.(value)
|
|
82
|
-
else onFailed?.()
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<Button {...rest} color={color} emphasis={emphasis} size={size} shape={shape} onClick={run}>
|
|
87
|
-
<svg
|
|
88
|
-
viewBox="0 0 24 24"
|
|
89
|
-
fill="none"
|
|
90
|
-
stroke="currentColor"
|
|
91
|
-
strokeWidth="2"
|
|
92
|
-
strokeLinecap="round"
|
|
93
|
-
strokeLinejoin="round"
|
|
94
|
-
aria-hidden="true"
|
|
95
|
-
className="shrink-0"
|
|
96
|
-
>
|
|
97
|
-
{state === 'idle' && (
|
|
98
|
-
<g>
|
|
99
|
-
<rect x="8" y="8" width="14" height="14" rx="2" />
|
|
100
|
-
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
|
101
|
-
</g>
|
|
102
|
-
)}
|
|
103
|
-
|
|
104
|
-
{state === 'copied' && (
|
|
105
|
-
<path className="animate-draw motion-reduce:animate-none" d="M4 12l5 5L20 6" />
|
|
106
|
-
)}
|
|
107
|
-
|
|
108
|
-
{state === 'failed' && (
|
|
109
|
-
<g className="animate-enter motion-reduce:animate-none">
|
|
110
|
-
<path d="M18 6 6 18" />
|
|
111
|
-
<path d="m6 6 12 12" />
|
|
112
|
-
</g>
|
|
113
|
-
)}
|
|
114
|
-
</svg>
|
|
115
|
-
|
|
116
|
-
{children}
|
|
117
|
-
</Button>
|
|
118
|
-
)
|
|
119
|
-
}
|