@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
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Accordion
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
받기 npx @pantoia/ui add accordion
|
|
5
|
+
층 ui
|
|
6
|
+
파일 accordion/index.tsx · accordion/index.module.css · accordion/AGENTS.md
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 부위
|
|
10
|
+
|
|
11
|
+
| 이름 | props |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `Accordion` | `AccordionProps` |
|
|
14
|
+
| `AccordionItem` | — |
|
|
15
|
+
| `AccordionTrigger` | — |
|
|
16
|
+
| `AccordionContent` | — |
|
|
17
|
+
|
|
18
|
+
### `AccordionItem`
|
|
19
|
+
|
|
20
|
+
| 이름 | 타입 | 필수 |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `value` | `string` | 예 |
|
|
23
|
+
| `disabled` | `boolean` | |
|
|
24
|
+
| `className` | `string` | |
|
|
25
|
+
| `children` | `ReactNode` | |
|
|
26
|
+
|
|
27
|
+
### `AccordionTrigger`
|
|
28
|
+
|
|
29
|
+
| 이름 | 타입 | 필수 |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `className` | `string` | |
|
|
32
|
+
| `children` | `ReactNode` | |
|
|
33
|
+
|
|
34
|
+
### `AccordionContent`
|
|
35
|
+
|
|
36
|
+
| 이름 | 타입 | 필수 |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `className` | `string` | |
|
|
39
|
+
| `children` | `ReactNode` | |
|
|
40
|
+
|
|
41
|
+
## props
|
|
42
|
+
|
|
43
|
+
### `AccordionProps`
|
|
44
|
+
|
|
45
|
+
| 이름 | 타입 | 필수 |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| `value` | `string \| string[]` | |
|
|
48
|
+
| `defaultValue` | `string \| string[]` | |
|
|
49
|
+
| `onValueChange` | `(value: string & string[]) => void` | |
|
|
50
|
+
| `multiple` | `boolean` | |
|
|
51
|
+
| `className` | `string` | |
|
|
52
|
+
| `children` | `ReactNode` | |
|
|
53
|
+
|
|
54
|
+
## 함께 받는 것
|
|
55
|
+
|
|
56
|
+
`tv`
|
|
57
|
+
|
|
58
|
+
## npm 의존
|
|
59
|
+
|
|
60
|
+
`lucide-react` · `radix-ui`
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
@keyframes acc-open { from { height: 0; } to { height: var(--radix-accordion-content-height); } }
|
|
2
|
+
@keyframes acc-close { from { height: var(--radix-accordion-content-height); } to { height: 0; } }
|
|
3
|
+
|
|
4
|
+
.panel { overflow: hidden; }
|
|
5
|
+
.panel[data-state="open"] { animation: acc-open var(--dur-enter) var(--ease-enter); }
|
|
6
|
+
.panel[data-state="closed"] { animation: acc-close var(--dur-exit) var(--ease-exit); }
|
|
7
|
+
|
|
8
|
+
@media (prefers-reduced-motion: reduce) {
|
|
9
|
+
.panel { animation: none; }
|
|
10
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { ChevronDown } from 'lucide-react'
|
|
4
|
+
import { Accordion as AccordionPrimitive } from 'radix-ui'
|
|
5
|
+
import type { ReactNode } from 'react'
|
|
6
|
+
|
|
7
|
+
import { tv } from '../tv'
|
|
8
|
+
|
|
9
|
+
import s from './index.module.css'
|
|
10
|
+
|
|
11
|
+
export interface AccordionProps {
|
|
12
|
+
value?: string | string[]
|
|
13
|
+
defaultValue?: string | string[]
|
|
14
|
+
onValueChange?: (value: string & string[]) => void
|
|
15
|
+
multiple?: boolean
|
|
16
|
+
className?: string
|
|
17
|
+
children?: ReactNode
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Accordion({ value, defaultValue, onValueChange, multiple, className, children }: AccordionProps) {
|
|
21
|
+
const shared = {
|
|
22
|
+
className: tv({ base: 'rounded-panel border border-line overflow-hidden divide-y divide-line' })({ className }),
|
|
23
|
+
children,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return multiple
|
|
27
|
+
? (
|
|
28
|
+
<AccordionPrimitive.Root
|
|
29
|
+
type="multiple"
|
|
30
|
+
value={value as string[] | undefined}
|
|
31
|
+
defaultValue={defaultValue as string[] | undefined}
|
|
32
|
+
onValueChange={onValueChange}
|
|
33
|
+
{...shared}
|
|
34
|
+
/>
|
|
35
|
+
)
|
|
36
|
+
: (
|
|
37
|
+
<AccordionPrimitive.Root
|
|
38
|
+
type="single"
|
|
39
|
+
collapsible
|
|
40
|
+
value={value as string | undefined}
|
|
41
|
+
defaultValue={defaultValue as string | undefined}
|
|
42
|
+
onValueChange={onValueChange}
|
|
43
|
+
{...shared}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function AccordionItem({ value, disabled, className, children }: {
|
|
49
|
+
value: string
|
|
50
|
+
disabled?: boolean
|
|
51
|
+
className?: string
|
|
52
|
+
children?: ReactNode
|
|
53
|
+
}) {
|
|
54
|
+
return (
|
|
55
|
+
<AccordionPrimitive.Item value={value} disabled={disabled} className={className}>
|
|
56
|
+
{children}
|
|
57
|
+
</AccordionPrimitive.Item>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function AccordionTrigger({ className, children }: { className?: string, children?: ReactNode }) {
|
|
62
|
+
return (
|
|
63
|
+
<AccordionPrimitive.Header>
|
|
64
|
+
<AccordionPrimitive.Trigger
|
|
65
|
+
className={tv({ base: 'text-label text-fg flex h-11 w-full items-center'
|
|
66
|
+
+ ' gap-stack border-b border-transparent px-4 transition-state'
|
|
67
|
+
+ ' press open:border-line enabled:hover:bg-hover'
|
|
68
|
+
+ ' disabled:text-fg-disabled disabled:cursor-not-allowed'
|
|
69
|
+
+ ' focus-visible:outline-2 focus-visible:-outline-offset-2'
|
|
70
|
+
+ ' focus-visible:outline-ring' })({ className })}
|
|
71
|
+
>
|
|
72
|
+
<span className="flex min-w-0 grow items-center gap-tight">{children}</span>
|
|
73
|
+
|
|
74
|
+
<ChevronDown
|
|
75
|
+
className="text-fg-subtle size-4 shrink-0 transition-transform open:rotate-180
|
|
76
|
+
motion-reduce:transition-none"
|
|
77
|
+
/>
|
|
78
|
+
</AccordionPrimitive.Trigger>
|
|
79
|
+
</AccordionPrimitive.Header>
|
|
80
|
+
)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function AccordionContent({ className, children }: { className?: string, children?: ReactNode }) {
|
|
84
|
+
return (
|
|
85
|
+
<AccordionPrimitive.Content className={s.panel}>
|
|
86
|
+
<div className={tv({ base: 'bg-well text-caption text-fg-muted px-4 py-3' })({ className })}>
|
|
87
|
+
{children}
|
|
88
|
+
</div>
|
|
89
|
+
</AccordionPrimitive.Content>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# AlertDialog
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
받기 npx @pantoia/ui add alert-dialog
|
|
5
|
+
층 ui
|
|
6
|
+
파일 alert-dialog/index.tsx · alert-dialog/AGENTS.md
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 부위
|
|
10
|
+
|
|
11
|
+
| 이름 | props |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `AlertDialog` | `AlertDialogProps` |
|
|
14
|
+
| `AlertDialogTrigger` | — |
|
|
15
|
+
| `AlertDialogContent` | `ComponentProps<typeof AlertDialogPrimitive.Content>` |
|
|
16
|
+
| `AlertDialogTitle` | `ComponentProps<typeof AlertDialogPrimitive.Title>` |
|
|
17
|
+
| `AlertDialogDescription` | `ComponentProps<typeof AlertDialogPrimitive.Description>` |
|
|
18
|
+
| `AlertDialogHeader` | `AlertDialogHeaderProps` |
|
|
19
|
+
| `AlertDialogFooter` | — |
|
|
20
|
+
| `AlertDialogAction` | — |
|
|
21
|
+
| `AlertDialogCancel` | — |
|
|
22
|
+
|
|
23
|
+
### `AlertDialogTrigger`
|
|
24
|
+
|
|
25
|
+
| 이름 | 타입 | 필수 |
|
|
26
|
+
|---|---|---|
|
|
27
|
+
| `children` | `ReactNode` | 예 |
|
|
28
|
+
|
|
29
|
+
### `AlertDialogFooter`
|
|
30
|
+
|
|
31
|
+
| 이름 | 타입 | 필수 |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| `className` | `string` | |
|
|
34
|
+
| `children` | `ReactNode` | |
|
|
35
|
+
|
|
36
|
+
### `AlertDialogAction`
|
|
37
|
+
|
|
38
|
+
| 이름 | 타입 | 필수 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| `color` | `'neutral' \| 'primary' \| 'danger'` | |
|
|
41
|
+
| `onClick` | `() => void` | |
|
|
42
|
+
| `className` | `string` | |
|
|
43
|
+
| `children` | `ReactNode` | |
|
|
44
|
+
|
|
45
|
+
### `AlertDialogCancel`
|
|
46
|
+
|
|
47
|
+
| 이름 | 타입 | 필수 |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| `className` | `string` | |
|
|
50
|
+
| `children` | `ReactNode` | |
|
|
51
|
+
|
|
52
|
+
## props
|
|
53
|
+
|
|
54
|
+
### `AlertDialogProps`
|
|
55
|
+
|
|
56
|
+
| 이름 | 타입 | 필수 |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| `open` | `boolean` | |
|
|
59
|
+
| `defaultOpen` | `boolean` | |
|
|
60
|
+
| `onOpenChange` | `(open: boolean) => void` | |
|
|
61
|
+
| `children` | `ReactNode` | |
|
|
62
|
+
|
|
63
|
+
### `AlertDialogHeaderProps`
|
|
64
|
+
|
|
65
|
+
| 이름 | 타입 | 필수 |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| `lead` | `ReactNode` | |
|
|
68
|
+
| `trail` | `ReactNode` | |
|
|
69
|
+
| `className` | `string` | |
|
|
70
|
+
| `children` | `ReactNode` | |
|
|
71
|
+
|
|
72
|
+
## 함께 받는 것
|
|
73
|
+
|
|
74
|
+
`button` · `tv`
|
|
75
|
+
|
|
76
|
+
## npm 의존
|
|
77
|
+
|
|
78
|
+
`radix-ui`
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { AlertDialog as AlertDialogPrimitive } from 'radix-ui'
|
|
4
|
+
import type { ComponentProps, ReactNode } from 'react'
|
|
5
|
+
|
|
6
|
+
import { Button } from '../button'
|
|
7
|
+
import { tv } from '../tv'
|
|
8
|
+
|
|
9
|
+
export interface AlertDialogProps {
|
|
10
|
+
open?: boolean
|
|
11
|
+
defaultOpen?: boolean
|
|
12
|
+
onOpenChange?: (open: boolean) => void
|
|
13
|
+
children?: ReactNode
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function AlertDialog({ open, defaultOpen, onOpenChange, children }: AlertDialogProps) {
|
|
17
|
+
return (
|
|
18
|
+
<AlertDialogPrimitive.Root open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange}>
|
|
19
|
+
{children}
|
|
20
|
+
</AlertDialogPrimitive.Root>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function AlertDialogTrigger({ children }: { children: ReactNode }) {
|
|
25
|
+
return <AlertDialogPrimitive.Trigger asChild>{children}</AlertDialogPrimitive.Trigger>
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function AlertDialogContent({ className, children, ...rest }: ComponentProps<typeof AlertDialogPrimitive.Content>) {
|
|
29
|
+
return (
|
|
30
|
+
<AlertDialogPrimitive.Portal>
|
|
31
|
+
<AlertDialogPrimitive.Overlay className="bg-overlay z-overlay fixed inset-0 open:animate-fade-in closed:animate-fade-out" />
|
|
32
|
+
|
|
33
|
+
<AlertDialogPrimitive.Content
|
|
34
|
+
{...rest}
|
|
35
|
+
className={tv({ base: 'bg-float text-fg z-overlay fixed left-1/2 top-1/2'
|
|
36
|
+
+ ' w-full max-w-112 -translate-x-1/2 -translate-y-1/2'
|
|
37
|
+
+ ' rounded-r20 p-6 flex flex-col gap-stack overflow-y-auto'
|
|
38
|
+
+ ' open:animate-fade-in closed:animate-fade-out'
|
|
39
|
+
+ ' focus-visible:outline-none' })({ className })}
|
|
40
|
+
>
|
|
41
|
+
{children}
|
|
42
|
+
</AlertDialogPrimitive.Content>
|
|
43
|
+
</AlertDialogPrimitive.Portal>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function AlertDialogTitle({ className, ...rest }: ComponentProps<typeof AlertDialogPrimitive.Title>) {
|
|
48
|
+
return <AlertDialogPrimitive.Title {...rest} className={tv({ base: 'text-section' })({ className })} />
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function AlertDialogDescription({ className, ...rest }: ComponentProps<typeof AlertDialogPrimitive.Description>) {
|
|
52
|
+
return (
|
|
53
|
+
<AlertDialogPrimitive.Description
|
|
54
|
+
{...rest}
|
|
55
|
+
className={tv({ base: 'text-prose text-fg-muted' })({ className })}
|
|
56
|
+
/>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const alertDialogHeader = tv({
|
|
61
|
+
slots: {
|
|
62
|
+
root: 'flex items-start gap-stack',
|
|
63
|
+
lead: 'shrink-0',
|
|
64
|
+
body: 'flex min-w-0 grow flex-col gap-tight',
|
|
65
|
+
trail: 'shrink-0',
|
|
66
|
+
},
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
export interface AlertDialogHeaderProps {
|
|
70
|
+
lead?: ReactNode
|
|
71
|
+
trail?: ReactNode
|
|
72
|
+
className?: string
|
|
73
|
+
children?: ReactNode
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function AlertDialogHeader({ lead, trail, className, children }: AlertDialogHeaderProps) {
|
|
77
|
+
const ui = alertDialogHeader()
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div className={ui.root({ className })}>
|
|
81
|
+
{lead && <div className={ui.lead()}>{lead}</div>}
|
|
82
|
+
<div className={ui.body()}>{children}</div>
|
|
83
|
+
{trail && <div className={ui.trail()}>{trail}</div>}
|
|
84
|
+
</div>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function AlertDialogFooter({ className, children }: { className?: string, children?: ReactNode }) {
|
|
89
|
+
return (
|
|
90
|
+
<div className={tv({ base: 'flex justify-end gap-2' })({ className })}>
|
|
91
|
+
{children}
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function AlertDialogAction({ color = 'neutral', onClick, className, children }: {
|
|
97
|
+
color?: 'neutral' | 'primary' | 'danger'
|
|
98
|
+
onClick?: () => void
|
|
99
|
+
className?: string
|
|
100
|
+
children?: ReactNode
|
|
101
|
+
}) {
|
|
102
|
+
return (
|
|
103
|
+
<AlertDialogPrimitive.Action asChild>
|
|
104
|
+
<Button emphasis="solid" color={color} onClick={onClick} className={className}>
|
|
105
|
+
{children}
|
|
106
|
+
</Button>
|
|
107
|
+
</AlertDialogPrimitive.Action>
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function AlertDialogCancel({ className, children }: { className?: string, children?: ReactNode }) {
|
|
112
|
+
return (
|
|
113
|
+
<AlertDialogPrimitive.Cancel asChild>
|
|
114
|
+
<Button color="neutral" emphasis="outline" className={className}>
|
|
115
|
+
{children}
|
|
116
|
+
</Button>
|
|
117
|
+
</AlertDialogPrimitive.Cancel>
|
|
118
|
+
)
|
|
119
|
+
}
|
package/badge/AGENTS.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Badge
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
받기 npx @pantoia/ui add badge
|
|
5
|
+
층 ui
|
|
6
|
+
파일 badge/index.tsx · badge/AGENTS.md
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 부위
|
|
10
|
+
|
|
11
|
+
| 이름 | props |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `Badge` | `BadgeProps` |
|
|
14
|
+
|
|
15
|
+
## props
|
|
16
|
+
|
|
17
|
+
### `BadgeProps`
|
|
18
|
+
|
|
19
|
+
`Omit<HTMLAttributes<HTMLSpanElement>, 'color'>` 를 확장한다.
|
|
20
|
+
|
|
21
|
+
| 이름 | 타입 | 필수 |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `color` | `'neutral' \| 'primary' \| 'info' \| 'success' \| 'warning' \| 'danger'` | |
|
|
24
|
+
| `shape` | `'square' \| 'pill'` | |
|
|
25
|
+
|
|
26
|
+
## 변형 — `badge`
|
|
27
|
+
|
|
28
|
+
| 축 | 값 | 기본 |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `color` | `neutral` · `primary` · `info` · `success` · `warning` · `danger` | `neutral` |
|
|
31
|
+
| `shape` | `square` · `pill` | `square` |
|
|
32
|
+
|
|
33
|
+
## 함께 받는 것
|
|
34
|
+
|
|
35
|
+
`tv`
|
package/badge/index.tsx
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { HTMLAttributes } from 'react'
|
|
2
|
+
|
|
3
|
+
import { tv } from '../tv'
|
|
4
|
+
|
|
5
|
+
export const badge = tv({
|
|
6
|
+
base: 'inline-flex items-center gap-tight px-2 py-0.5 text-micro'
|
|
7
|
+
+ ' [&_svg]:size-3',
|
|
8
|
+
|
|
9
|
+
variants: {
|
|
10
|
+
color: {
|
|
11
|
+
neutral: 'bg-well text-fg-muted',
|
|
12
|
+
primary: 'bg-primary-weak text-primary-on-weak',
|
|
13
|
+
info: 'bg-info-weak text-info-on-weak',
|
|
14
|
+
success: 'bg-success-weak text-success-on-weak',
|
|
15
|
+
warning: 'bg-warning-weak text-warning-on-weak',
|
|
16
|
+
danger: 'bg-danger-weak text-danger-on-weak',
|
|
17
|
+
},
|
|
18
|
+
shape: {
|
|
19
|
+
square: 'rounded-mark',
|
|
20
|
+
pill: 'rounded-full',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
defaultVariants: { color: 'neutral', shape: 'square' },
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export interface BadgeProps extends Omit<HTMLAttributes<HTMLSpanElement>, 'color'> {
|
|
28
|
+
color?: 'neutral' | 'primary' | 'info' | 'success' | 'warning' | 'danger'
|
|
29
|
+
shape?: 'square' | 'pill'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function Badge({ color, shape, className, ...rest }: BadgeProps) {
|
|
33
|
+
return <span {...rest} className={badge({ color, shape, className })} />
|
|
34
|
+
}
|
package/button/AGENTS.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Button
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
받기 npx @pantoia/ui add button
|
|
5
|
+
층 ui
|
|
6
|
+
파일 button/index.tsx · button/AGENTS.md
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 부위
|
|
10
|
+
|
|
11
|
+
| 이름 | props |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `Button` | `ButtonProps` |
|
|
14
|
+
|
|
15
|
+
## props
|
|
16
|
+
|
|
17
|
+
### `ButtonProps`
|
|
18
|
+
|
|
19
|
+
`Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'color'>` 를 확장한다.
|
|
20
|
+
|
|
21
|
+
| 이름 | 타입 | 필수 |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `color` | `'primary' \| 'neutral' \| 'danger'` | 예 |
|
|
24
|
+
| `emphasis` | `'solid' \| 'weak' \| 'outline' \| 'ghost'` | 예 |
|
|
25
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | |
|
|
26
|
+
| `shape` | `'rect' \| 'square' \| 'pill' \| 'circle'` | |
|
|
27
|
+
|
|
28
|
+
## 변형 — `button`
|
|
29
|
+
|
|
30
|
+
| 축 | 값 | 기본 |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
| `color` | `primary` · `neutral` · `danger` | — |
|
|
33
|
+
| `emphasis` | `solid` · `weak` · `outline` · `ghost` | — |
|
|
34
|
+
| `size` | `sm` · `md` · `lg` | `md` |
|
|
35
|
+
| `shape` | `rect` · `square` · `pill` · `circle` | `rect` |
|
|
36
|
+
|
|
37
|
+
## 함께 받는 것
|
|
38
|
+
|
|
39
|
+
`tv`
|
package/button/index.tsx
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes } from 'react'
|
|
2
|
+
|
|
3
|
+
import { tv } from '../tv'
|
|
4
|
+
|
|
5
|
+
export const button = tv({
|
|
6
|
+
base: 'inline-flex shrink-0 items-center justify-center gap-tight whitespace-nowrap'
|
|
7
|
+
+ ' rounded-r8 text-label'
|
|
8
|
+
+ ' transition-state press press-shrink'
|
|
9
|
+
+ ' focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring'
|
|
10
|
+
+ ' disabled:text-fg-disabled disabled:cursor-not-allowed',
|
|
11
|
+
|
|
12
|
+
variants: {
|
|
13
|
+
color: { primary: '', neutral: '', danger: '' },
|
|
14
|
+
emphasis: { solid: '', weak: '', outline: '', ghost: '' },
|
|
15
|
+
size: {
|
|
16
|
+
sm: 'h-8 [&_svg]:size-4',
|
|
17
|
+
md: 'h-9 [&_svg]:size-4',
|
|
18
|
+
lg: 'h-10 text-t16 font-medium [&_svg]:size-5',
|
|
19
|
+
},
|
|
20
|
+
shape: {
|
|
21
|
+
rect: '',
|
|
22
|
+
square: '',
|
|
23
|
+
pill: 'rounded-full',
|
|
24
|
+
circle: 'rounded-full',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
compoundVariants: [
|
|
29
|
+
{ color: 'primary', emphasis: 'solid',
|
|
30
|
+
class: 'bg-primary-solid text-fg-on-solid'
|
|
31
|
+
+ ' enabled:hover:bg-primary-solid-hover'
|
|
32
|
+
+ ' disabled:bg-primary-solid/20' },
|
|
33
|
+
{ color: 'primary', emphasis: 'weak',
|
|
34
|
+
class: 'bg-primary-weak text-primary-on-weak disabled:text-primary-on-weak/40'
|
|
35
|
+
+ ' enabled:hover:bg-primary-solid/10' },
|
|
36
|
+
{ color: 'primary', emphasis: 'outline',
|
|
37
|
+
class: 'text-primary-on-weak disabled:text-primary-on-weak/40'
|
|
38
|
+
+ ' ring-1 ring-inset ring-primary-stroke disabled:ring-line'
|
|
39
|
+
+ ' enabled:hover:bg-primary-solid/10' },
|
|
40
|
+
{ color: 'primary', emphasis: 'ghost',
|
|
41
|
+
class: 'text-primary-on-weak disabled:text-primary-on-weak/40'
|
|
42
|
+
+ ' enabled:hover:bg-primary-solid/10' },
|
|
43
|
+
|
|
44
|
+
{ color: 'neutral', emphasis: 'solid',
|
|
45
|
+
class: 'bg-neutral-solid text-fg-on-control'
|
|
46
|
+
+ ' enabled:hover:bg-neutral-solid-hover'
|
|
47
|
+
+ ' disabled:bg-neutral-solid/20' },
|
|
48
|
+
{ color: 'neutral', emphasis: 'weak',
|
|
49
|
+
class: 'bg-well text-fg'
|
|
50
|
+
+ ' enabled:hover:bg-fg-muted/10' },
|
|
51
|
+
{ color: 'neutral', emphasis: 'outline',
|
|
52
|
+
class: 'text-fg ring-1 ring-inset ring-line-strong disabled:ring-line'
|
|
53
|
+
+ ' enabled:hover:bg-hover' },
|
|
54
|
+
{ color: 'neutral', emphasis: 'ghost',
|
|
55
|
+
class: 'text-fg enabled:hover:bg-hover' },
|
|
56
|
+
|
|
57
|
+
{ color: 'danger', emphasis: 'solid',
|
|
58
|
+
class: 'bg-danger-solid text-fg-on-solid'
|
|
59
|
+
+ ' enabled:hover:bg-danger-solid-hover'
|
|
60
|
+
+ ' disabled:bg-danger-solid/20' },
|
|
61
|
+
{ color: 'danger', emphasis: 'weak',
|
|
62
|
+
class: 'bg-danger-weak text-danger-on-weak disabled:text-danger-on-weak/40'
|
|
63
|
+
+ ' enabled:hover:bg-danger/10' },
|
|
64
|
+
{ color: 'danger', emphasis: 'outline',
|
|
65
|
+
class: 'text-danger-on-weak disabled:text-danger-on-weak/40'
|
|
66
|
+
+ ' ring-1 ring-inset ring-danger-stroke disabled:ring-line'
|
|
67
|
+
+ ' enabled:hover:bg-danger/10' },
|
|
68
|
+
{ color: 'danger', emphasis: 'ghost',
|
|
69
|
+
class: 'text-danger-on-weak disabled:text-danger-on-weak/40'
|
|
70
|
+
+ ' enabled:hover:bg-danger/10' },
|
|
71
|
+
|
|
72
|
+
{ shape: ['rect', 'pill'], size: 'sm', class: 'px-3' },
|
|
73
|
+
{ shape: ['rect', 'pill'], size: 'md', class: 'px-3.5' },
|
|
74
|
+
{ shape: ['rect', 'pill'], size: 'lg', class: 'px-4' },
|
|
75
|
+
{ shape: ['square', 'circle'], size: 'sm', class: 'w-8' },
|
|
76
|
+
{ shape: ['square', 'circle'], size: 'md', class: 'w-9' },
|
|
77
|
+
{ shape: ['square', 'circle'], size: 'lg', class: 'w-10' },
|
|
78
|
+
],
|
|
79
|
+
|
|
80
|
+
defaultVariants: { size: 'md', shape: 'rect' },
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
export interface ButtonProps extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'color'> {
|
|
84
|
+
color: 'primary' | 'neutral' | 'danger'
|
|
85
|
+
emphasis: 'solid' | 'weak' | 'outline' | 'ghost'
|
|
86
|
+
size?: 'sm' | 'md' | 'lg'
|
|
87
|
+
shape?: 'rect' | 'square' | 'pill' | 'circle'
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function Button({ color, emphasis, size, shape, className, ...rest }: ButtonProps) {
|
|
91
|
+
return (
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
{...rest}
|
|
95
|
+
className={button({ color, emphasis, size, shape, className })}
|
|
96
|
+
/>
|
|
97
|
+
)
|
|
98
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Callout
|
|
2
|
+
|
|
3
|
+
```
|
|
4
|
+
받기 npx @pantoia/ui add callout
|
|
5
|
+
층 ui
|
|
6
|
+
파일 callout/index.tsx · callout/AGENTS.md
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## 부위
|
|
10
|
+
|
|
11
|
+
| 이름 | props |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `Callout` | `CalloutProps` |
|
|
14
|
+
|
|
15
|
+
## props
|
|
16
|
+
|
|
17
|
+
### `CalloutProps`
|
|
18
|
+
|
|
19
|
+
| 이름 | 타입 | 필수 |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| `color` | `'info' \| 'success' \| 'warning' \| 'danger'` | |
|
|
22
|
+
| `live` | `boolean` | |
|
|
23
|
+
| `icon` | `ReactNode` | |
|
|
24
|
+
| `title` | `ReactNode` | |
|
|
25
|
+
| `className` | `string` | |
|
|
26
|
+
| `children` | `ReactNode` | |
|
|
27
|
+
|
|
28
|
+
## 변형 — `callout`
|
|
29
|
+
|
|
30
|
+
슬롯 `root` · `icon` · `title` · `body` · `text`
|
|
31
|
+
|
|
32
|
+
| 축 | 값 | 기본 |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `color` | `info` · `success` · `warning` · `danger` | `info` |
|
|
35
|
+
|
|
36
|
+
## 함께 받는 것
|
|
37
|
+
|
|
38
|
+
`tv`
|
|
39
|
+
|
|
40
|
+
## npm 의존
|
|
41
|
+
|
|
42
|
+
`lucide-react`
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { CircleCheck, CircleX, Info, TriangleAlert } from 'lucide-react'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
|
|
4
|
+
import { tv } from '../tv'
|
|
5
|
+
|
|
6
|
+
export const callout = tv({
|
|
7
|
+
slots: {
|
|
8
|
+
root: 'flex gap-stack rounded-panel border p-4 text-prose',
|
|
9
|
+
icon: 'flex shrink-0 mt-0.5 [&_svg]:size-5',
|
|
10
|
+
title: 'font-bold',
|
|
11
|
+
body: 'flex min-w-0 flex-col gap-tight',
|
|
12
|
+
text: 'flex flex-col gap-2',
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
variants: {
|
|
16
|
+
color: {
|
|
17
|
+
info: {
|
|
18
|
+
root: 'bg-info-weak text-info-on-weak border-info-stroke',
|
|
19
|
+
icon: 'text-info',
|
|
20
|
+
},
|
|
21
|
+
success: {
|
|
22
|
+
root: 'bg-success-weak text-success-on-weak border-success-stroke',
|
|
23
|
+
icon: 'text-success',
|
|
24
|
+
},
|
|
25
|
+
warning: {
|
|
26
|
+
root: 'bg-warning-weak text-warning-on-weak border-warning-stroke',
|
|
27
|
+
icon: 'text-warning',
|
|
28
|
+
},
|
|
29
|
+
danger: {
|
|
30
|
+
root: 'bg-danger-weak text-danger-on-weak border-danger-stroke',
|
|
31
|
+
icon: 'text-danger',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
defaultVariants: { color: 'info' },
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const ICONS = {
|
|
40
|
+
info: Info,
|
|
41
|
+
success: CircleCheck,
|
|
42
|
+
warning: TriangleAlert,
|
|
43
|
+
danger: CircleX,
|
|
44
|
+
} as const
|
|
45
|
+
|
|
46
|
+
export interface CalloutProps {
|
|
47
|
+
color?: 'info' | 'success' | 'warning' | 'danger'
|
|
48
|
+
live?: boolean
|
|
49
|
+
icon?: ReactNode
|
|
50
|
+
title?: ReactNode
|
|
51
|
+
className?: string
|
|
52
|
+
children?: ReactNode
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function Callout({ color, live, icon, title, className, children }: CalloutProps) {
|
|
56
|
+
const ui = callout({ color })
|
|
57
|
+
const Fallback = ICONS[color ?? 'info']
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className={ui.root({ className })} role={live ? 'alert' : 'note'}>
|
|
61
|
+
<span className={ui.icon()}>
|
|
62
|
+
{icon ?? <Fallback />}
|
|
63
|
+
</span>
|
|
64
|
+
<div className={ui.body()}>
|
|
65
|
+
{title && <span className={ui.title()}>{title}</span>}
|
|
66
|
+
<div className={ui.text()}>{children}</div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
)
|
|
70
|
+
}
|