@pantoia/ui 0.2.0 → 0.4.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/index.module.css +4 -4
- package/accordion/index.tsx +30 -58
- package/alert-dialog/index.tsx +38 -44
- package/badge/index.tsx +2 -2
- package/button/index.tsx +31 -30
- package/callout/index.tsx +29 -8
- package/checkbox/index.tsx +24 -88
- package/combobox/index.tsx +133 -323
- package/command-palette/index.tsx +55 -34
- package/dialog/index.tsx +30 -26
- package/dist/index.js +155 -27
- package/drawer/index.module.css +2 -2
- package/drawer/index.tsx +54 -132
- package/dropdown-menu/index.tsx +95 -100
- package/field/index.tsx +43 -94
- package/input/index.tsx +13 -9
- package/kbd/index.tsx +2 -2
- package/label/index.tsx +7 -9
- package/number-field/index.tsx +129 -176
- package/package.json +12 -15
- package/palette.css +114 -0
- package/popover/index.tsx +62 -33
- package/radio-group/index.tsx +36 -39
- package/registry.json +65 -169
- package/select/index.tsx +154 -76
- package/switch/index.tsx +13 -6
- package/table/index.tsx +5 -6
- package/tabs/index.module.css +5 -3
- package/tabs/index.tsx +26 -77
- package/tailwind.css +3 -0
- package/textarea/index.tsx +25 -13
- package/toast/index.module.css +187 -0
- package/toast/index.tsx +141 -46
- package/toggle/index.tsx +8 -7
- package/{styles.css → tokens.css} +82 -303
- package/tooltip/index.tsx +62 -31
- package/tv.ts +0 -1
- package/utilities.css +124 -0
- package/accordion/AGENTS.md +0 -60
- package/alert-dialog/AGENTS.md +0 -78
- package/badge/AGENTS.md +0 -35
- package/button/AGENTS.md +0 -39
- package/callout/AGENTS.md +0 -41
- package/checkbox/AGENTS.md +0 -54
- package/combobox/AGENTS.md +0 -150
- package/command-palette/AGENTS.md +0 -45
- package/dialog/AGENTS.md +0 -81
- package/drawer/AGENTS.md +0 -66
- package/dropdown-menu/AGENTS.md +0 -133
- package/field/AGENTS.md +0 -96
- package/input/AGENTS.md +0 -35
- package/kbd/AGENTS.md +0 -17
- package/label/AGENTS.md +0 -21
- package/number-field/AGENTS.md +0 -53
- package/popover/AGENTS.md +0 -64
- package/press.css +0 -37
- package/radio-group/AGENTS.md +0 -81
- package/select/AGENTS.md +0 -90
- package/switch/AGENTS.md +0 -37
- package/table/AGENTS.md +0 -82
- package/tabs/AGENTS.md +0 -60
- package/test-utils.ts +0 -10
- package/textarea/AGENTS.md +0 -35
- package/toast/AGENTS.md +0 -34
- package/toggle/AGENTS.md +0 -37
- package/tooltip/AGENTS.md +0 -50
package/select/index.tsx
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { Select as SelectPrimitive } from '@base-ui/react/select'
|
|
4
|
+
import { Children, isValidElement, useEffect, useRef, useState } from 'react'
|
|
5
5
|
import type { ComponentProps, ReactNode } from 'react'
|
|
6
6
|
|
|
7
7
|
import { tv } from '../tv'
|
|
8
8
|
|
|
9
9
|
export const selectTrigger = tv({
|
|
10
10
|
base: 'flex w-full items-center justify-between gap-tight rounded-r8'
|
|
11
|
-
+ ' text-value text-
|
|
11
|
+
+ ' text-value text-fg'
|
|
12
12
|
+ ' transition-state'
|
|
13
13
|
+ ' invalid:ring-2 invalid:ring-line-danger'
|
|
14
14
|
+ ' focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-line-focus-ring'
|
|
15
|
-
+ ' disabled:bg-bg-neutral-disabled disabled:text-
|
|
16
|
-
+ ' unset:text-
|
|
15
|
+
+ ' disabled:bg-bg-neutral-disabled disabled:text-fg-disabled disabled:cursor-not-allowed'
|
|
16
|
+
+ ' unset:text-fg-placeholder',
|
|
17
17
|
|
|
18
18
|
variants: {
|
|
19
19
|
surface: {
|
|
20
20
|
bare: 'bg-transparent ring-1 ring-inset ring-line-strong',
|
|
21
|
-
well: 'bg-
|
|
21
|
+
well: 'bg-bg-well',
|
|
22
22
|
},
|
|
23
23
|
size: {
|
|
24
24
|
sm: 'h-8 px-3',
|
|
@@ -30,20 +30,53 @@ export const selectTrigger = tv({
|
|
|
30
30
|
defaultVariants: { surface: 'bare', size: 'md' },
|
|
31
31
|
})
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
const selectArrow = tv({
|
|
34
|
+
base: 'data-uncentered:invisible'
|
|
35
|
+
+ ' side-top:top-full'
|
|
36
|
+
+ ' side-bottom:bottom-full side-bottom:rotate-180'
|
|
37
|
+
+ ' side-right:left-0 side-right:origin-top-left side-right:rotate-90 side-right:-translate-y-1/2'
|
|
38
|
+
+ ' side-left:right-0 side-left:origin-top-right side-left:-rotate-90 side-left:-translate-y-1/2',
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
type ItemElementProps = { value?: string, children?: ReactNode }
|
|
42
|
+
|
|
43
|
+
function findItemText(children: ReactNode): { label: ReactNode } | undefined {
|
|
44
|
+
let found: { label: ReactNode } | undefined
|
|
45
|
+
Children.forEach(children, child => {
|
|
46
|
+
if (found || !isValidElement<ItemElementProps>(child)) return
|
|
47
|
+
found = child.type === SelectItemText
|
|
48
|
+
? { label: child.props.children }
|
|
49
|
+
: findItemText(child.props.children)
|
|
50
|
+
})
|
|
51
|
+
return found
|
|
45
52
|
}
|
|
46
53
|
|
|
54
|
+
function collectItems(children: ReactNode, items: Record<string, ReactNode>) {
|
|
55
|
+
Children.forEach(children, child => {
|
|
56
|
+
if (!isValidElement<ItemElementProps>(child)) return
|
|
57
|
+
if (child.type === SelectItem && child.props.value !== undefined) {
|
|
58
|
+
const text = findItemText(child.props.children)
|
|
59
|
+
if (text) items[child.props.value] = text.label
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
collectItems(child.props.children, items)
|
|
63
|
+
})
|
|
64
|
+
return items
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type SelectProps = Omit<ComponentProps<typeof SelectPrimitive.Trigger>, 'className' | 'value' | 'defaultValue'>
|
|
68
|
+
& Pick<SelectPrimitive.Root.Props<string>, 'open' | 'defaultOpen' | 'onOpenChange' | 'items' | 'name' | 'required' | 'readOnly' | 'modal' | 'form'>
|
|
69
|
+
& {
|
|
70
|
+
value?: string
|
|
71
|
+
defaultValue?: string
|
|
72
|
+
onValueChange?: (value: string, eventDetails: SelectPrimitive.Root.ChangeEventDetails) => void
|
|
73
|
+
placeholder?: string
|
|
74
|
+
surface?: 'bare' | 'well'
|
|
75
|
+
size?: 'sm' | 'md' | 'lg'
|
|
76
|
+
arrow?: boolean
|
|
77
|
+
className?: string
|
|
78
|
+
}
|
|
79
|
+
|
|
47
80
|
export function Select({
|
|
48
81
|
value,
|
|
49
82
|
defaultValue,
|
|
@@ -51,6 +84,12 @@ export function Select({
|
|
|
51
84
|
open,
|
|
52
85
|
defaultOpen,
|
|
53
86
|
onOpenChange,
|
|
87
|
+
items,
|
|
88
|
+
name,
|
|
89
|
+
required,
|
|
90
|
+
readOnly,
|
|
91
|
+
modal,
|
|
92
|
+
form,
|
|
54
93
|
placeholder,
|
|
55
94
|
surface,
|
|
56
95
|
size,
|
|
@@ -59,53 +98,91 @@ export function Select({
|
|
|
59
98
|
children,
|
|
60
99
|
...rest
|
|
61
100
|
}: SelectProps) {
|
|
101
|
+
const [shown, setShown] = useState(defaultOpen ?? false)
|
|
102
|
+
const popup = useRef<HTMLDivElement>(null)
|
|
103
|
+
const isOpen = open ?? shown
|
|
104
|
+
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (!isOpen) return
|
|
107
|
+
|
|
108
|
+
const frame = requestAnimationFrame(() => {
|
|
109
|
+
const list = popup.current
|
|
110
|
+
if (!list || list.querySelector('[role="option"][aria-selected="true"]')) return
|
|
111
|
+
list.querySelector<HTMLElement>('[role="option"]:not([data-disabled])')?.focus({ preventScroll: true })
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return () => cancelAnimationFrame(frame)
|
|
115
|
+
}, [isOpen])
|
|
116
|
+
|
|
62
117
|
return (
|
|
63
|
-
<SelectPrimitive.Root
|
|
118
|
+
<SelectPrimitive.Root<string>
|
|
64
119
|
value={value}
|
|
65
120
|
defaultValue={defaultValue}
|
|
66
|
-
onValueChange={onValueChange}
|
|
121
|
+
onValueChange={(next, eventDetails) => onValueChange?.(next ?? '', eventDetails)}
|
|
67
122
|
open={open}
|
|
68
123
|
defaultOpen={defaultOpen}
|
|
69
|
-
onOpenChange={
|
|
124
|
+
onOpenChange={(next, eventDetails) => {
|
|
125
|
+
setShown(next)
|
|
126
|
+
onOpenChange?.(next, eventDetails)
|
|
127
|
+
}}
|
|
128
|
+
items={items ?? collectItems(children, {})}
|
|
129
|
+
name={name}
|
|
130
|
+
required={required}
|
|
131
|
+
readOnly={readOnly}
|
|
132
|
+
modal={modal}
|
|
133
|
+
form={form}
|
|
70
134
|
>
|
|
71
135
|
<SelectPrimitive.Trigger {...rest} className={selectTrigger({ surface, size, className })}>
|
|
72
136
|
<span className="min-w-0 truncate">
|
|
73
137
|
<SelectPrimitive.Value placeholder={placeholder} />
|
|
74
138
|
</span>
|
|
75
|
-
<SelectPrimitive.Icon className="text-
|
|
76
|
-
<
|
|
139
|
+
<SelectPrimitive.Icon className="text-fg-subtle">
|
|
140
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4 transition-transform in-data-popup-open:rotate-180 motion-reduce:transition-none">
|
|
141
|
+
<path d="m6 9 6 6 6-6" />
|
|
142
|
+
</svg>
|
|
77
143
|
</SelectPrimitive.Icon>
|
|
78
144
|
</SelectPrimitive.Trigger>
|
|
79
145
|
|
|
80
146
|
<SelectPrimitive.Portal>
|
|
81
|
-
<SelectPrimitive.
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
position="popper"
|
|
86
|
-
sideOffset={8}
|
|
147
|
+
<SelectPrimitive.Positioner
|
|
148
|
+
alignItemWithTrigger={false}
|
|
149
|
+
align="start"
|
|
150
|
+
sideOffset={arrow ? 14 : 8}
|
|
87
151
|
arrowPadding={16}
|
|
152
|
+
className="z-overlay min-w-max will-change-transform"
|
|
88
153
|
>
|
|
89
|
-
<SelectPrimitive.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
</SelectPrimitive.ScrollDownButton>
|
|
100
|
-
|
|
101
|
-
{arrow && (
|
|
102
|
-
<SelectPrimitive.Arrow asChild width={12} height={6}>
|
|
103
|
-
<svg viewBox="0 0 12 6" preserveAspectRatio="none" className="fill-background-panel -translate-y-px">
|
|
104
|
-
<path d="M0 0L4.58579 4.58579C5.36683 5.36683 6.63316 5.36684 7.41421 4.58579L12 0" className="stroke-line stroke-1" />
|
|
154
|
+
<SelectPrimitive.Popup
|
|
155
|
+
ref={popup}
|
|
156
|
+
className="float text-fg rounded-r12 z-overlay
|
|
157
|
+
flex flex-col outline-none
|
|
158
|
+
min-w-(--anchor-width) py-1
|
|
159
|
+
open:animate-enter closed:animate-exit"
|
|
160
|
+
>
|
|
161
|
+
<SelectPrimitive.ScrollUpArrow className="text-fg-subtle flex h-6 cursor-default items-center justify-center">
|
|
162
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
|
|
163
|
+
<path d="m18 15-6-6-6 6" />
|
|
105
164
|
</svg>
|
|
106
|
-
</SelectPrimitive.
|
|
107
|
-
|
|
108
|
-
|
|
165
|
+
</SelectPrimitive.ScrollUpArrow>
|
|
166
|
+
|
|
167
|
+
<SelectPrimitive.List className="relative flex flex-1 flex-col gap-px overflow-x-hidden overflow-y-auto px-1">
|
|
168
|
+
{children}
|
|
169
|
+
</SelectPrimitive.List>
|
|
170
|
+
|
|
171
|
+
<SelectPrimitive.ScrollDownArrow className="text-fg-subtle flex h-6 cursor-default items-center justify-center">
|
|
172
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
|
|
173
|
+
<path d="m6 9 6 6 6-6" />
|
|
174
|
+
</svg>
|
|
175
|
+
</SelectPrimitive.ScrollDownArrow>
|
|
176
|
+
|
|
177
|
+
{arrow && (
|
|
178
|
+
<SelectPrimitive.Arrow className={selectArrow()}>
|
|
179
|
+
<svg width={12} height={6} viewBox="0 0 12 6" preserveAspectRatio="none" className="block fill-bg-panel -translate-y-px">
|
|
180
|
+
<path d="M0 0L4.58579 4.58579C5.36683 5.36683 6.63316 5.36684 7.41421 4.58579L12 0" className="stroke-line stroke-1" />
|
|
181
|
+
</svg>
|
|
182
|
+
</SelectPrimitive.Arrow>
|
|
183
|
+
)}
|
|
184
|
+
</SelectPrimitive.Popup>
|
|
185
|
+
</SelectPrimitive.Positioner>
|
|
109
186
|
</SelectPrimitive.Portal>
|
|
110
187
|
</SelectPrimitive.Root>
|
|
111
188
|
)
|
|
@@ -113,17 +190,18 @@ export function Select({
|
|
|
113
190
|
|
|
114
191
|
const selectGroup = tv({ base: 'flex flex-col gap-px' })
|
|
115
192
|
|
|
116
|
-
export
|
|
193
|
+
export type SelectGroupProps = Omit<ComponentProps<typeof SelectPrimitive.Group>, 'className'> & {
|
|
117
194
|
label?: string
|
|
118
195
|
className?: string
|
|
119
|
-
|
|
120
|
-
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function SelectGroup({ label, className, children, ...rest }: SelectGroupProps) {
|
|
121
199
|
return (
|
|
122
|
-
<SelectPrimitive.Group className={selectGroup({ className })}>
|
|
200
|
+
<SelectPrimitive.Group {...rest} className={selectGroup({ className })}>
|
|
123
201
|
{label && (
|
|
124
|
-
<SelectPrimitive.
|
|
202
|
+
<SelectPrimitive.GroupLabel className="text-caption text-fg-subtle px-2 pb-1 pt-2">
|
|
125
203
|
{label}
|
|
126
|
-
</SelectPrimitive.
|
|
204
|
+
</SelectPrimitive.GroupLabel>
|
|
127
205
|
)}
|
|
128
206
|
{children}
|
|
129
207
|
</SelectPrimitive.Group>
|
|
@@ -134,43 +212,39 @@ const selectItem = tv({
|
|
|
134
212
|
base: 'flex min-h-9 cursor-default items-center justify-between gap-stack'
|
|
135
213
|
+ ' rounded-r8 px-2 py-1 transition-state'
|
|
136
214
|
+ ' data-highlighted:bg-bg-neutral-hover data-highlighted:outline-none'
|
|
137
|
-
+ ' data-disabled:text-
|
|
215
|
+
+ ' data-disabled:text-fg-disabled data-disabled:pointer-events-none',
|
|
138
216
|
})
|
|
139
217
|
|
|
140
|
-
export
|
|
218
|
+
export type SelectItemProps = Omit<ComponentProps<typeof SelectPrimitive.Item>, 'className' | 'value'> & {
|
|
141
219
|
value: string
|
|
142
|
-
disabled?: boolean
|
|
143
220
|
className?: string
|
|
144
|
-
|
|
145
|
-
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function SelectItem({ className, children, ...rest }: SelectItemProps) {
|
|
146
224
|
return (
|
|
147
|
-
<SelectPrimitive.Item
|
|
148
|
-
value={value}
|
|
149
|
-
disabled={disabled}
|
|
150
|
-
className={selectItem({ className })}
|
|
151
|
-
>
|
|
225
|
+
<SelectPrimitive.Item {...rest} className={selectItem({ className })}>
|
|
152
226
|
<span className="flex min-w-0 flex-col">
|
|
153
227
|
{children}
|
|
154
228
|
</span>
|
|
155
229
|
|
|
156
|
-
<SelectPrimitive.ItemIndicator className="text-
|
|
157
|
-
<
|
|
230
|
+
<SelectPrimitive.ItemIndicator className="text-fg-muted flex shrink-0">
|
|
231
|
+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
|
|
232
|
+
<path d="M20 6 9 17l-5-5" />
|
|
233
|
+
</svg>
|
|
158
234
|
</SelectPrimitive.ItemIndicator>
|
|
159
235
|
</SelectPrimitive.Item>
|
|
160
236
|
)
|
|
161
237
|
}
|
|
162
238
|
|
|
163
|
-
|
|
239
|
+
export type SelectItemTextProps = Omit<ComponentProps<typeof SelectPrimitive.ItemText>, 'className'> & {
|
|
240
|
+
className?: string
|
|
241
|
+
}
|
|
164
242
|
|
|
165
|
-
export function SelectItemText({ className,
|
|
166
|
-
return
|
|
167
|
-
<SelectPrimitive.ItemText className={selectItemText({ className })}>
|
|
168
|
-
{children}
|
|
169
|
-
</SelectPrimitive.ItemText>
|
|
170
|
-
)
|
|
243
|
+
export function SelectItemText({ className: _className, render = <span />, ...rest }: SelectItemTextProps) {
|
|
244
|
+
return <SelectPrimitive.ItemText {...rest} render={render} />
|
|
171
245
|
}
|
|
172
246
|
|
|
173
|
-
const selectItemDescription = tv({ base: 'text-caption text-
|
|
247
|
+
const selectItemDescription = tv({ base: 'text-caption text-fg-subtle' })
|
|
174
248
|
|
|
175
249
|
export function SelectItemDescription({ className, children }: { className?: string, children?: ReactNode }) {
|
|
176
250
|
return (
|
|
@@ -182,6 +256,10 @@ export function SelectItemDescription({ className, children }: { className?: str
|
|
|
182
256
|
|
|
183
257
|
const selectSeparator = tv({ base: 'bg-line -mx-1 my-1 h-px' })
|
|
184
258
|
|
|
185
|
-
export
|
|
186
|
-
|
|
259
|
+
export type SelectSeparatorProps = Omit<ComponentProps<typeof SelectPrimitive.Separator>, 'className'> & {
|
|
260
|
+
className?: string
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export function SelectSeparator({ className, ...rest }: SelectSeparatorProps) {
|
|
264
|
+
return <SelectPrimitive.Separator {...rest} className={selectSeparator({ className })} />
|
|
187
265
|
}
|
package/switch/index.tsx
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { Switch as SwitchPrimitive } from '
|
|
3
|
+
import { Switch as SwitchPrimitive } from '@base-ui/react/switch'
|
|
4
4
|
import type { ComponentProps } from 'react'
|
|
5
5
|
|
|
6
6
|
import { tv } from '../tv'
|
|
7
7
|
|
|
8
8
|
export const uiSwitch = tv({
|
|
9
9
|
base: 'h-6 w-10 shrink-0 rounded-full p-0.5 transition-state press press-shrink'
|
|
10
|
-
+ ' bg-
|
|
10
|
+
+ ' bg-bg-neutral-track ring-1 ring-inset ring-line-strong'
|
|
11
11
|
+ ' invalid:ring-2 invalid:ring-line-danger'
|
|
12
12
|
+ ' focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-line-focus-ring'
|
|
13
13
|
+ ' disabled:bg-bg-neutral-disabled disabled:cursor-not-allowed'
|
|
@@ -26,15 +26,22 @@ export const uiSwitch = tv({
|
|
|
26
26
|
defaultVariants: { color: 'neutral' },
|
|
27
27
|
})
|
|
28
28
|
|
|
29
|
-
export
|
|
29
|
+
export type SwitchProps = Omit<ComponentProps<typeof SwitchPrimitive.Root>, 'className'> & {
|
|
30
30
|
color?: 'neutral' | 'info' | 'success' | 'warning' | 'danger'
|
|
31
|
+
className?: string
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
export function Switch({
|
|
34
|
+
export function Switch({
|
|
35
|
+
color,
|
|
36
|
+
className,
|
|
37
|
+
nativeButton = true,
|
|
38
|
+
render = <button type="button" />,
|
|
39
|
+
...rest
|
|
40
|
+
}: SwitchProps) {
|
|
34
41
|
return (
|
|
35
|
-
<SwitchPrimitive.Root {...rest} className={uiSwitch({ color, className })}>
|
|
42
|
+
<SwitchPrimitive.Root {...rest} nativeButton={nativeButton} render={render} className={uiSwitch({ color, className })}>
|
|
36
43
|
<SwitchPrimitive.Thumb
|
|
37
|
-
className="bg-bg-neutral-knob block size-5 rounded-full checked:translate-x-4
|
|
44
|
+
className="bg-bg-neutral-knob-off checked:bg-bg-neutral-knob block size-5 rounded-full checked:translate-x-4
|
|
38
45
|
transition-transform ease-state motion-reduce:transition-none"
|
|
39
46
|
/>
|
|
40
47
|
</SwitchPrimitive.Root>
|
package/table/index.tsx
CHANGED
|
@@ -20,7 +20,7 @@ export interface TableProps {
|
|
|
20
20
|
children?: ReactNode
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
const tableFrame = tv({ base: 'frame rounded-
|
|
23
|
+
const tableFrame = tv({ base: 'frame rounded-r12 overflow-hidden' })
|
|
24
24
|
|
|
25
25
|
export function Table({ label, fixed = false, className, children }: TableProps) {
|
|
26
26
|
const [box, setBox] = useState<HTMLDivElement | null>(null)
|
|
@@ -59,7 +59,7 @@ export function Table({ label, fixed = false, className, children }: TableProps)
|
|
|
59
59
|
)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const tableHeader = tv({ base: 'bg-
|
|
62
|
+
const tableHeader = tv({ base: 'bg-bg-well border-b border-line' })
|
|
63
63
|
|
|
64
64
|
export function TableHeader({ className, children }: { className?: string, children?: ReactNode }) {
|
|
65
65
|
return (
|
|
@@ -71,7 +71,7 @@ export function TableHeader({ className, children }: { className?: string, child
|
|
|
71
71
|
)
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
const tableHeadCell = tv({ base: 'text-caption text-
|
|
74
|
+
const tableHeadCell = tv({ base: 'text-caption text-fg-muted h-9 px-4 text-left whitespace-nowrap' })
|
|
75
75
|
|
|
76
76
|
export function TableHeadCell({ className, children }: { className?: string, children?: ReactNode }) {
|
|
77
77
|
return (
|
|
@@ -89,8 +89,7 @@ export function TableBody({ className, children }: { className?: string, childre
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
const tableRow = tv({
|
|
92
|
-
base: 'border-b border-line last:border-b-0
|
|
93
|
-
+ ' hover:bg-bg-neutral-hover',
|
|
92
|
+
base: 'border-b border-line last:border-b-0',
|
|
94
93
|
})
|
|
95
94
|
|
|
96
95
|
export function TableRow({ className, children }: { className?: string, children?: ReactNode }) {
|
|
@@ -104,7 +103,7 @@ export function TableRow({ className, children }: { className?: string, children
|
|
|
104
103
|
}
|
|
105
104
|
|
|
106
105
|
export const tableCell = tv({
|
|
107
|
-
base: 'text-label text-
|
|
106
|
+
base: 'text-label text-fg h-11 px-4',
|
|
108
107
|
|
|
109
108
|
variants: {
|
|
110
109
|
mono: { true: 'font-mono', false: '' },
|
package/tabs/index.module.css
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
position: absolute;
|
|
3
3
|
bottom: 0;
|
|
4
4
|
height: 2px;
|
|
5
|
-
background: var(--color-
|
|
6
|
-
width: var(--
|
|
7
|
-
translate: var(--
|
|
5
|
+
background: var(--color-fg);
|
|
6
|
+
width: var(--active-tab-width);
|
|
7
|
+
translate: var(--active-tab-left) 0;
|
|
8
8
|
transition: translate var(--dur-state) var(--ease-state),
|
|
9
9
|
width var(--dur-state) var(--ease-state);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
.content[data-hidden] { display: none; }
|
|
13
|
+
|
|
12
14
|
@media (prefers-reduced-motion: reduce) {
|
|
13
15
|
.underline { transition: none; }
|
|
14
16
|
}
|
package/tabs/index.tsx
CHANGED
|
@@ -1,113 +1,62 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import { Tabs as TabsPrimitive } from '
|
|
4
|
-
import {
|
|
3
|
+
import { Tabs as TabsPrimitive } from '@base-ui/react/tabs'
|
|
4
|
+
import type { ComponentProps } from 'react'
|
|
5
5
|
|
|
6
6
|
import { tv } from '../tv'
|
|
7
7
|
|
|
8
8
|
import s from './index.module.css'
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
export interface TabsProps {
|
|
13
|
-
value?: string
|
|
14
|
-
defaultValue?: string
|
|
15
|
-
onValueChange?: (value: string) => void
|
|
16
|
-
className?: string
|
|
17
|
-
children?: ReactNode
|
|
18
|
-
}
|
|
10
|
+
export type TabsProps = Omit<ComponentProps<typeof TabsPrimitive.Root>, 'className'> & { className?: string }
|
|
19
11
|
|
|
20
12
|
const tabs = tv({ base: 'flex flex-col gap-stack' })
|
|
21
13
|
|
|
22
|
-
export function Tabs({
|
|
23
|
-
return (
|
|
24
|
-
<TabsPrimitive.Root
|
|
25
|
-
value={value}
|
|
26
|
-
defaultValue={defaultValue}
|
|
27
|
-
onValueChange={onValueChange}
|
|
28
|
-
className={tabs({ className })}
|
|
29
|
-
>
|
|
30
|
-
{children}
|
|
31
|
-
</TabsPrimitive.Root>
|
|
32
|
-
)
|
|
14
|
+
export function Tabs({ className, ...rest }: TabsProps) {
|
|
15
|
+
return <TabsPrimitive.Root {...rest} className={tabs({ className })} />
|
|
33
16
|
}
|
|
34
17
|
|
|
35
18
|
const tabsList = tv({ base: 'border-line scrollbar-thin scrollbar-thumb-line-strong scrollbar-track-transparent overflow-x-auto border-b' })
|
|
36
19
|
|
|
37
|
-
export
|
|
38
|
-
const list = useRef<HTMLDivElement>(null)
|
|
39
|
-
const bar = useRef<HTMLSpanElement>(null)
|
|
40
|
-
|
|
41
|
-
useEffect(() => {
|
|
42
|
-
const root = list.current
|
|
43
|
-
const mark = bar.current
|
|
44
|
-
if (!root || !mark) return
|
|
45
|
-
|
|
46
|
-
const measure = () => {
|
|
47
|
-
const active = root.querySelector<HTMLElement>(ACTIVE)
|
|
48
|
-
mark.style.setProperty('--tabs-indicator-size', `${active?.offsetWidth ?? 0}px`)
|
|
49
|
-
mark.style.setProperty('--tabs-indicator-position', `${active?.offsetLeft ?? 0}px`)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
measure()
|
|
53
|
-
|
|
54
|
-
const attrs = new MutationObserver(measure)
|
|
55
|
-
attrs.observe(root, { attributes: true, subtree: true, attributeFilter: ['data-state'] })
|
|
56
|
-
|
|
57
|
-
const box = new ResizeObserver(measure)
|
|
58
|
-
box.observe(root)
|
|
59
|
-
|
|
60
|
-
return () => { attrs.disconnect(); box.disconnect() }
|
|
61
|
-
}, [])
|
|
20
|
+
export type TabsListProps = Omit<ComponentProps<typeof TabsPrimitive.List>, 'className'> & { className?: string }
|
|
62
21
|
|
|
22
|
+
export function TabsList({ className, children, ...rest }: TabsListProps) {
|
|
63
23
|
return (
|
|
64
|
-
<div
|
|
65
|
-
|
|
66
|
-
>
|
|
67
|
-
<TabsPrimitive.List ref={list} className="relative flex w-max min-w-full">
|
|
24
|
+
<div className={tabsList({ className })}>
|
|
25
|
+
<TabsPrimitive.List {...rest} className="relative flex w-max min-w-full">
|
|
68
26
|
{children}
|
|
69
27
|
|
|
70
|
-
<
|
|
28
|
+
<TabsPrimitive.Indicator className={s.underline} />
|
|
71
29
|
</TabsPrimitive.List>
|
|
72
30
|
</div>
|
|
73
31
|
)
|
|
74
32
|
}
|
|
75
33
|
|
|
76
34
|
const tabsTrigger = tv({
|
|
77
|
-
base: 'text-label text-
|
|
35
|
+
base: 'text-label text-fg-muted inline-flex h-9 items-center gap-tight px-3 transition-state'
|
|
78
36
|
+ ' [&_svg]:size-4'
|
|
79
|
-
+ ' press-shrink
|
|
80
|
-
+ ' disabled:text-
|
|
81
|
-
+ ' aria-selected:text-
|
|
37
|
+
+ ' press-shrink not-data-disabled:cursor-pointer not-data-disabled:hover:text-fg'
|
|
38
|
+
+ ' data-disabled:text-fg-disabled data-disabled:cursor-not-allowed'
|
|
39
|
+
+ ' aria-selected:text-fg'
|
|
82
40
|
+ ' focus-visible:outline-2 focus-visible:-outline-offset-2'
|
|
83
41
|
+ ' focus-visible:outline-line-focus-ring',
|
|
84
42
|
})
|
|
85
43
|
|
|
86
|
-
export
|
|
44
|
+
export type TabsTriggerProps = Omit<ComponentProps<typeof TabsPrimitive.Tab>, 'className' | 'value'> & {
|
|
87
45
|
value: string
|
|
88
|
-
disabled?: boolean
|
|
89
46
|
className?: string
|
|
90
|
-
children?: ReactNode
|
|
91
|
-
}) {
|
|
92
|
-
return (
|
|
93
|
-
<TabsPrimitive.Trigger
|
|
94
|
-
value={value}
|
|
95
|
-
disabled={disabled}
|
|
96
|
-
className={tabsTrigger({ className })}
|
|
97
|
-
>
|
|
98
|
-
{children}
|
|
99
|
-
</TabsPrimitive.Trigger>
|
|
100
|
-
)
|
|
101
47
|
}
|
|
102
48
|
|
|
103
|
-
export function
|
|
49
|
+
export function TabsTrigger({ className, ...rest }: TabsTriggerProps) {
|
|
50
|
+
return <TabsPrimitive.Tab {...rest} className={tabsTrigger({ className })} />
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const tabsContent = tv({ base: s.content })
|
|
54
|
+
|
|
55
|
+
export type TabsContentProps = Omit<ComponentProps<typeof TabsPrimitive.Panel>, 'className' | 'value'> & {
|
|
104
56
|
value: string
|
|
105
57
|
className?: string
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
{children}
|
|
111
|
-
</TabsPrimitive.Content>
|
|
112
|
-
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function TabsContent({ className, ...rest }: TabsContentProps) {
|
|
61
|
+
return <TabsPrimitive.Panel {...rest} className={tabsContent({ className })} />
|
|
113
62
|
}
|
package/tailwind.css
ADDED
package/textarea/index.tsx
CHANGED
|
@@ -1,21 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Field as FieldPrimitive } from '@base-ui/react/field'
|
|
4
|
+
import type { ComponentProps } from 'react'
|
|
2
5
|
|
|
3
6
|
import { tv } from '../tv'
|
|
4
7
|
|
|
5
8
|
export const textarea = tv({
|
|
6
|
-
base: 'w-full rounded-r8 text-value text-
|
|
7
|
-
+ ' placeholder:text-
|
|
9
|
+
base: 'w-full rounded-r8 text-value text-fg'
|
|
10
|
+
+ ' placeholder:text-fg-placeholder'
|
|
8
11
|
+ ' invalid:ring-2 invalid:ring-line-danger'
|
|
9
|
-
+ ' read-only:text-
|
|
12
|
+
+ ' read-only:text-fg-muted'
|
|
10
13
|
+ ' resize-y'
|
|
11
14
|
+ ' transition-state'
|
|
12
15
|
+ ' focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-line-focus-ring'
|
|
13
|
-
+ ' disabled:bg-bg-neutral-disabled disabled:text-
|
|
16
|
+
+ ' disabled:bg-bg-neutral-disabled disabled:text-fg-disabled disabled:cursor-not-allowed',
|
|
14
17
|
|
|
15
18
|
variants: {
|
|
16
19
|
surface: {
|
|
17
|
-
bare: 'bg-transparent ring-1 ring-inset ring-line-strong read-only:bg-
|
|
18
|
-
well: 'bg-
|
|
20
|
+
bare: 'bg-transparent ring-1 ring-inset ring-line-strong read-only:bg-bg-well',
|
|
21
|
+
well: 'bg-bg-well',
|
|
19
22
|
},
|
|
20
23
|
size: {
|
|
21
24
|
sm: 'p-2',
|
|
@@ -27,11 +30,20 @@ export const textarea = tv({
|
|
|
27
30
|
defaultVariants: { surface: 'bare', size: 'md' },
|
|
28
31
|
})
|
|
29
32
|
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
export type TextareaProps = Omit<ComponentProps<'textarea'>, 'className' | 'value' | 'defaultValue'>
|
|
34
|
+
& Pick<FieldPrimitive.Control.Props, 'value' | 'defaultValue' | 'onValueChange'>
|
|
35
|
+
& {
|
|
36
|
+
surface?: 'bare' | 'well'
|
|
37
|
+
size?: 'sm' | 'md' | 'lg'
|
|
38
|
+
className?: string
|
|
39
|
+
}
|
|
34
40
|
|
|
35
|
-
export function Textarea({ surface, size, rows = 3, className, ...rest }: TextareaProps) {
|
|
36
|
-
return
|
|
41
|
+
export function Textarea({ surface, size, rows = 3, cols, wrap, className, ...rest }: TextareaProps) {
|
|
42
|
+
return (
|
|
43
|
+
<FieldPrimitive.Control
|
|
44
|
+
{...(rest as FieldPrimitive.Control.Props)}
|
|
45
|
+
render={<textarea rows={rows} cols={cols} wrap={wrap} />}
|
|
46
|
+
className={textarea({ surface, size, className })}
|
|
47
|
+
/>
|
|
48
|
+
)
|
|
37
49
|
}
|