@pantoia/ui 0.1.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 +60 -0
- package/accordion/index.module.css +10 -0
- package/accordion/index.tsx +91 -0
- package/alert-dialog/AGENTS.md +78 -0
- package/alert-dialog/index.tsx +119 -0
- package/badge/AGENTS.md +35 -0
- package/badge/index.tsx +34 -0
- package/button/AGENTS.md +39 -0
- package/button/index.tsx +98 -0
- package/callout/AGENTS.md +42 -0
- package/callout/index.tsx +70 -0
- package/checkbox/AGENTS.md +54 -0
- package/checkbox/index.tsx +151 -0
- package/code-block/AGENTS.md +60 -0
- package/code-block/index.module.css +46 -0
- package/code-block/index.tsx +127 -0
- package/combobox/AGENTS.md +150 -0
- package/combobox/index.tsx +420 -0
- package/command-palette/AGENTS.md +44 -0
- package/command-palette/index.tsx +108 -0
- package/copy-button/AGENTS.md +43 -0
- package/copy-button/index.tsx +119 -0
- package/dialog/AGENTS.md +81 -0
- package/dialog/index.tsx +108 -0
- package/dist/index.js +189 -0
- package/drawer/AGENTS.md +66 -0
- package/drawer/index.module.css +17 -0
- package/drawer/index.tsx +198 -0
- package/dropdown-menu/AGENTS.md +133 -0
- package/dropdown-menu/index.tsx +223 -0
- package/field/AGENTS.md +96 -0
- package/field/index.tsx +149 -0
- package/input/AGENTS.md +35 -0
- package/input/index.tsx +36 -0
- package/kbd/AGENTS.md +17 -0
- package/kbd/index.tsx +13 -0
- package/label/AGENTS.md +21 -0
- package/label/index.tsx +13 -0
- package/number-field/AGENTS.md +53 -0
- package/number-field/index.tsx +227 -0
- package/package.json +73 -0
- package/popover/AGENTS.md +64 -0
- package/popover/index.tsx +66 -0
- package/press.css +37 -0
- package/radio-group/AGENTS.md +81 -0
- package/radio-group/index.tsx +139 -0
- package/registry.json +726 -0
- package/select/AGENTS.md +90 -0
- package/select/index.tsx +169 -0
- package/styles.css +347 -0
- package/switch/AGENTS.md +37 -0
- package/switch/index.tsx +42 -0
- package/table/AGENTS.md +82 -0
- package/table/index.tsx +120 -0
- package/tabs/AGENTS.md +60 -0
- package/tabs/index.module.css +14 -0
- package/tabs/index.tsx +105 -0
- package/test-utils.ts +10 -0
- package/textarea/AGENTS.md +35 -0
- package/textarea/index.tsx +37 -0
- package/toast/AGENTS.md +42 -0
- package/toast/index.tsx +71 -0
- package/toggle/AGENTS.md +37 -0
- package/toggle/index.tsx +36 -0
- package/tooltip/AGENTS.md +50 -0
- package/tooltip/index.tsx +57 -0
- package/tv.ts +23 -0
package/table/AGENTS.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Table
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
받기 npx @pantoia/ui add table
|
|
5
|
+
층 ui
|
|
6
|
+
파일 table/index.tsx · table/AGENTS.md
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 부위
|
|
10
|
+
|
|
11
|
+
| 이름 | props |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `Table` | `TableProps` |
|
|
14
|
+
| `TableHeader` | — |
|
|
15
|
+
| `TableHeadCell` | — |
|
|
16
|
+
| `TableBody` | — |
|
|
17
|
+
| `TableRow` | — |
|
|
18
|
+
| `TableCell` | `TableCellProps` |
|
|
19
|
+
|
|
20
|
+
### `TableHeader`
|
|
21
|
+
|
|
22
|
+
| 이름 | 타입 | 필수 |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `className` | `string` | |
|
|
25
|
+
| `children` | `ReactNode` | |
|
|
26
|
+
|
|
27
|
+
### `TableHeadCell`
|
|
28
|
+
|
|
29
|
+
| 이름 | 타입 | 필수 |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `className` | `string` | |
|
|
32
|
+
| `children` | `ReactNode` | |
|
|
33
|
+
|
|
34
|
+
### `TableBody`
|
|
35
|
+
|
|
36
|
+
| 이름 | 타입 | 필수 |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `className` | `string` | |
|
|
39
|
+
| `children` | `ReactNode` | |
|
|
40
|
+
|
|
41
|
+
### `TableRow`
|
|
42
|
+
|
|
43
|
+
| 이름 | 타입 | 필수 |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| `className` | `string` | |
|
|
46
|
+
| `children` | `ReactNode` | |
|
|
47
|
+
|
|
48
|
+
## props
|
|
49
|
+
|
|
50
|
+
### `TableProps`
|
|
51
|
+
|
|
52
|
+
| 이름 | 타입 | 필수 |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `label` | `string` | 예 |
|
|
55
|
+
| `fixed` | `boolean` | |
|
|
56
|
+
| `className` | `string` | |
|
|
57
|
+
| `children` | `ReactNode` | |
|
|
58
|
+
|
|
59
|
+
### `TableCellProps`
|
|
60
|
+
|
|
61
|
+
`TdHTMLAttributes<HTMLTableCellElement>` 를 확장한다.
|
|
62
|
+
|
|
63
|
+
| 이름 | 타입 | 필수 |
|
|
64
|
+
|---|---|---|
|
|
65
|
+
| `mono` | `boolean` | |
|
|
66
|
+
| `clamp` | `boolean` | |
|
|
67
|
+
|
|
68
|
+
## 변형 — `table`
|
|
69
|
+
|
|
70
|
+
| 축 | 값 | 기본 |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `fixed` | `true` | — |
|
|
73
|
+
|
|
74
|
+
## 변형 — `tableCell`
|
|
75
|
+
|
|
76
|
+
| 축 | 값 | 기본 |
|
|
77
|
+
|---|---|---|
|
|
78
|
+
| `mono` | `true` · `false` | `false` |
|
|
79
|
+
|
|
80
|
+
## 함께 받는 것
|
|
81
|
+
|
|
82
|
+
`tv`
|
package/table/index.tsx
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { createContext, use, useCallback, useState, useSyncExternalStore, type ReactNode, type TdHTMLAttributes } from 'react'
|
|
4
|
+
|
|
5
|
+
import { tv } from '../tv'
|
|
6
|
+
|
|
7
|
+
const FixedContext = createContext(false)
|
|
8
|
+
|
|
9
|
+
export const table = tv({
|
|
10
|
+
base: 'w-full border-collapse',
|
|
11
|
+
variants: {
|
|
12
|
+
fixed: { true: 'table-fixed' },
|
|
13
|
+
},
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export interface TableProps {
|
|
17
|
+
label: string
|
|
18
|
+
fixed?: boolean
|
|
19
|
+
className?: string
|
|
20
|
+
children?: ReactNode
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function Table({ label, fixed = false, className, children }: TableProps) {
|
|
24
|
+
const [box, setBox] = useState<HTMLDivElement | null>(null)
|
|
25
|
+
|
|
26
|
+
const subscribe = useCallback((onChange: () => void) => {
|
|
27
|
+
if (!box) return () => {}
|
|
28
|
+
const observer = new ResizeObserver(onChange)
|
|
29
|
+
observer.observe(box)
|
|
30
|
+
if (box.firstElementChild) observer.observe(box.firstElementChild)
|
|
31
|
+
return () => observer.disconnect()
|
|
32
|
+
}, [box])
|
|
33
|
+
|
|
34
|
+
const overflows = useSyncExternalStore(
|
|
35
|
+
subscribe,
|
|
36
|
+
() => box !== null && box.scrollWidth > box.clientWidth,
|
|
37
|
+
() => false,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<FixedContext value={fixed}>
|
|
42
|
+
<div className={tv({ base: 'rounded-panel border border-line overflow-hidden' })({ className })}>
|
|
43
|
+
<div
|
|
44
|
+
ref={setBox}
|
|
45
|
+
tabIndex={overflows ? 0 : undefined}
|
|
46
|
+
aria-label={overflows ? label : undefined}
|
|
47
|
+
role={overflows ? 'region' : undefined}
|
|
48
|
+
className="overflow-x-auto focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-ring"
|
|
49
|
+
>
|
|
50
|
+
<table className={table({ fixed })}>
|
|
51
|
+
<caption className="sr-only">{label}</caption>
|
|
52
|
+
{children}
|
|
53
|
+
</table>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</FixedContext>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function TableHeader({ className, children }: { className?: string, children?: ReactNode }) {
|
|
61
|
+
return (
|
|
62
|
+
<thead>
|
|
63
|
+
<tr className={tv({ base: 'bg-well border-b border-line' })({ className })}>
|
|
64
|
+
{children}
|
|
65
|
+
</tr>
|
|
66
|
+
</thead>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function TableHeadCell({ className, children }: { className?: string, children?: ReactNode }) {
|
|
71
|
+
return (
|
|
72
|
+
<th
|
|
73
|
+
scope="col"
|
|
74
|
+
className={tv({ base: 'text-caption text-fg-muted h-9 px-4 text-left whitespace-nowrap' })({ className })}
|
|
75
|
+
>
|
|
76
|
+
{children}
|
|
77
|
+
</th>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function TableBody({ className, children }: { className?: string, children?: ReactNode }) {
|
|
82
|
+
return <tbody className={className}>{children}</tbody>
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function TableRow({ className, children }: { className?: string, children?: ReactNode }) {
|
|
86
|
+
return (
|
|
87
|
+
<tr
|
|
88
|
+
className={tv({ base: 'border-b border-line last:border-b-0 transition-state'
|
|
89
|
+
+ ' hover:bg-hover' })({ className })}
|
|
90
|
+
>
|
|
91
|
+
{children}
|
|
92
|
+
</tr>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const tableCell = tv({
|
|
97
|
+
base: 'text-label text-fg h-11 px-4',
|
|
98
|
+
|
|
99
|
+
variants: {
|
|
100
|
+
mono: { true: 'font-mono', false: '' },
|
|
101
|
+
},
|
|
102
|
+
|
|
103
|
+
defaultVariants: { mono: false },
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
export interface TableCellProps extends TdHTMLAttributes<HTMLTableCellElement> {
|
|
107
|
+
mono?: boolean
|
|
108
|
+
clamp?: boolean
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function TableCell({ mono = false, clamp, className, children, ...rest }: TableCellProps) {
|
|
112
|
+
const fixed = use(FixedContext)
|
|
113
|
+
const clamped = clamp ?? fixed
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<td {...rest} className={tableCell({ mono, className })}>
|
|
117
|
+
{clamped ? <span className="line-clamp-2">{children}</span> : children}
|
|
118
|
+
</td>
|
|
119
|
+
)
|
|
120
|
+
}
|
package/tabs/AGENTS.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Tabs
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
받기 npx @pantoia/ui add tabs
|
|
5
|
+
층 ui
|
|
6
|
+
파일 tabs/index.tsx · tabs/index.module.css · tabs/AGENTS.md
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 부위
|
|
10
|
+
|
|
11
|
+
| 이름 | props |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `Tabs` | `TabsProps` |
|
|
14
|
+
| `TabsList` | — |
|
|
15
|
+
| `TabsTrigger` | — |
|
|
16
|
+
| `TabsContent` | — |
|
|
17
|
+
|
|
18
|
+
### `TabsList`
|
|
19
|
+
|
|
20
|
+
| 이름 | 타입 | 필수 |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `className` | `string` | |
|
|
23
|
+
| `children` | `ReactNode` | |
|
|
24
|
+
|
|
25
|
+
### `TabsTrigger`
|
|
26
|
+
|
|
27
|
+
| 이름 | 타입 | 필수 |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `value` | `string` | 예 |
|
|
30
|
+
| `disabled` | `boolean` | |
|
|
31
|
+
| `className` | `string` | |
|
|
32
|
+
| `children` | `ReactNode` | |
|
|
33
|
+
|
|
34
|
+
### `TabsContent`
|
|
35
|
+
|
|
36
|
+
| 이름 | 타입 | 필수 |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `value` | `string` | 예 |
|
|
39
|
+
| `className` | `string` | |
|
|
40
|
+
| `children` | `ReactNode` | |
|
|
41
|
+
|
|
42
|
+
## props
|
|
43
|
+
|
|
44
|
+
### `TabsProps`
|
|
45
|
+
|
|
46
|
+
| 이름 | 타입 | 필수 |
|
|
47
|
+
|---|---|---|
|
|
48
|
+
| `value` | `string` | |
|
|
49
|
+
| `defaultValue` | `string` | |
|
|
50
|
+
| `onValueChange` | `(value: string) => void` | |
|
|
51
|
+
| `className` | `string` | |
|
|
52
|
+
| `children` | `ReactNode` | |
|
|
53
|
+
|
|
54
|
+
## 함께 받는 것
|
|
55
|
+
|
|
56
|
+
`tv`
|
|
57
|
+
|
|
58
|
+
## npm 의존
|
|
59
|
+
|
|
60
|
+
`radix-ui`
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.underline {
|
|
2
|
+
position: absolute;
|
|
3
|
+
bottom: 0;
|
|
4
|
+
height: 2px;
|
|
5
|
+
background: var(--color-fg);
|
|
6
|
+
width: var(--tabs-indicator-size);
|
|
7
|
+
translate: var(--tabs-indicator-position) 0;
|
|
8
|
+
transition: translate var(--dur-state) var(--ease-state),
|
|
9
|
+
width var(--dur-state) var(--ease-state);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
@media (prefers-reduced-motion: reduce) {
|
|
13
|
+
.underline { transition: none; }
|
|
14
|
+
}
|
package/tabs/index.tsx
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Tabs as TabsPrimitive } from 'radix-ui'
|
|
4
|
+
import { useEffect, useRef, type ReactNode } from 'react'
|
|
5
|
+
|
|
6
|
+
import { tv } from '../tv'
|
|
7
|
+
|
|
8
|
+
import s from './index.module.css'
|
|
9
|
+
|
|
10
|
+
const ACTIVE = '[data-state="active"]'
|
|
11
|
+
|
|
12
|
+
export interface TabsProps {
|
|
13
|
+
value?: string
|
|
14
|
+
defaultValue?: string
|
|
15
|
+
onValueChange?: (value: string) => void
|
|
16
|
+
className?: string
|
|
17
|
+
children?: ReactNode
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Tabs({ value, defaultValue, onValueChange, className, children }: TabsProps) {
|
|
21
|
+
return (
|
|
22
|
+
<TabsPrimitive.Root
|
|
23
|
+
value={value}
|
|
24
|
+
defaultValue={defaultValue}
|
|
25
|
+
onValueChange={onValueChange}
|
|
26
|
+
className={tv({ base: 'flex flex-col gap-stack' })({ className })}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</TabsPrimitive.Root>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function TabsList({ className, children }: { className?: string, children?: ReactNode }) {
|
|
34
|
+
const list = useRef<HTMLDivElement>(null)
|
|
35
|
+
const bar = useRef<HTMLSpanElement>(null)
|
|
36
|
+
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const root = list.current
|
|
39
|
+
const mark = bar.current
|
|
40
|
+
if (!root || !mark) return
|
|
41
|
+
|
|
42
|
+
const measure = () => {
|
|
43
|
+
const active = root.querySelector<HTMLElement>(ACTIVE)
|
|
44
|
+
mark.style.setProperty('--tabs-indicator-size', `${active?.offsetWidth ?? 0}px`)
|
|
45
|
+
mark.style.setProperty('--tabs-indicator-position', `${active?.offsetLeft ?? 0}px`)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
measure()
|
|
49
|
+
|
|
50
|
+
const attrs = new MutationObserver(measure)
|
|
51
|
+
attrs.observe(root, { attributes: true, subtree: true, attributeFilter: ['data-state'] })
|
|
52
|
+
|
|
53
|
+
const box = new ResizeObserver(measure)
|
|
54
|
+
box.observe(root)
|
|
55
|
+
|
|
56
|
+
return () => { attrs.disconnect(); box.disconnect() }
|
|
57
|
+
}, [])
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
className={tv({ base: 'border-line scroll-slim overflow-x-auto border-b' })({ className })}
|
|
62
|
+
>
|
|
63
|
+
<TabsPrimitive.List ref={list} className="relative flex w-max min-w-full">
|
|
64
|
+
{children}
|
|
65
|
+
|
|
66
|
+
<span ref={bar} className={s.underline} />
|
|
67
|
+
</TabsPrimitive.List>
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function TabsTrigger({ value, disabled, className, children }: {
|
|
73
|
+
value: string
|
|
74
|
+
disabled?: boolean
|
|
75
|
+
className?: string
|
|
76
|
+
children?: ReactNode
|
|
77
|
+
}) {
|
|
78
|
+
return (
|
|
79
|
+
<TabsPrimitive.Trigger
|
|
80
|
+
value={value}
|
|
81
|
+
disabled={disabled}
|
|
82
|
+
className={tv({ base: 'text-label text-fg-muted inline-flex h-9 items-center gap-tight px-3 transition-state'
|
|
83
|
+
+ ' [&_svg]:size-4'
|
|
84
|
+
+ ' press-shrink enabled:hover:text-fg'
|
|
85
|
+
+ ' disabled:text-fg-disabled disabled:cursor-not-allowed'
|
|
86
|
+
+ ' aria-selected:text-fg'
|
|
87
|
+
+ ' focus-visible:outline-2 focus-visible:-outline-offset-2'
|
|
88
|
+
+ ' focus-visible:outline-ring' })({ className })}
|
|
89
|
+
>
|
|
90
|
+
{children}
|
|
91
|
+
</TabsPrimitive.Trigger>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function TabsContent({ value, className, children }: {
|
|
96
|
+
value: string
|
|
97
|
+
className?: string
|
|
98
|
+
children?: ReactNode
|
|
99
|
+
}) {
|
|
100
|
+
return (
|
|
101
|
+
<TabsPrimitive.Content value={value} className={className}>
|
|
102
|
+
{children}
|
|
103
|
+
</TabsPrimitive.Content>
|
|
104
|
+
)
|
|
105
|
+
}
|
package/test-utils.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { act, cleanup, fireEvent, render, renderHook, screen, waitFor, within } from '@testing-library/react'
|
|
2
|
+
export { default as userEvent } from '@testing-library/user-event'
|
|
3
|
+
|
|
4
|
+
export function cls(el: Element | null): string[] {
|
|
5
|
+
return (el?.getAttribute('class') ?? '').split(/\s+/).filter(Boolean)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function has(el: Element | null, needle: string): boolean {
|
|
9
|
+
return cls(el).some(part => part.includes(needle))
|
|
10
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Textarea
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
받기 npx @pantoia/ui add textarea
|
|
5
|
+
층 ui
|
|
6
|
+
파일 textarea/index.tsx · textarea/AGENTS.md
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 부위
|
|
10
|
+
|
|
11
|
+
| 이름 | props |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `Textarea` | `TextareaProps` |
|
|
14
|
+
|
|
15
|
+
## props
|
|
16
|
+
|
|
17
|
+
### `TextareaProps`
|
|
18
|
+
|
|
19
|
+
`TextareaHTMLAttributes<HTMLTextAreaElement>` 를 확장한다.
|
|
20
|
+
|
|
21
|
+
| 이름 | 타입 | 필수 |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `surface` | `'bare' \| 'well'` | |
|
|
24
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | |
|
|
25
|
+
|
|
26
|
+
## 변형 — `textarea`
|
|
27
|
+
|
|
28
|
+
| 축 | 값 | 기본 |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `surface` | `bare` · `well` | `bare` |
|
|
31
|
+
| `size` | `sm` · `md` · `lg` | `md` |
|
|
32
|
+
|
|
33
|
+
## 함께 받는 것
|
|
34
|
+
|
|
35
|
+
`tv`
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { TextareaHTMLAttributes } from 'react'
|
|
2
|
+
|
|
3
|
+
import { tv } from '../tv'
|
|
4
|
+
|
|
5
|
+
export const textarea = tv({
|
|
6
|
+
base: 'w-full rounded-r8 text-value text-fg'
|
|
7
|
+
+ ' placeholder:text-fg-placeholder'
|
|
8
|
+
+ ' invalid:ring-2 invalid:ring-danger'
|
|
9
|
+
+ ' read-only:text-fg-muted'
|
|
10
|
+
+ ' resize-y'
|
|
11
|
+
+ ' transition-state'
|
|
12
|
+
+ ' focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
13
|
+
+ ' disabled:bg-disabled disabled:text-fg-disabled disabled:cursor-not-allowed',
|
|
14
|
+
|
|
15
|
+
variants: {
|
|
16
|
+
surface: {
|
|
17
|
+
bare: 'bg-transparent ring-1 ring-inset ring-line-strong read-only:bg-well',
|
|
18
|
+
well: 'bg-well',
|
|
19
|
+
},
|
|
20
|
+
size: {
|
|
21
|
+
sm: 'p-2',
|
|
22
|
+
md: 'p-3',
|
|
23
|
+
lg: 'p-3 text-t16',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
defaultVariants: { surface: 'bare', size: 'md' },
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
|
|
31
|
+
surface?: 'bare' | 'well'
|
|
32
|
+
size?: 'sm' | 'md' | 'lg'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function Textarea({ surface, size, rows = 3, className, ...rest }: TextareaProps) {
|
|
36
|
+
return <textarea {...rest} rows={rows} className={textarea({ surface, size, className })} />
|
|
37
|
+
}
|
package/toast/AGENTS.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Toast
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
받기 npx @pantoia/ui add toast
|
|
5
|
+
층 ui
|
|
6
|
+
파일 toast/index.tsx · toast/AGENTS.md
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 부위
|
|
10
|
+
|
|
11
|
+
| 이름 | props |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `Toast` | `ToastProps` |
|
|
14
|
+
|
|
15
|
+
## props
|
|
16
|
+
|
|
17
|
+
### `ToastTone`
|
|
18
|
+
|
|
19
|
+
`'neutral' | 'success' | 'warning' | 'danger' | 'info'`
|
|
20
|
+
|
|
21
|
+
### `ToastOptions`
|
|
22
|
+
|
|
23
|
+
| 이름 | 타입 | 필수 |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `description` | `string` | |
|
|
26
|
+
| `action` | `{ label: string, onClick: () => void }` | |
|
|
27
|
+
| `toasterId` | `string` | |
|
|
28
|
+
|
|
29
|
+
### `ToastProps`
|
|
30
|
+
|
|
31
|
+
`Omit<ComponentProps<typeof SonnerToaster>, 'id' | 'position' | 'duration' | 'expand'>` 를 확장한다.
|
|
32
|
+
|
|
33
|
+
| 이름 | 타입 | 필수 |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `toasterId` | `string` | |
|
|
36
|
+
| `position` | `'top-left' \| 'top-center' \| 'top-right' \| 'bottom-left' \| 'bottom-center' \| 'bottom-right'` | |
|
|
37
|
+
| `duration` | `number` | |
|
|
38
|
+
| `expand` | `boolean` | |
|
|
39
|
+
|
|
40
|
+
## npm 의존
|
|
41
|
+
|
|
42
|
+
`sonner`
|
package/toast/index.tsx
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { ComponentProps } from 'react'
|
|
4
|
+
import { toast as sonner, Toaster as SonnerToaster } from 'sonner'
|
|
5
|
+
|
|
6
|
+
export type ToastTone = 'neutral' | 'success' | 'warning' | 'danger' | 'info'
|
|
7
|
+
|
|
8
|
+
export type ToastOptions = {
|
|
9
|
+
description?: string
|
|
10
|
+
action?: { label: string, onClick: () => void }
|
|
11
|
+
toasterId?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const show = (tone: ToastTone, message: string, options?: ToastOptions) => {
|
|
15
|
+
if (tone === 'neutral') return sonner(message, options)
|
|
16
|
+
if (tone === 'danger') return sonner.error(message, options)
|
|
17
|
+
return sonner[tone](message, options)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const toast = {
|
|
21
|
+
show,
|
|
22
|
+
neutral: (m: string, o?: ToastOptions) => show('neutral', m, o),
|
|
23
|
+
success: (m: string, o?: ToastOptions) => show('success', m, o),
|
|
24
|
+
warning: (m: string, o?: ToastOptions) => show('warning', m, o),
|
|
25
|
+
danger: (m: string, o?: ToastOptions) => show('danger', m, o),
|
|
26
|
+
info: (m: string, o?: ToastOptions) => show('info', m, o),
|
|
27
|
+
dismiss: (id?: string | number) => sonner.dismiss(id),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ToastProps
|
|
31
|
+
extends Omit<ComponentProps<typeof SonnerToaster>, 'id' | 'position' | 'duration' | 'expand'> {
|
|
32
|
+
toasterId?: string
|
|
33
|
+
position?: 'top-left' | 'top-center' | 'top-right'
|
|
34
|
+
| 'bottom-left' | 'bottom-center' | 'bottom-right'
|
|
35
|
+
duration?: number
|
|
36
|
+
expand?: boolean
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function Toast({ toasterId, position = 'bottom-center', duration = 4000, expand = false, ...rest }: ToastProps) {
|
|
40
|
+
return (
|
|
41
|
+
<SonnerToaster
|
|
42
|
+
{...rest}
|
|
43
|
+
id={toasterId}
|
|
44
|
+
position={position}
|
|
45
|
+
duration={duration}
|
|
46
|
+
expand={expand}
|
|
47
|
+
offset={16}
|
|
48
|
+
gap={8}
|
|
49
|
+
className="z-toast"
|
|
50
|
+
toastOptions={{
|
|
51
|
+
unstyled: true,
|
|
52
|
+
classNames: {
|
|
53
|
+
toast: 'bg-float border border-line rounded-r8 shadow-s3'
|
|
54
|
+
+ ' flex w-full items-center gap-stack p-4',
|
|
55
|
+
content: 'min-w-0 flex-1',
|
|
56
|
+
title: 'text-label text-fg',
|
|
57
|
+
description: 'text-caption text-fg-muted',
|
|
58
|
+
icon: 'shrink-0',
|
|
59
|
+
actionButton: 'text-label text-fg shrink-0 rounded-r8 px-3 h-8'
|
|
60
|
+
+ ' bg-well transition-state press press-shrink enabled:hover:bg-fg-muted/10'
|
|
61
|
+
+ ' focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
62
|
+
+ ' disabled:text-fg-disabled disabled:cursor-not-allowed',
|
|
63
|
+
success: 'text-success',
|
|
64
|
+
error: 'text-danger',
|
|
65
|
+
warning: 'text-warning',
|
|
66
|
+
info: 'text-info',
|
|
67
|
+
},
|
|
68
|
+
}}
|
|
69
|
+
/>
|
|
70
|
+
)
|
|
71
|
+
}
|
package/toggle/AGENTS.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Toggle
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
받기 npx @pantoia/ui add toggle
|
|
5
|
+
층 ui
|
|
6
|
+
파일 toggle/index.tsx · toggle/AGENTS.md
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 부위
|
|
10
|
+
|
|
11
|
+
| 이름 | props |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `Toggle` | `ToggleProps` |
|
|
14
|
+
|
|
15
|
+
## props
|
|
16
|
+
|
|
17
|
+
### `ToggleProps`
|
|
18
|
+
|
|
19
|
+
`ComponentProps<typeof TogglePrimitive.Root>` 를 확장한다.
|
|
20
|
+
|
|
21
|
+
| 이름 | 타입 | 필수 |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `surface` | `'bare' \| 'canvas'` | |
|
|
24
|
+
|
|
25
|
+
## 변형 — `toggle`
|
|
26
|
+
|
|
27
|
+
| 축 | 값 | 기본 |
|
|
28
|
+
|---|---|---|
|
|
29
|
+
| `surface` | `bare` · `canvas` | `bare` |
|
|
30
|
+
|
|
31
|
+
## 함께 받는 것
|
|
32
|
+
|
|
33
|
+
`tv`
|
|
34
|
+
|
|
35
|
+
## npm 의존
|
|
36
|
+
|
|
37
|
+
`radix-ui`
|
package/toggle/index.tsx
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Toggle as TogglePrimitive } from 'radix-ui'
|
|
4
|
+
import type { ComponentProps } from 'react'
|
|
5
|
+
|
|
6
|
+
import { tv } from '../tv'
|
|
7
|
+
|
|
8
|
+
export const toggle = tv({
|
|
9
|
+
base: 'text-label rounded-full inline-flex h-8 items-center gap-tight px-3'
|
|
10
|
+
+ ' [&_svg]:size-4'
|
|
11
|
+
+ ' ring-1 ring-inset ring-line-strong transition-state press press-shrink'
|
|
12
|
+
+ ' text-fg-muted'
|
|
13
|
+
+ ' enabled:hover:bg-hover'
|
|
14
|
+
+ ' on:bg-control-on on:ring-control-on on:enabled:text-fg-on-control'
|
|
15
|
+
+ ' on:enabled:hover:bg-control-on-hover'
|
|
16
|
+
+ ' focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
17
|
+
+ ' disabled:text-fg-disabled disabled:ring-line disabled:cursor-not-allowed'
|
|
18
|
+
+ ' on:disabled:bg-control-on-disabled on:disabled:ring-control-on-disabled on:disabled:text-fg-on-control',
|
|
19
|
+
|
|
20
|
+
variants: {
|
|
21
|
+
surface: {
|
|
22
|
+
bare: '',
|
|
23
|
+
canvas: 'bg-canvas',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
defaultVariants: { surface: 'bare' },
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
export interface ToggleProps extends ComponentProps<typeof TogglePrimitive.Root> {
|
|
31
|
+
surface?: 'bare' | 'canvas'
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function Toggle({ surface, className, ...rest }: ToggleProps) {
|
|
35
|
+
return <TogglePrimitive.Root {...rest} className={toggle({ surface, className })} />
|
|
36
|
+
}
|