@glyphui/ui 0.0.0-stage → 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/LICENSE +21 -0
- package/README.md +137 -2
- package/dist/data-table.d.ts +110 -0
- package/dist/data-table.js +225 -0
- package/dist/field-DXZD0l8w.js +107 -0
- package/dist/field-context-B8cYheXz.js +321 -0
- package/dist/form.d.ts +79 -0
- package/dist/form.js +84 -0
- package/dist/index-BWK4czIG.d.ts +233 -0
- package/dist/index.d.ts +2155 -0
- package/dist/index.js +1529 -0
- package/dist/registry-BGSJoiLq.d.ts +9 -0
- package/dist/states-BxPjCUxz.js +287 -0
- package/dist/tv-DuMh7UH2.d.ts +6 -0
- package/package.json +113 -3
- package/src/components/avatar/avatar.styles.ts +22 -0
- package/src/components/avatar/avatar.tsx +62 -0
- package/src/components/avatar/index.ts +2 -0
- package/src/components/badge/badge.styles.ts +69 -0
- package/src/components/badge/badge.tsx +30 -0
- package/src/components/badge/index.ts +2 -0
- package/src/components/button/button.styles.ts +43 -0
- package/src/components/button/button.tsx +77 -0
- package/src/components/button/index.ts +2 -0
- package/src/components/card/card.styles.ts +32 -0
- package/src/components/card/card.tsx +91 -0
- package/src/components/card/index.ts +11 -0
- package/src/components/checkbox/checkbox.tsx +39 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/container/container.tsx +35 -0
- package/src/components/container/index.ts +1 -0
- package/src/components/eyebrow/eyebrow.styles.ts +14 -0
- package/src/components/eyebrow/eyebrow.tsx +19 -0
- package/src/components/eyebrow/index.ts +2 -0
- package/src/components/field/field-context.ts +40 -0
- package/src/components/field/field.styles.ts +23 -0
- package/src/components/field/field.tsx +109 -0
- package/src/components/field/index.ts +3 -0
- package/src/components/heading/heading.styles.ts +16 -0
- package/src/components/heading/heading.tsx +26 -0
- package/src/components/heading/index.ts +2 -0
- package/src/components/input/index.ts +2 -0
- package/src/components/input/input.styles.ts +36 -0
- package/src/components/input/input.tsx +33 -0
- package/src/components/kbd/index.ts +1 -0
- package/src/components/kbd/kbd.tsx +21 -0
- package/src/components/label/index.ts +1 -0
- package/src/components/label/label.tsx +21 -0
- package/src/components/section/index.ts +1 -0
- package/src/components/section/section.tsx +39 -0
- package/src/components/segmented-control/index.ts +6 -0
- package/src/components/segmented-control/segmented-control.tsx +79 -0
- package/src/components/select/index.ts +1 -0
- package/src/components/select/select.tsx +133 -0
- package/src/components/separator/index.ts +1 -0
- package/src/components/separator/separator.tsx +29 -0
- package/src/components/skeleton/index.ts +1 -0
- package/src/components/skeleton/skeleton.tsx +20 -0
- package/src/components/spinner/index.ts +2 -0
- package/src/components/spinner/spinner.styles.ts +19 -0
- package/src/components/spinner/spinner.tsx +42 -0
- package/src/components/stack/index.ts +1 -0
- package/src/components/stack/stack.tsx +62 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch/switch.tsx +48 -0
- package/src/components/table/index.ts +10 -0
- package/src/components/table/table.tsx +90 -0
- package/src/components/tabs/index.ts +1 -0
- package/src/components/tabs/tabs.tsx +92 -0
- package/src/components/text/index.ts +2 -0
- package/src/components/text/text.styles.ts +32 -0
- package/src/components/text/text.tsx +17 -0
- package/src/components/textarea/index.ts +1 -0
- package/src/components/textarea/textarea.tsx +27 -0
- package/src/components/tooltip/index.ts +1 -0
- package/src/components/tooltip/tooltip.tsx +42 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/use-component-config.ts +42 -0
- package/src/hooks/use-controllable-state.ts +27 -0
- package/src/hooks/use-media-query.ts +14 -0
- package/src/hooks/use-scrolled.ts +14 -0
- package/src/index.ts +52 -0
- package/src/internal/icons.tsx +99 -0
- package/src/patterns/app-shell/app-shell-context.ts +27 -0
- package/src/patterns/app-shell/app-shell.styles.ts +127 -0
- package/src/patterns/app-shell/app-shell.tsx +81 -0
- package/src/patterns/app-shell/index.ts +16 -0
- package/src/patterns/app-shell/navbar.tsx +119 -0
- package/src/patterns/app-shell/page-header.tsx +30 -0
- package/src/patterns/app-shell/sidebar.tsx +163 -0
- package/src/patterns/async/async.tsx +83 -0
- package/src/patterns/async/index.ts +10 -0
- package/src/patterns/async/states.tsx +128 -0
- package/src/patterns/data-table/data-table.tsx +367 -0
- package/src/patterns/data-table/index.ts +2 -0
- package/src/patterns/form/form.tsx +150 -0
- package/src/patterns/form/index.ts +13 -0
- package/src/provider/color-mode.tsx +133 -0
- package/src/provider/glyph-context.ts +42 -0
- package/src/provider/glyph-provider.tsx +41 -0
- package/src/provider/index.ts +18 -0
- package/src/provider/theme-scope.tsx +36 -0
- package/src/provider/theme-style.tsx +19 -0
- package/src/register.ts +69 -0
- package/src/themes.ts +56 -0
- package/src/utils/cn.ts +12 -0
- package/src/utils/compose-refs.ts +11 -0
- package/src/utils/registry.ts +9 -0
- package/src/utils/styles.ts +13 -0
- package/src/utils/tv.ts +7 -0
- package/src/utils/tw-merge-config.ts +14 -0
- package/styles.css +178 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Label as LabelPrimitive } from 'radix-ui'
|
|
2
|
+
import type { ComponentProps } from 'react'
|
|
3
|
+
import { useComponentConfig } from '../../hooks/use-component-config'
|
|
4
|
+
import { tv } from '../../utils/tv'
|
|
5
|
+
|
|
6
|
+
export const labelStyles = tv({
|
|
7
|
+
slots: {
|
|
8
|
+
base: 'inline-flex select-none items-center gap-1 text-small font-medium text-foreground peer-disabled:cursor-not-allowed peer-disabled:opacity-60',
|
|
9
|
+
},
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export function Label(inProps: ComponentProps<typeof LabelPrimitive.Root>) {
|
|
13
|
+
const [{ className, ...rest }, slots] = useComponentConfig('Label', inProps)
|
|
14
|
+
return (
|
|
15
|
+
<LabelPrimitive.Root
|
|
16
|
+
data-slot="label"
|
|
17
|
+
className={labelStyles().base({ class: [slots.base, className] })}
|
|
18
|
+
{...rest}
|
|
19
|
+
/>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Section, type SectionProps, sectionStyles } from './section'
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Slot } from 'radix-ui'
|
|
2
|
+
import type { ComponentProps } from 'react'
|
|
3
|
+
import { tv, type VariantProps } from '../../utils/tv'
|
|
4
|
+
|
|
5
|
+
export const sectionStyles = tv({
|
|
6
|
+
base: 'w-full',
|
|
7
|
+
variants: {
|
|
8
|
+
spacing: {
|
|
9
|
+
none: 'py-0',
|
|
10
|
+
sm: 'py-10 sm:py-12',
|
|
11
|
+
md: 'py-16 sm:py-20',
|
|
12
|
+
lg: 'py-20 sm:py-28',
|
|
13
|
+
},
|
|
14
|
+
tone: {
|
|
15
|
+
default: '',
|
|
16
|
+
subtle: 'bg-subtle',
|
|
17
|
+
},
|
|
18
|
+
divided: { true: 'border-t-(length:--glyph-border-width) border-border' },
|
|
19
|
+
},
|
|
20
|
+
defaultVariants: { spacing: 'md', tone: 'default' },
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export interface SectionProps
|
|
24
|
+
extends ComponentProps<'section'>,
|
|
25
|
+
VariantProps<typeof sectionStyles> {
|
|
26
|
+
asChild?: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** A full-width page band with consistent vertical rhythm. Put a Container inside. */
|
|
30
|
+
export function Section({ spacing, tone, divided, asChild, className, ...props }: SectionProps) {
|
|
31
|
+
const Comp = asChild ? Slot.Root : 'section'
|
|
32
|
+
return (
|
|
33
|
+
<Comp
|
|
34
|
+
data-slot="section"
|
|
35
|
+
className={sectionStyles({ spacing, tone, divided, class: className })}
|
|
36
|
+
{...props}
|
|
37
|
+
/>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { ToggleGroup } from 'radix-ui'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import { useComponentConfig } from '../../hooks/use-component-config'
|
|
4
|
+
import { focusRing, themedBorder, transition } from '../../utils/styles'
|
|
5
|
+
import { tv, type VariantProps } from '../../utils/tv'
|
|
6
|
+
|
|
7
|
+
export const segmentedControlStyles = tv({
|
|
8
|
+
slots: {
|
|
9
|
+
root: [
|
|
10
|
+
'inline-flex items-center gap-0.5 rounded-md border-border bg-muted p-0.5',
|
|
11
|
+
themedBorder,
|
|
12
|
+
],
|
|
13
|
+
item: [
|
|
14
|
+
'inline-flex h-full cursor-pointer select-none items-center justify-center gap-1.5 whitespace-nowrap rounded-[calc(var(--radius-md)-2px)] px-3 font-medium text-muted-foreground',
|
|
15
|
+
transition,
|
|
16
|
+
focusRing,
|
|
17
|
+
'hover:text-foreground data-[state=on]:bg-raised data-[state=on]:text-foreground data-[state=on]:shadow-xs',
|
|
18
|
+
'disabled:pointer-events-none disabled:opacity-50 [&>svg]:size-4',
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
variants: {
|
|
22
|
+
size: {
|
|
23
|
+
sm: { root: 'h-(--glyph-control-sm)', item: 'px-2.5 text-caption' },
|
|
24
|
+
md: { root: 'h-(--glyph-control-md)', item: 'text-small' },
|
|
25
|
+
},
|
|
26
|
+
fullWidth: { true: { root: 'flex w-full', item: 'flex-1' } },
|
|
27
|
+
},
|
|
28
|
+
defaultVariants: { size: 'md' },
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
export interface SegmentedOption<T extends string> {
|
|
32
|
+
value: T
|
|
33
|
+
label: ReactNode
|
|
34
|
+
disabled?: boolean
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface SegmentedControlProps<T extends string>
|
|
38
|
+
extends VariantProps<typeof segmentedControlStyles> {
|
|
39
|
+
options: readonly SegmentedOption<T>[]
|
|
40
|
+
value?: T
|
|
41
|
+
defaultValue?: T
|
|
42
|
+
onValueChange?: (value: T) => void
|
|
43
|
+
'aria-label': string
|
|
44
|
+
className?: string
|
|
45
|
+
id?: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Pick exactly one of a few options. `T` is inferred from `options`, so `onValueChange`
|
|
50
|
+
* receives your union type, not `string`.
|
|
51
|
+
*/
|
|
52
|
+
export function SegmentedControl<T extends string>(inProps: SegmentedControlProps<T>) {
|
|
53
|
+
const [props, slots] = useComponentConfig('SegmentedControl', inProps)
|
|
54
|
+
const { options, value, defaultValue, onValueChange, size, fullWidth, className, ...rest } = props
|
|
55
|
+
const s = segmentedControlStyles({ size, fullWidth })
|
|
56
|
+
return (
|
|
57
|
+
<ToggleGroup.Root
|
|
58
|
+
type="single"
|
|
59
|
+
data-slot="segmented-control"
|
|
60
|
+
{...(value !== undefined && { value })}
|
|
61
|
+
{...(defaultValue !== undefined && { defaultValue })}
|
|
62
|
+
// Radix emits '' when the active item is clicked again; a segmented control never deselects.
|
|
63
|
+
onValueChange={(next: string) => next && onValueChange?.(next as T)}
|
|
64
|
+
className={s.root({ class: [slots.root, className] })}
|
|
65
|
+
{...rest}
|
|
66
|
+
>
|
|
67
|
+
{options.map((option) => (
|
|
68
|
+
<ToggleGroup.Item
|
|
69
|
+
key={option.value}
|
|
70
|
+
value={option.value}
|
|
71
|
+
disabled={option.disabled}
|
|
72
|
+
className={s.item({ class: slots.item })}
|
|
73
|
+
>
|
|
74
|
+
{option.label}
|
|
75
|
+
</ToggleGroup.Item>
|
|
76
|
+
))}
|
|
77
|
+
</ToggleGroup.Root>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Select, SelectItem, type SelectItemProps, type SelectProps, selectStyles } from './select'
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Select as SelectPrimitive } from 'radix-ui'
|
|
2
|
+
import type { ComponentProps, ReactNode } from 'react'
|
|
3
|
+
import { useComponentConfig } from '../../hooks/use-component-config'
|
|
4
|
+
import { CheckIcon, ChevronDownIcon } from '../../internal/icons'
|
|
5
|
+
import { popover } from '../../utils/styles'
|
|
6
|
+
import { tv, type VariantProps } from '../../utils/tv'
|
|
7
|
+
import { useFieldControl } from '../field/field-context'
|
|
8
|
+
import { fieldControl } from '../input/input.styles'
|
|
9
|
+
|
|
10
|
+
export const selectStyles = tv({
|
|
11
|
+
slots: {
|
|
12
|
+
trigger: [
|
|
13
|
+
...fieldControl,
|
|
14
|
+
'flex cursor-pointer items-center justify-between gap-2 px-3 text-left outline-none',
|
|
15
|
+
'focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/20',
|
|
16
|
+
'data-placeholder:text-faint-foreground disabled:cursor-not-allowed disabled:opacity-50',
|
|
17
|
+
'[&>span]:truncate',
|
|
18
|
+
],
|
|
19
|
+
icon: 'size-4 shrink-0 text-faint-foreground',
|
|
20
|
+
content: [
|
|
21
|
+
popover,
|
|
22
|
+
'max-h-(--radix-select-content-available-height) min-w-(--radix-select-trigger-width)',
|
|
23
|
+
],
|
|
24
|
+
viewport: 'p-1',
|
|
25
|
+
item: [
|
|
26
|
+
'relative flex h-8 cursor-pointer select-none items-center gap-2 rounded-sm pr-8 pl-2 text-small outline-none',
|
|
27
|
+
'data-highlighted:bg-muted data-disabled:pointer-events-none data-disabled:opacity-50',
|
|
28
|
+
],
|
|
29
|
+
indicator: 'absolute right-2 flex items-center text-primary [&>svg]:size-4',
|
|
30
|
+
},
|
|
31
|
+
variants: {
|
|
32
|
+
size: {
|
|
33
|
+
sm: { trigger: 'h-(--glyph-control-sm) text-small' },
|
|
34
|
+
md: { trigger: 'h-(--glyph-control-md) text-small' },
|
|
35
|
+
lg: { trigger: 'h-(--glyph-control-lg) text-body' },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
defaultVariants: { size: 'md' },
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
type RootProps<T extends string> = Omit<
|
|
42
|
+
ComponentProps<typeof SelectPrimitive.Root>,
|
|
43
|
+
'value' | 'defaultValue' | 'onValueChange'
|
|
44
|
+
> & {
|
|
45
|
+
value?: T
|
|
46
|
+
defaultValue?: T
|
|
47
|
+
onValueChange?: (value: T) => void
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface SelectProps<T extends string = string>
|
|
51
|
+
extends RootProps<T>,
|
|
52
|
+
VariantProps<typeof selectStyles> {
|
|
53
|
+
placeholder?: ReactNode
|
|
54
|
+
id?: string
|
|
55
|
+
className?: string
|
|
56
|
+
contentClassName?: string
|
|
57
|
+
'aria-label'?: string
|
|
58
|
+
'aria-describedby'?: string
|
|
59
|
+
'aria-invalid'?: boolean
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* A dropdown select. `value` is typed by the `T` you use:
|
|
64
|
+
* `<Select<Plan> value={plan} onValueChange={setPlan}>` only accepts `Plan` values.
|
|
65
|
+
*/
|
|
66
|
+
export function Select<T extends string = string>(inProps: SelectProps<T>) {
|
|
67
|
+
const [props, slots] = useComponentConfig('Select', useFieldControl(inProps))
|
|
68
|
+
const {
|
|
69
|
+
size,
|
|
70
|
+
placeholder,
|
|
71
|
+
id,
|
|
72
|
+
className,
|
|
73
|
+
contentClassName,
|
|
74
|
+
children,
|
|
75
|
+
onValueChange,
|
|
76
|
+
'aria-label': ariaLabel,
|
|
77
|
+
'aria-describedby': ariaDescribedBy,
|
|
78
|
+
'aria-invalid': ariaInvalid,
|
|
79
|
+
...rootProps
|
|
80
|
+
} = props
|
|
81
|
+
const s = selectStyles({ size })
|
|
82
|
+
return (
|
|
83
|
+
<SelectPrimitive.Root
|
|
84
|
+
{...rootProps}
|
|
85
|
+
{...(onValueChange && { onValueChange: (v: string) => onValueChange(v as T) })}
|
|
86
|
+
>
|
|
87
|
+
<SelectPrimitive.Trigger
|
|
88
|
+
id={id}
|
|
89
|
+
data-slot="select-trigger"
|
|
90
|
+
aria-label={ariaLabel}
|
|
91
|
+
aria-describedby={ariaDescribedBy}
|
|
92
|
+
aria-invalid={ariaInvalid}
|
|
93
|
+
className={s.trigger({ class: [slots.trigger, className] })}
|
|
94
|
+
>
|
|
95
|
+
<SelectPrimitive.Value placeholder={placeholder} />
|
|
96
|
+
<SelectPrimitive.Icon asChild>
|
|
97
|
+
<ChevronDownIcon className={s.icon({ class: slots.icon })} />
|
|
98
|
+
</SelectPrimitive.Icon>
|
|
99
|
+
</SelectPrimitive.Trigger>
|
|
100
|
+
<SelectPrimitive.Portal>
|
|
101
|
+
<SelectPrimitive.Content
|
|
102
|
+
position="popper"
|
|
103
|
+
sideOffset={4}
|
|
104
|
+
data-slot="select-content"
|
|
105
|
+
className={s.content({ class: [slots.content, contentClassName] })}
|
|
106
|
+
>
|
|
107
|
+
<SelectPrimitive.Viewport className={s.viewport({ class: slots.viewport })}>
|
|
108
|
+
{children}
|
|
109
|
+
</SelectPrimitive.Viewport>
|
|
110
|
+
</SelectPrimitive.Content>
|
|
111
|
+
</SelectPrimitive.Portal>
|
|
112
|
+
</SelectPrimitive.Root>
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export interface SelectItemProps extends ComponentProps<typeof SelectPrimitive.Item> {}
|
|
117
|
+
|
|
118
|
+
export function SelectItem({ className, children, ...props }: SelectItemProps) {
|
|
119
|
+
const [, slots] = useComponentConfig('Select', {})
|
|
120
|
+
const s = selectStyles()
|
|
121
|
+
return (
|
|
122
|
+
<SelectPrimitive.Item
|
|
123
|
+
data-slot="select-item"
|
|
124
|
+
className={s.item({ class: [slots.item, className] })}
|
|
125
|
+
{...props}
|
|
126
|
+
>
|
|
127
|
+
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
128
|
+
<SelectPrimitive.ItemIndicator className={s.indicator({ class: slots.indicator })}>
|
|
129
|
+
<CheckIcon />
|
|
130
|
+
</SelectPrimitive.ItemIndicator>
|
|
131
|
+
</SelectPrimitive.Item>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Separator, separatorStyles } from './separator'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Separator as SeparatorPrimitive } from 'radix-ui'
|
|
2
|
+
import type { ComponentProps } from 'react'
|
|
3
|
+
import { useComponentConfig } from '../../hooks/use-component-config'
|
|
4
|
+
import { tv } from '../../utils/tv'
|
|
5
|
+
|
|
6
|
+
export const separatorStyles = tv({
|
|
7
|
+
slots: { base: 'shrink-0 bg-border' },
|
|
8
|
+
variants: {
|
|
9
|
+
orientation: {
|
|
10
|
+
horizontal: { base: 'h-px w-full' },
|
|
11
|
+
vertical: { base: 'h-full w-px self-stretch' },
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
defaultVariants: { orientation: 'horizontal' },
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
export function Separator(inProps: ComponentProps<typeof SeparatorPrimitive.Root>) {
|
|
18
|
+
const [props, slots] = useComponentConfig('Separator', inProps)
|
|
19
|
+
const { orientation = 'horizontal', decorative = true, className, ...rest } = props
|
|
20
|
+
return (
|
|
21
|
+
<SeparatorPrimitive.Root
|
|
22
|
+
data-slot="separator"
|
|
23
|
+
orientation={orientation}
|
|
24
|
+
decorative={decorative}
|
|
25
|
+
className={separatorStyles({ orientation }).base({ class: [slots.base, className] })}
|
|
26
|
+
{...rest}
|
|
27
|
+
/>
|
|
28
|
+
)
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Skeleton, skeletonStyles } from './skeleton'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react'
|
|
2
|
+
import { useComponentConfig } from '../../hooks/use-component-config'
|
|
3
|
+
import { tv } from '../../utils/tv'
|
|
4
|
+
|
|
5
|
+
export const skeletonStyles = tv({
|
|
6
|
+
slots: { base: 'animate-pulse rounded-sm bg-muted' },
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
/** Placeholder block shown while content loads. Size it with classes: `h-4 w-32`. */
|
|
10
|
+
export function Skeleton(inProps: ComponentProps<'div'>) {
|
|
11
|
+
const [{ className, ...rest }, slots] = useComponentConfig('Skeleton', inProps)
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
data-slot="skeleton"
|
|
15
|
+
aria-hidden="true"
|
|
16
|
+
className={skeletonStyles().base({ class: [slots.base, className] })}
|
|
17
|
+
{...rest}
|
|
18
|
+
/>
|
|
19
|
+
)
|
|
20
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { tv, type VariantProps } from '../../utils/tv'
|
|
2
|
+
|
|
3
|
+
export const spinnerStyles = tv({
|
|
4
|
+
slots: {
|
|
5
|
+
base: 'inline-block shrink-0 animate-spin',
|
|
6
|
+
track: 'opacity-20',
|
|
7
|
+
arc: '',
|
|
8
|
+
},
|
|
9
|
+
variants: {
|
|
10
|
+
size: {
|
|
11
|
+
sm: { base: 'size-3.5' },
|
|
12
|
+
md: { base: 'size-4' },
|
|
13
|
+
lg: { base: 'size-6' },
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
defaultVariants: { size: 'md' },
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
export type SpinnerVariants = VariantProps<typeof spinnerStyles>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react'
|
|
2
|
+
import { useComponentConfig } from '../../hooks/use-component-config'
|
|
3
|
+
import { type SpinnerVariants, spinnerStyles } from './spinner.styles'
|
|
4
|
+
|
|
5
|
+
export interface SpinnerProps extends Omit<ComponentProps<'svg'>, 'children'>, SpinnerVariants {
|
|
6
|
+
/** Accessible label. Omit when the spinner sits inside an element that already says it's busy. */
|
|
7
|
+
label?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function Spinner(inProps: SpinnerProps) {
|
|
11
|
+
const [props, slots] = useComponentConfig('Spinner', inProps)
|
|
12
|
+
const { size, label, className, ...rest } = props
|
|
13
|
+
const s = spinnerStyles({ size })
|
|
14
|
+
return (
|
|
15
|
+
<svg
|
|
16
|
+
data-slot="spinner"
|
|
17
|
+
viewBox="0 0 24 24"
|
|
18
|
+
fill="none"
|
|
19
|
+
role={label ? 'status' : undefined}
|
|
20
|
+
aria-label={label}
|
|
21
|
+
aria-hidden={label ? undefined : true}
|
|
22
|
+
className={s.base({ class: [slots.base, className] })}
|
|
23
|
+
{...rest}
|
|
24
|
+
>
|
|
25
|
+
<circle
|
|
26
|
+
className={s.track({ class: slots.track })}
|
|
27
|
+
cx="12"
|
|
28
|
+
cy="12"
|
|
29
|
+
r="9"
|
|
30
|
+
stroke="currentColor"
|
|
31
|
+
strokeWidth="3"
|
|
32
|
+
/>
|
|
33
|
+
<path
|
|
34
|
+
className={s.arc({ class: slots.arc })}
|
|
35
|
+
d="M21 12a9 9 0 0 0-9-9"
|
|
36
|
+
stroke="currentColor"
|
|
37
|
+
strokeWidth="3"
|
|
38
|
+
strokeLinecap="round"
|
|
39
|
+
/>
|
|
40
|
+
</svg>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Stack, type StackProps, stackStyles } from './stack'
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Slot } from 'radix-ui'
|
|
2
|
+
import type { ComponentProps } from 'react'
|
|
3
|
+
import { tv, type VariantProps } from '../../utils/tv'
|
|
4
|
+
|
|
5
|
+
export const stackStyles = tv({
|
|
6
|
+
base: 'flex',
|
|
7
|
+
variants: {
|
|
8
|
+
direction: { column: 'flex-col', row: 'flex-row' },
|
|
9
|
+
gap: {
|
|
10
|
+
0: 'gap-0',
|
|
11
|
+
1: 'gap-1',
|
|
12
|
+
2: 'gap-2',
|
|
13
|
+
3: 'gap-3',
|
|
14
|
+
4: 'gap-4',
|
|
15
|
+
5: 'gap-5',
|
|
16
|
+
6: 'gap-6',
|
|
17
|
+
8: 'gap-8',
|
|
18
|
+
10: 'gap-10',
|
|
19
|
+
12: 'gap-12',
|
|
20
|
+
},
|
|
21
|
+
align: {
|
|
22
|
+
start: 'items-start',
|
|
23
|
+
center: 'items-center',
|
|
24
|
+
end: 'items-end',
|
|
25
|
+
stretch: 'items-stretch',
|
|
26
|
+
baseline: 'items-baseline',
|
|
27
|
+
},
|
|
28
|
+
justify: {
|
|
29
|
+
start: 'justify-start',
|
|
30
|
+
center: 'justify-center',
|
|
31
|
+
end: 'justify-end',
|
|
32
|
+
between: 'justify-between',
|
|
33
|
+
},
|
|
34
|
+
wrap: { true: 'flex-wrap' },
|
|
35
|
+
},
|
|
36
|
+
defaultVariants: { direction: 'column', gap: 4 },
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
export interface StackProps extends ComponentProps<'div'>, VariantProps<typeof stackStyles> {
|
|
40
|
+
asChild?: boolean
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Flex layout with spacing from the scale. `direction="row"` for horizontal. */
|
|
44
|
+
export function Stack({
|
|
45
|
+
direction,
|
|
46
|
+
gap,
|
|
47
|
+
align,
|
|
48
|
+
justify,
|
|
49
|
+
wrap,
|
|
50
|
+
asChild,
|
|
51
|
+
className,
|
|
52
|
+
...props
|
|
53
|
+
}: StackProps) {
|
|
54
|
+
const Comp = asChild ? Slot.Root : 'div'
|
|
55
|
+
return (
|
|
56
|
+
<Comp
|
|
57
|
+
data-slot="stack"
|
|
58
|
+
className={stackStyles({ direction, gap, align, justify, wrap, class: className })}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Switch, type SwitchProps, switchStyles } from './switch'
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Switch as SwitchPrimitive } from 'radix-ui'
|
|
2
|
+
import type { ComponentProps } from 'react'
|
|
3
|
+
import { useComponentConfig } from '../../hooks/use-component-config'
|
|
4
|
+
import { focusRing, themedBorder, transition } from '../../utils/styles'
|
|
5
|
+
import { tv, type VariantProps } from '../../utils/tv'
|
|
6
|
+
import { useFieldControl } from '../field/field-context'
|
|
7
|
+
|
|
8
|
+
export const switchStyles = tv({
|
|
9
|
+
slots: {
|
|
10
|
+
root: [
|
|
11
|
+
'peer inline-flex shrink-0 cursor-pointer items-center rounded-pill border-transparent bg-input p-px',
|
|
12
|
+
themedBorder,
|
|
13
|
+
transition,
|
|
14
|
+
focusRing,
|
|
15
|
+
'data-[state=checked]:bg-primary disabled:cursor-not-allowed disabled:opacity-50',
|
|
16
|
+
],
|
|
17
|
+
thumb:
|
|
18
|
+
'pointer-events-none block rounded-pill bg-white shadow-sm transition-transform duration-(--glyph-duration-base) ease-emphasized data-[state=unchecked]:translate-x-0',
|
|
19
|
+
},
|
|
20
|
+
variants: {
|
|
21
|
+
size: {
|
|
22
|
+
sm: { root: 'h-4 w-7', thumb: 'size-3 data-[state=checked]:translate-x-3' },
|
|
23
|
+
md: { root: 'h-5 w-9', thumb: 'size-4 data-[state=checked]:translate-x-4' },
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
defaultVariants: { size: 'md' },
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
export interface SwitchProps
|
|
30
|
+
extends ComponentProps<typeof SwitchPrimitive.Root>,
|
|
31
|
+
VariantProps<typeof switchStyles> {}
|
|
32
|
+
|
|
33
|
+
export function Switch(inProps: SwitchProps) {
|
|
34
|
+
const [{ size, className, ...rest }, slots] = useComponentConfig(
|
|
35
|
+
'Switch',
|
|
36
|
+
useFieldControl(inProps),
|
|
37
|
+
)
|
|
38
|
+
const s = switchStyles({ size })
|
|
39
|
+
return (
|
|
40
|
+
<SwitchPrimitive.Root
|
|
41
|
+
data-slot="switch"
|
|
42
|
+
className={s.root({ class: [slots.root, className] })}
|
|
43
|
+
{...rest}
|
|
44
|
+
>
|
|
45
|
+
<SwitchPrimitive.Thumb className={s.thumb({ class: slots.thumb })} />
|
|
46
|
+
</SwitchPrimitive.Root>
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { ComponentProps } from 'react'
|
|
2
|
+
import { useComponentConfig } from '../../hooks/use-component-config'
|
|
3
|
+
import { tv } from '../../utils/tv'
|
|
4
|
+
|
|
5
|
+
export const tableStyles = tv({
|
|
6
|
+
slots: {
|
|
7
|
+
wrapper: 'relative w-full overflow-x-auto',
|
|
8
|
+
table: 'w-full caption-bottom border-collapse text-small tabular-nums',
|
|
9
|
+
header: '[&_tr]:border-b [&_tr]:border-border',
|
|
10
|
+
body: '[&_tr:last-child]:border-0',
|
|
11
|
+
row: 'border-b border-border transition-colors duration-(--glyph-duration-fast) hover:bg-muted/50 data-[state=selected]:bg-primary/6',
|
|
12
|
+
head: 'h-10 whitespace-nowrap px-3 first:pl-4 last:pr-4 text-left align-middle text-caption font-medium text-muted-foreground [&:has([role=checkbox])]:w-10 [&:has([role=checkbox])]:pr-0',
|
|
13
|
+
cell: 'px-3 py-2.5 align-middle first:pl-4 last:pr-4 [&:has([role=checkbox])]:pr-0',
|
|
14
|
+
caption: 'mt-3 text-small text-faint-foreground',
|
|
15
|
+
},
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
const s = tableStyles()
|
|
19
|
+
|
|
20
|
+
function useSlots() {
|
|
21
|
+
return useComponentConfig('Table', {})[1]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Semantic table primitives styled by the theme. For sorting and pagination use DataTable. */
|
|
25
|
+
export function Table({ className, ...props }: ComponentProps<'table'>) {
|
|
26
|
+
const slots = useSlots()
|
|
27
|
+
return (
|
|
28
|
+
<div data-slot="table-wrapper" className={s.wrapper({ class: slots.wrapper })}>
|
|
29
|
+
<table
|
|
30
|
+
data-slot="table"
|
|
31
|
+
className={s.table({ class: [slots.table, className] })}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
</div>
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function TableHeader({ className, ...props }: ComponentProps<'thead'>) {
|
|
39
|
+
const slots = useSlots()
|
|
40
|
+
return (
|
|
41
|
+
<thead
|
|
42
|
+
data-slot="table-header"
|
|
43
|
+
className={s.header({ class: [slots.header, className] })}
|
|
44
|
+
{...props}
|
|
45
|
+
/>
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function TableBody({ className, ...props }: ComponentProps<'tbody'>) {
|
|
50
|
+
const slots = useSlots()
|
|
51
|
+
return (
|
|
52
|
+
<tbody
|
|
53
|
+
data-slot="table-body"
|
|
54
|
+
className={s.body({ class: [slots.body, className] })}
|
|
55
|
+
{...props}
|
|
56
|
+
/>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function TableRow({ className, ...props }: ComponentProps<'tr'>) {
|
|
61
|
+
const slots = useSlots()
|
|
62
|
+
return (
|
|
63
|
+
<tr data-slot="table-row" className={s.row({ class: [slots.row, className] })} {...props} />
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function TableHead({ className, ...props }: ComponentProps<'th'>) {
|
|
68
|
+
const slots = useSlots()
|
|
69
|
+
return (
|
|
70
|
+
<th data-slot="table-head" className={s.head({ class: [slots.head, className] })} {...props} />
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function TableCell({ className, ...props }: ComponentProps<'td'>) {
|
|
75
|
+
const slots = useSlots()
|
|
76
|
+
return (
|
|
77
|
+
<td data-slot="table-cell" className={s.cell({ class: [slots.cell, className] })} {...props} />
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function TableCaption({ className, ...props }: ComponentProps<'caption'>) {
|
|
82
|
+
const slots = useSlots()
|
|
83
|
+
return (
|
|
84
|
+
<caption
|
|
85
|
+
data-slot="table-caption"
|
|
86
|
+
className={s.caption({ class: [slots.caption, className] })}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
)
|
|
90
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { Tabs, TabsContent, TabsList, type TabsProps, TabsTrigger, tabsStyles } from './tabs'
|