@pantoia/ui 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/accordion/AGENTS.md +5 -5
  2. package/accordion/index.tsx +17 -9
  3. package/alert-dialog/AGENTS.md +7 -7
  4. package/alert-dialog/index.tsx +20 -10
  5. package/badge/AGENTS.md +5 -5
  6. package/badge/index.tsx +7 -7
  7. package/button/AGENTS.md +7 -7
  8. package/button/index.tsx +36 -36
  9. package/callout/AGENTS.md +4 -5
  10. package/callout/index.tsx +13 -14
  11. package/checkbox/AGENTS.md +5 -5
  12. package/checkbox/index.tsx +15 -13
  13. package/combobox/AGENTS.md +13 -13
  14. package/combobox/index.tsx +82 -38
  15. package/command-palette/AGENTS.md +3 -2
  16. package/command-palette/index.tsx +4 -2
  17. package/dialog/AGENTS.md +6 -6
  18. package/dialog/index.tsx +11 -5
  19. package/dist/index.js +63 -34
  20. package/drawer/AGENTS.md +5 -5
  21. package/drawer/index.tsx +8 -4
  22. package/dropdown-menu/AGENTS.md +12 -12
  23. package/dropdown-menu/index.tsx +23 -11
  24. package/field/AGENTS.md +8 -8
  25. package/field/index.tsx +27 -9
  26. package/input/AGENTS.md +4 -4
  27. package/input/index.tsx +8 -8
  28. package/kbd/AGENTS.md +1 -1
  29. package/kbd/index.tsx +6 -2
  30. package/label/AGENTS.md +1 -1
  31. package/label/index.tsx +3 -1
  32. package/number-field/AGENTS.md +6 -6
  33. package/number-field/index.tsx +22 -16
  34. package/package.json +3 -6
  35. package/popover/AGENTS.md +4 -4
  36. package/popover/index.tsx +14 -4
  37. package/press.css +7 -7
  38. package/radio-group/AGENTS.md +8 -8
  39. package/radio-group/index.tsx +20 -16
  40. package/registry.json +3 -51
  41. package/select/AGENTS.md +10 -10
  42. package/select/index.tsx +41 -23
  43. package/styles.css +291 -139
  44. package/switch/AGENTS.md +4 -4
  45. package/switch/index.tsx +13 -13
  46. package/table/AGENTS.md +9 -9
  47. package/table/index.tsx +17 -7
  48. package/tabs/AGENTS.md +4 -4
  49. package/tabs/index.module.css +1 -1
  50. package/tabs/index.tsx +17 -9
  51. package/textarea/AGENTS.md +4 -4
  52. package/textarea/index.tsx +8 -8
  53. package/toast/AGENTS.md +5 -13
  54. package/toast/index.tsx +31 -37
  55. package/toggle/AGENTS.md +4 -4
  56. package/toggle/index.tsx +10 -9
  57. package/tooltip/AGENTS.md +2 -2
  58. package/tooltip/index.tsx +14 -4
  59. package/code-block/AGENTS.md +0 -60
  60. package/code-block/index.module.css +0 -46
  61. package/code-block/index.tsx +0 -127
  62. package/copy-button/AGENTS.md +0 -43
  63. package/copy-button/index.tsx +0 -119
@@ -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
- }
@@ -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
- }
@@ -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`
@@ -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
- }