@glyphui/ui 0.1.1 → 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/README.md +1 -1
- package/dist/chart.d.ts +202 -0
- package/dist/chart.js +457 -0
- package/dist/data-table.js +3 -2
- package/dist/{field-DXZD0l8w.js → field-CWZ_FdkD.js} +2 -1
- package/dist/{field-context-B8cYheXz.js → field-context-anGPYXbW.js} +89 -143
- package/dist/form.js +3 -2
- package/dist/index.d.ts +4960 -266
- package/dist/index.js +5336 -146
- package/dist/{states-BxPjCUxz.js → states-DR-OoDT-.js} +2 -1
- package/dist/tv-DBAGlgXJ.js +58 -0
- package/package.json +19 -2
- package/src/components/accordion/accordion.styles.ts +41 -0
- package/src/components/accordion/accordion.tsx +86 -0
- package/src/components/accordion/index.ts +8 -0
- package/src/components/alert/alert-icons.tsx +66 -0
- package/src/components/alert/alert.styles.ts +57 -0
- package/src/components/alert/alert.tsx +86 -0
- package/src/components/alert/index.ts +2 -0
- package/src/components/alert-dialog/alert-dialog.styles.ts +24 -0
- package/src/components/alert-dialog/alert-dialog.tsx +118 -0
- package/src/components/alert-dialog/confirm.tsx +111 -0
- package/src/components/alert-dialog/index.ts +15 -0
- package/src/components/aspect-ratio/aspect-ratio.tsx +40 -0
- package/src/components/aspect-ratio/index.ts +1 -0
- package/src/components/badge/badge.styles.ts +1 -1
- package/src/components/banner/banner.styles.ts +40 -0
- package/src/components/banner/banner.tsx +101 -0
- package/src/components/banner/index.ts +2 -0
- package/src/components/blockquote/blockquote.tsx +59 -0
- package/src/components/blockquote/index.ts +1 -0
- package/src/components/breadcrumb/breadcrumb.styles.ts +20 -0
- package/src/components/breadcrumb/breadcrumb.tsx +135 -0
- package/src/components/breadcrumb/index.ts +12 -0
- package/src/components/button/button.tsx +11 -8
- package/src/components/calendar/calendar.styles.ts +50 -0
- package/src/components/calendar/calendar.tsx +84 -0
- package/src/components/calendar/index.ts +3 -0
- package/src/components/carousel/carousel.styles.ts +43 -0
- package/src/components/carousel/carousel.tsx +278 -0
- package/src/components/carousel/index.ts +13 -0
- package/src/components/code/code.styles.ts +33 -0
- package/src/components/code/code.tsx +96 -0
- package/src/components/code/index.ts +2 -0
- package/src/components/collapsible/collapsible.tsx +66 -0
- package/src/components/collapsible/index.ts +6 -0
- package/src/components/combobox/combobox.styles.ts +50 -0
- package/src/components/combobox/combobox.tsx +203 -0
- package/src/components/combobox/index.ts +2 -0
- package/src/components/command-menu/command-menu.styles.ts +41 -0
- package/src/components/command-menu/command-menu.tsx +217 -0
- package/src/components/command-menu/index.ts +14 -0
- package/src/components/context-menu/context-menu.styles.ts +19 -0
- package/src/components/context-menu/context-menu.tsx +226 -0
- package/src/components/context-menu/index.ts +19 -0
- package/src/components/copy-button/copy-button.tsx +117 -0
- package/src/components/copy-button/index.ts +1 -0
- package/src/components/date-picker/date-picker.styles.ts +42 -0
- package/src/components/date-picker/date-picker.tsx +322 -0
- package/src/components/date-picker/index.ts +8 -0
- package/src/components/description-list/description-list.styles.ts +31 -0
- package/src/components/description-list/description-list.tsx +56 -0
- package/src/components/description-list/index.ts +7 -0
- package/src/components/dialog/close-icon.tsx +22 -0
- package/src/components/dialog/dialog.styles.ts +41 -0
- package/src/components/dialog/dialog.tsx +111 -0
- package/src/components/dialog/index.ts +13 -0
- package/src/components/dropdown-menu/dropdown-menu.styles.ts +45 -0
- package/src/components/dropdown-menu/dropdown-menu.tsx +230 -0
- package/src/components/dropdown-menu/index.ts +19 -0
- package/src/components/file-upload/file-upload.styles.ts +43 -0
- package/src/components/file-upload/file-upload.tsx +343 -0
- package/src/components/file-upload/index.ts +10 -0
- package/src/components/grid/grid.tsx +144 -0
- package/src/components/grid/index.ts +1 -0
- package/src/components/hover-card/hover-card.tsx +41 -0
- package/src/components/hover-card/index.ts +1 -0
- package/src/components/input-otp/index.ts +10 -0
- package/src/components/input-otp/input-otp.styles.ts +38 -0
- package/src/components/input-otp/input-otp.tsx +116 -0
- package/src/components/link/index.ts +2 -0
- package/src/components/link/link.styles.ts +39 -0
- package/src/components/link/link.tsx +62 -0
- package/src/components/modal/index.ts +2 -0
- package/src/components/modal/modal.styles.ts +18 -0
- package/src/components/modal/modal.tsx +86 -0
- package/src/components/navigation-menu/index.ts +14 -0
- package/src/components/navigation-menu/navigation-menu.styles.ts +48 -0
- package/src/components/navigation-menu/navigation-menu.tsx +198 -0
- package/src/components/number-input/index.ts +2 -0
- package/src/components/number-input/number-input.styles.ts +41 -0
- package/src/components/number-input/number-input.tsx +211 -0
- package/src/components/pagination/index.ts +3 -0
- package/src/components/pagination/pagination-range.ts +35 -0
- package/src/components/pagination/pagination.styles.ts +41 -0
- package/src/components/pagination/pagination.tsx +130 -0
- package/src/components/password-input/index.ts +7 -0
- package/src/components/password-input/password-input.styles.ts +30 -0
- package/src/components/password-input/password-input.tsx +127 -0
- package/src/components/popover/index.ts +9 -0
- package/src/components/popover/popover.tsx +66 -0
- package/src/components/progress/index.ts +7 -0
- package/src/components/progress/progress.styles.ts +68 -0
- package/src/components/progress/progress.tsx +158 -0
- package/src/components/prose/index.ts +1 -0
- package/src/components/prose/prose.tsx +82 -0
- package/src/components/radio-group/index.ts +7 -0
- package/src/components/radio-group/radio-group.styles.ts +64 -0
- package/src/components/radio-group/radio-group.tsx +128 -0
- package/src/components/resizable/index.ts +9 -0
- package/src/components/resizable/resizable.styles.ts +28 -0
- package/src/components/resizable/resizable.tsx +69 -0
- package/src/components/scroll-area/index.ts +1 -0
- package/src/components/scroll-area/scroll-area.tsx +68 -0
- package/src/components/sheet/index.ts +13 -0
- package/src/components/sheet/sheet.styles.ts +60 -0
- package/src/components/sheet/sheet.tsx +108 -0
- package/src/components/slider/index.ts +2 -0
- package/src/components/slider/slider.styles.ts +32 -0
- package/src/components/slider/slider.tsx +131 -0
- package/src/components/stat-card/index.ts +2 -0
- package/src/components/stat-card/stat-card.styles.ts +24 -0
- package/src/components/stat-card/stat-card.tsx +108 -0
- package/src/components/stepper/index.ts +2 -0
- package/src/components/stepper/stepper.styles.ts +74 -0
- package/src/components/stepper/stepper.tsx +138 -0
- package/src/components/tag-input/index.ts +2 -0
- package/src/components/tag-input/tag-input.styles.ts +39 -0
- package/src/components/tag-input/tag-input.tsx +198 -0
- package/src/components/timeline/index.ts +8 -0
- package/src/components/timeline/timeline.styles.ts +56 -0
- package/src/components/timeline/timeline.tsx +91 -0
- package/src/components/toast/index.ts +1 -0
- package/src/components/toast/toast.tsx +91 -0
- package/src/components/toggle/index.ts +9 -0
- package/src/components/toggle/toggle.styles.ts +39 -0
- package/src/components/toggle/toggle.tsx +61 -0
- package/src/components/tree-view/index.ts +2 -0
- package/src/components/tree-view/tree-view.styles.ts +42 -0
- package/src/components/tree-view/tree-view.tsx +274 -0
- package/src/components/visually-hidden/index.ts +1 -0
- package/src/components/visually-hidden/visually-hidden.tsx +10 -0
- package/src/index.ts +45 -0
- package/src/patterns/chart/chart-parts.tsx +171 -0
- package/src/patterns/chart/chart.styles.ts +35 -0
- package/src/patterns/chart/chart.tsx +369 -0
- package/src/patterns/chart/index.ts +13 -0
- package/src/provider/glyph-provider.tsx +16 -2
- package/styles.css +16 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { ContextMenu as MenuPrimitive } from 'radix-ui'
|
|
2
|
+
import type { ComponentProps, ReactNode, SVGProps } from 'react'
|
|
3
|
+
import { useComponentConfig } from '../../hooks/use-component-config'
|
|
4
|
+
import { contextMenuStyles } from './context-menu.styles'
|
|
5
|
+
|
|
6
|
+
function useParts(tone?: 'default' | 'danger', inset?: boolean) {
|
|
7
|
+
const [, slots] = useComponentConfig('ContextMenu', {})
|
|
8
|
+
return { s: contextMenuStyles({ tone, inset }), slots }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const svg = {
|
|
12
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
13
|
+
viewBox: '0 0 24 24',
|
|
14
|
+
fill: 'none',
|
|
15
|
+
stroke: 'currentColor',
|
|
16
|
+
strokeWidth: 2,
|
|
17
|
+
strokeLinecap: 'round',
|
|
18
|
+
strokeLinejoin: 'round',
|
|
19
|
+
} as const satisfies SVGProps<SVGSVGElement>
|
|
20
|
+
|
|
21
|
+
const CheckIcon = () => (
|
|
22
|
+
<svg aria-hidden="true" {...svg}>
|
|
23
|
+
<path d="M20 6 9 17l-5-5" />
|
|
24
|
+
</svg>
|
|
25
|
+
)
|
|
26
|
+
const DotIcon = () => (
|
|
27
|
+
<svg aria-hidden="true" {...svg}>
|
|
28
|
+
<circle cx="12" cy="12" r="4" fill="currentColor" />
|
|
29
|
+
</svg>
|
|
30
|
+
)
|
|
31
|
+
const ChevronRightIcon = () => (
|
|
32
|
+
<svg aria-hidden="true" {...svg}>
|
|
33
|
+
<path d="m9 18 6-6-6-6" />
|
|
34
|
+
</svg>
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
/** A menu opened by right-click or long-press on an area. Same items as DropdownMenu. */
|
|
38
|
+
export const ContextMenu = MenuPrimitive.Root
|
|
39
|
+
export const ContextMenuTrigger = MenuPrimitive.Trigger
|
|
40
|
+
export const ContextMenuGroup = MenuPrimitive.Group
|
|
41
|
+
export const ContextMenuRadioGroup = MenuPrimitive.RadioGroup
|
|
42
|
+
export const ContextMenuSub = MenuPrimitive.Sub
|
|
43
|
+
|
|
44
|
+
export function ContextMenuContent(inProps: ComponentProps<typeof MenuPrimitive.Content>) {
|
|
45
|
+
const [props, slots] = useComponentConfig('ContextMenu', inProps)
|
|
46
|
+
const { className, ...rest } = props
|
|
47
|
+
const s = contextMenuStyles()
|
|
48
|
+
return (
|
|
49
|
+
<MenuPrimitive.Portal>
|
|
50
|
+
<MenuPrimitive.Content
|
|
51
|
+
data-slot="context-menu-content"
|
|
52
|
+
className={s.content({ class: [slots.content, className] })}
|
|
53
|
+
{...rest}
|
|
54
|
+
/>
|
|
55
|
+
</MenuPrimitive.Portal>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface ContextMenuItemProps extends ComponentProps<typeof MenuPrimitive.Item> {
|
|
60
|
+
icon?: ReactNode
|
|
61
|
+
/** Keyboard hint shown at the end, e.g. "⌘C". Display only. */
|
|
62
|
+
shortcut?: ReactNode
|
|
63
|
+
/** `danger` for destructive actions. */
|
|
64
|
+
tone?: 'default' | 'danger'
|
|
65
|
+
/** Indent to line up with checkbox and radio items. */
|
|
66
|
+
inset?: boolean
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function ContextMenuItem({
|
|
70
|
+
icon,
|
|
71
|
+
shortcut,
|
|
72
|
+
tone,
|
|
73
|
+
inset,
|
|
74
|
+
className,
|
|
75
|
+
children,
|
|
76
|
+
...props
|
|
77
|
+
}: ContextMenuItemProps) {
|
|
78
|
+
const { s, slots } = useParts(tone, inset)
|
|
79
|
+
return (
|
|
80
|
+
<MenuPrimitive.Item
|
|
81
|
+
data-slot="context-menu-item"
|
|
82
|
+
data-tone={tone ?? 'default'}
|
|
83
|
+
className={s.item({ class: [slots.item, className] })}
|
|
84
|
+
{...props}
|
|
85
|
+
>
|
|
86
|
+
{icon && (
|
|
87
|
+
<span className={s.icon({ class: slots.icon })} aria-hidden="true">
|
|
88
|
+
{icon}
|
|
89
|
+
</span>
|
|
90
|
+
)}
|
|
91
|
+
{children}
|
|
92
|
+
{shortcut && <ContextMenuShortcut>{shortcut}</ContextMenuShortcut>}
|
|
93
|
+
</MenuPrimitive.Item>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function ContextMenuCheckboxItem({
|
|
98
|
+
className,
|
|
99
|
+
children,
|
|
100
|
+
...props
|
|
101
|
+
}: ComponentProps<typeof MenuPrimitive.CheckboxItem>) {
|
|
102
|
+
const { s, slots } = useParts(undefined, true)
|
|
103
|
+
return (
|
|
104
|
+
<MenuPrimitive.CheckboxItem
|
|
105
|
+
data-slot="context-menu-checkbox-item"
|
|
106
|
+
className={s.item({ class: [slots.item, className] })}
|
|
107
|
+
{...props}
|
|
108
|
+
>
|
|
109
|
+
<span className={s.indicator({ class: slots.indicator })}>
|
|
110
|
+
<MenuPrimitive.ItemIndicator>
|
|
111
|
+
<CheckIcon />
|
|
112
|
+
</MenuPrimitive.ItemIndicator>
|
|
113
|
+
</span>
|
|
114
|
+
{children}
|
|
115
|
+
</MenuPrimitive.CheckboxItem>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function ContextMenuRadioItem({
|
|
120
|
+
className,
|
|
121
|
+
children,
|
|
122
|
+
...props
|
|
123
|
+
}: ComponentProps<typeof MenuPrimitive.RadioItem>) {
|
|
124
|
+
const { s, slots } = useParts(undefined, true)
|
|
125
|
+
return (
|
|
126
|
+
<MenuPrimitive.RadioItem
|
|
127
|
+
data-slot="context-menu-radio-item"
|
|
128
|
+
className={s.item({ class: [slots.item, className] })}
|
|
129
|
+
{...props}
|
|
130
|
+
>
|
|
131
|
+
<span className={s.indicator({ class: slots.indicator })}>
|
|
132
|
+
<MenuPrimitive.ItemIndicator>
|
|
133
|
+
<DotIcon />
|
|
134
|
+
</MenuPrimitive.ItemIndicator>
|
|
135
|
+
</span>
|
|
136
|
+
{children}
|
|
137
|
+
</MenuPrimitive.RadioItem>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function ContextMenuLabel({
|
|
142
|
+
inset,
|
|
143
|
+
className,
|
|
144
|
+
...props
|
|
145
|
+
}: ComponentProps<typeof MenuPrimitive.Label> & { inset?: boolean }) {
|
|
146
|
+
const { s, slots } = useParts(undefined, inset)
|
|
147
|
+
return (
|
|
148
|
+
<MenuPrimitive.Label
|
|
149
|
+
data-slot="context-menu-label"
|
|
150
|
+
className={s.label({ class: [slots.label, className] })}
|
|
151
|
+
{...props}
|
|
152
|
+
/>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function ContextMenuSeparator({
|
|
157
|
+
className,
|
|
158
|
+
...props
|
|
159
|
+
}: ComponentProps<typeof MenuPrimitive.Separator>) {
|
|
160
|
+
const { s, slots } = useParts()
|
|
161
|
+
return (
|
|
162
|
+
<MenuPrimitive.Separator
|
|
163
|
+
data-slot="context-menu-separator"
|
|
164
|
+
className={s.separator({ class: [slots.separator, className] })}
|
|
165
|
+
{...props}
|
|
166
|
+
/>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export function ContextMenuShortcut({ className, ...props }: ComponentProps<'span'>) {
|
|
171
|
+
const { s, slots } = useParts()
|
|
172
|
+
return (
|
|
173
|
+
<span
|
|
174
|
+
data-slot="context-menu-shortcut"
|
|
175
|
+
className={s.shortcut({ class: [slots.shortcut, className] })}
|
|
176
|
+
{...props}
|
|
177
|
+
/>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export interface ContextMenuSubTriggerProps
|
|
182
|
+
extends ComponentProps<typeof MenuPrimitive.SubTrigger> {
|
|
183
|
+
icon?: ReactNode
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function ContextMenuSubTrigger({
|
|
187
|
+
icon,
|
|
188
|
+
className,
|
|
189
|
+
children,
|
|
190
|
+
...props
|
|
191
|
+
}: ContextMenuSubTriggerProps) {
|
|
192
|
+
const { s, slots } = useParts()
|
|
193
|
+
return (
|
|
194
|
+
<MenuPrimitive.SubTrigger
|
|
195
|
+
data-slot="context-menu-sub-trigger"
|
|
196
|
+
className={s.subTrigger({ class: [slots.subTrigger, className] })}
|
|
197
|
+
{...props}
|
|
198
|
+
>
|
|
199
|
+
{icon && (
|
|
200
|
+
<span className={s.icon({ class: slots.icon })} aria-hidden="true">
|
|
201
|
+
{icon}
|
|
202
|
+
</span>
|
|
203
|
+
)}
|
|
204
|
+
{children}
|
|
205
|
+
<span className="ml-auto flex text-faint-foreground">
|
|
206
|
+
<ChevronRightIcon />
|
|
207
|
+
</span>
|
|
208
|
+
</MenuPrimitive.SubTrigger>
|
|
209
|
+
)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function ContextMenuSubContent({
|
|
213
|
+
className,
|
|
214
|
+
...props
|
|
215
|
+
}: ComponentProps<typeof MenuPrimitive.SubContent>) {
|
|
216
|
+
const { s, slots } = useParts()
|
|
217
|
+
return (
|
|
218
|
+
<MenuPrimitive.Portal>
|
|
219
|
+
<MenuPrimitive.SubContent
|
|
220
|
+
data-slot="context-menu-sub-content"
|
|
221
|
+
className={s.subContent({ class: [slots.subContent, className] })}
|
|
222
|
+
{...props}
|
|
223
|
+
/>
|
|
224
|
+
</MenuPrimitive.Portal>
|
|
225
|
+
)
|
|
226
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export {
|
|
2
|
+
ContextMenu,
|
|
3
|
+
ContextMenuCheckboxItem,
|
|
4
|
+
ContextMenuContent,
|
|
5
|
+
ContextMenuGroup,
|
|
6
|
+
ContextMenuItem,
|
|
7
|
+
type ContextMenuItemProps,
|
|
8
|
+
ContextMenuLabel,
|
|
9
|
+
ContextMenuRadioGroup,
|
|
10
|
+
ContextMenuRadioItem,
|
|
11
|
+
ContextMenuSeparator,
|
|
12
|
+
ContextMenuShortcut,
|
|
13
|
+
ContextMenuSub,
|
|
14
|
+
ContextMenuSubContent,
|
|
15
|
+
ContextMenuSubTrigger,
|
|
16
|
+
type ContextMenuSubTriggerProps,
|
|
17
|
+
ContextMenuTrigger,
|
|
18
|
+
} from './context-menu'
|
|
19
|
+
export { type ContextMenuVariants, contextMenuStyles } from './context-menu.styles'
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { type ReactNode, useEffect, useRef, useState } from 'react'
|
|
2
|
+
import { useComponentConfig } from '../../hooks/use-component-config'
|
|
3
|
+
import type { StylesEntry } from '../../utils/registry'
|
|
4
|
+
import { tv } from '../../utils/tv'
|
|
5
|
+
import { Button, type ButtonProps } from '../button'
|
|
6
|
+
|
|
7
|
+
export const copyButtonStyles = tv({
|
|
8
|
+
slots: {
|
|
9
|
+
base: '',
|
|
10
|
+
icon: 'size-4',
|
|
11
|
+
status: 'sr-only',
|
|
12
|
+
},
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
declare module '@glyphui/tokens' {
|
|
16
|
+
interface GlyphComponentRegistry {
|
|
17
|
+
CopyButton: StylesEntry<typeof copyButtonStyles>
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const icon = {
|
|
22
|
+
xmlns: 'http://www.w3.org/2000/svg',
|
|
23
|
+
viewBox: '0 0 24 24',
|
|
24
|
+
fill: 'none',
|
|
25
|
+
stroke: 'currentColor',
|
|
26
|
+
strokeWidth: 2,
|
|
27
|
+
strokeLinecap: 'round',
|
|
28
|
+
strokeLinejoin: 'round',
|
|
29
|
+
} as const
|
|
30
|
+
|
|
31
|
+
const CopyIcon = ({ className }: { className?: string }) => (
|
|
32
|
+
<svg aria-hidden="true" {...icon} className={className}>
|
|
33
|
+
<rect width="13" height="13" x="9" y="9" rx="2" />
|
|
34
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
35
|
+
</svg>
|
|
36
|
+
)
|
|
37
|
+
const CheckIcon = ({ className }: { className?: string }) => (
|
|
38
|
+
<svg aria-hidden="true" {...icon} className={className}>
|
|
39
|
+
<path d="M20 6 9 17l-5-5" />
|
|
40
|
+
</svg>
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
export interface CopyButtonProps
|
|
44
|
+
extends Omit<
|
|
45
|
+
ButtonProps,
|
|
46
|
+
'onClick' | 'onCopy' | 'leftIcon' | 'rightIcon' | 'iconOnly' | 'asChild' | 'value'
|
|
47
|
+
> {
|
|
48
|
+
/** Text written to the clipboard. */
|
|
49
|
+
value: string
|
|
50
|
+
/** Visible label. Without it the button is icon-only, labelled "Copy". */
|
|
51
|
+
children?: ReactNode
|
|
52
|
+
/** Announced and shown after copying. Default "Copied". */
|
|
53
|
+
copiedLabel?: string
|
|
54
|
+
/** How long the copied state lasts, in ms. Default 1500. */
|
|
55
|
+
timeout?: number
|
|
56
|
+
onCopy?: (value: string) => void
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Copies a value to the clipboard and confirms it visually and to screen readers. */
|
|
60
|
+
export function CopyButton(inProps: CopyButtonProps) {
|
|
61
|
+
const [props, slots] = useComponentConfig('CopyButton', inProps)
|
|
62
|
+
const {
|
|
63
|
+
value,
|
|
64
|
+
children,
|
|
65
|
+
copiedLabel = 'Copied',
|
|
66
|
+
timeout = 1500,
|
|
67
|
+
onCopy,
|
|
68
|
+
variant = 'outline',
|
|
69
|
+
className,
|
|
70
|
+
'aria-label': ariaLabel,
|
|
71
|
+
...rest
|
|
72
|
+
} = props
|
|
73
|
+
const [copied, setCopied] = useState(false)
|
|
74
|
+
const timer = useRef<ReturnType<typeof setTimeout>>(undefined)
|
|
75
|
+
const s = copyButtonStyles()
|
|
76
|
+
|
|
77
|
+
useEffect(() => () => clearTimeout(timer.current), [])
|
|
78
|
+
|
|
79
|
+
const copy = async () => {
|
|
80
|
+
try {
|
|
81
|
+
await navigator.clipboard.writeText(value)
|
|
82
|
+
} catch {
|
|
83
|
+
// Clipboard blocked (permissions, insecure context): leave the button as it was.
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
setCopied(true)
|
|
87
|
+
onCopy?.(value)
|
|
88
|
+
clearTimeout(timer.current)
|
|
89
|
+
timer.current = setTimeout(() => setCopied(false), timeout)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const glyph = copied ? (
|
|
93
|
+
<CheckIcon className={s.icon({ class: slots.icon })} />
|
|
94
|
+
) : (
|
|
95
|
+
<CopyIcon className={s.icon({ class: slots.icon })} />
|
|
96
|
+
)
|
|
97
|
+
const iconOnly = children === undefined
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<Button
|
|
101
|
+
data-slot="copy-button"
|
|
102
|
+
data-copied={copied || undefined}
|
|
103
|
+
variant={variant}
|
|
104
|
+
iconOnly={iconOnly}
|
|
105
|
+
onClick={copy}
|
|
106
|
+
className={s.base({ class: [slots.base, className] })}
|
|
107
|
+
aria-label={iconOnly ? (copied ? copiedLabel : (ariaLabel ?? 'Copy')) : ariaLabel}
|
|
108
|
+
{...(!iconOnly && { leftIcon: glyph })}
|
|
109
|
+
{...rest}
|
|
110
|
+
>
|
|
111
|
+
{iconOnly ? glyph : copied ? copiedLabel : children}
|
|
112
|
+
<span role="status" className={s.status({ class: slots.status })}>
|
|
113
|
+
{copied ? copiedLabel : ''}
|
|
114
|
+
</span>
|
|
115
|
+
</Button>
|
|
116
|
+
)
|
|
117
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { CopyButton, type CopyButtonProps, copyButtonStyles } from './copy-button'
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { StylesEntry } from '../../utils/registry'
|
|
2
|
+
import { popover } from '../../utils/styles'
|
|
3
|
+
import { tv, type VariantProps } from '../../utils/tv'
|
|
4
|
+
import { fieldControl } from '../input/input.styles'
|
|
5
|
+
|
|
6
|
+
export const datePickerStyles = tv({
|
|
7
|
+
slots: {
|
|
8
|
+
trigger: [
|
|
9
|
+
...fieldControl,
|
|
10
|
+
'flex cursor-pointer items-center gap-2 px-3 text-left outline-none',
|
|
11
|
+
'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/20',
|
|
12
|
+
'disabled:cursor-not-allowed disabled:opacity-50',
|
|
13
|
+
],
|
|
14
|
+
icon: 'size-4 shrink-0 text-faint-foreground',
|
|
15
|
+
value: 'min-w-0 flex-1 truncate',
|
|
16
|
+
placeholder: 'min-w-0 flex-1 truncate text-faint-foreground',
|
|
17
|
+
clear:
|
|
18
|
+
'inline-flex size-5 shrink-0 cursor-pointer items-center justify-center rounded-xs text-faint-foreground hover:bg-muted hover:text-foreground [&>svg]:size-3.5',
|
|
19
|
+
content: [popover, 'w-auto p-0'],
|
|
20
|
+
layout: 'flex flex-col sm:flex-row',
|
|
21
|
+
presets:
|
|
22
|
+
'flex gap-1 overflow-x-auto border-b border-border p-2 sm:w-40 sm:flex-col sm:overflow-visible sm:border-r sm:border-b-0',
|
|
23
|
+
preset:
|
|
24
|
+
'shrink-0 cursor-pointer rounded-sm px-2 py-1.5 text-left text-small whitespace-nowrap text-muted-foreground hover:bg-muted hover:text-foreground data-[active=true]:bg-muted data-[active=true]:text-foreground',
|
|
25
|
+
},
|
|
26
|
+
variants: {
|
|
27
|
+
size: {
|
|
28
|
+
sm: { trigger: 'h-(--glyph-control-sm) text-small' },
|
|
29
|
+
md: { trigger: 'h-(--glyph-control-md) text-small' },
|
|
30
|
+
lg: { trigger: 'h-(--glyph-control-lg) text-body' },
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
defaultVariants: { size: 'md' },
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
export type DatePickerVariants = VariantProps<typeof datePickerStyles>
|
|
37
|
+
|
|
38
|
+
declare module '@glyphui/tokens' {
|
|
39
|
+
interface GlyphComponentRegistry {
|
|
40
|
+
DatePicker: StylesEntry<typeof datePickerStyles>
|
|
41
|
+
}
|
|
42
|
+
}
|