@pantoia/ui 0.3.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.
Files changed (62) hide show
  1. package/accordion/index.module.css +4 -4
  2. package/accordion/index.tsx +21 -51
  3. package/alert-dialog/index.tsx +36 -42
  4. package/badge/index.tsx +1 -1
  5. package/button/index.tsx +31 -30
  6. package/callout/index.tsx +4 -4
  7. package/checkbox/index.tsx +20 -85
  8. package/combobox/index.tsx +122 -316
  9. package/command-palette/index.tsx +51 -32
  10. package/dialog/index.tsx +28 -24
  11. package/dist/index.js +5 -5
  12. package/drawer/index.module.css +2 -2
  13. package/drawer/index.tsx +52 -130
  14. package/dropdown-menu/index.tsx +87 -95
  15. package/field/index.tsx +27 -86
  16. package/input/index.tsx +7 -3
  17. package/kbd/index.tsx +2 -2
  18. package/label/index.tsx +6 -8
  19. package/number-field/index.tsx +122 -173
  20. package/package.json +2 -6
  21. package/popover/index.tsx +61 -32
  22. package/radio-group/index.tsx +35 -38
  23. package/registry.json +64 -154
  24. package/select/index.tsx +141 -70
  25. package/switch/index.tsx +13 -6
  26. package/table/index.tsx +1 -2
  27. package/tabs/index.module.css +3 -3
  28. package/tabs/index.tsx +22 -76
  29. package/textarea/index.tsx +19 -7
  30. package/toast/index.module.css +187 -0
  31. package/toast/index.tsx +141 -46
  32. package/toggle/index.tsx +4 -3
  33. package/tokens.css +7 -1
  34. package/tooltip/index.tsx +61 -30
  35. package/utilities.css +11 -6
  36. package/accordion/AGENTS.md +0 -60
  37. package/alert-dialog/AGENTS.md +0 -78
  38. package/badge/AGENTS.md +0 -35
  39. package/button/AGENTS.md +0 -39
  40. package/callout/AGENTS.md +0 -37
  41. package/checkbox/AGENTS.md +0 -54
  42. package/combobox/AGENTS.md +0 -150
  43. package/command-palette/AGENTS.md +0 -41
  44. package/dialog/AGENTS.md +0 -81
  45. package/drawer/AGENTS.md +0 -66
  46. package/dropdown-menu/AGENTS.md +0 -133
  47. package/field/AGENTS.md +0 -92
  48. package/input/AGENTS.md +0 -35
  49. package/kbd/AGENTS.md +0 -17
  50. package/label/AGENTS.md +0 -21
  51. package/number-field/AGENTS.md +0 -49
  52. package/popover/AGENTS.md +0 -64
  53. package/radio-group/AGENTS.md +0 -81
  54. package/select/AGENTS.md +0 -90
  55. package/switch/AGENTS.md +0 -37
  56. package/table/AGENTS.md +0 -82
  57. package/tabs/AGENTS.md +0 -61
  58. package/test-utils.ts +0 -10
  59. package/textarea/AGENTS.md +0 -35
  60. package/toast/AGENTS.md +0 -34
  61. package/toggle/AGENTS.md +0 -37
  62. package/tooltip/AGENTS.md +0 -50
package/select/index.tsx CHANGED
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
- import { Select as SelectPrimitive } from 'radix-ui'
3
+ import { Select as SelectPrimitive } from '@base-ui/react/select'
4
+ import { Children, isValidElement, useEffect, useRef, useState } from 'react'
4
5
  import type { ComponentProps, ReactNode } from 'react'
5
6
 
6
7
  import { tv } from '../tv'
@@ -29,20 +30,53 @@ export const selectTrigger = tv({
29
30
  defaultVariants: { surface: 'bare', size: 'md' },
30
31
  })
31
32
 
32
- export interface SelectProps extends Omit<ComponentProps<typeof SelectPrimitive.Trigger>, 'value' | 'defaultValue' | 'dir'> {
33
- value?: string
34
- defaultValue?: string
35
- onValueChange?: (value: string) => void
36
- open?: boolean
37
- defaultOpen?: boolean
38
- onOpenChange?: (open: boolean) => void
39
- placeholder?: string
40
- surface?: 'bare' | 'well'
41
- size?: 'sm' | 'md' | 'lg'
42
- arrow?: boolean
43
- children?: ReactNode
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
44
52
  }
45
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
+
46
80
  export function Select({
47
81
  value,
48
82
  defaultValue,
@@ -50,6 +84,12 @@ export function Select({
50
84
  open,
51
85
  defaultOpen,
52
86
  onOpenChange,
87
+ items,
88
+ name,
89
+ required,
90
+ readOnly,
91
+ modal,
92
+ form,
53
93
  placeholder,
54
94
  surface,
55
95
  size,
@@ -58,59 +98,91 @@ export function Select({
58
98
  children,
59
99
  ...rest
60
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
+
61
117
  return (
62
- <SelectPrimitive.Root
118
+ <SelectPrimitive.Root<string>
63
119
  value={value}
64
120
  defaultValue={defaultValue}
65
- onValueChange={onValueChange}
121
+ onValueChange={(next, eventDetails) => onValueChange?.(next ?? '', eventDetails)}
66
122
  open={open}
67
123
  defaultOpen={defaultOpen}
68
- onOpenChange={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}
69
134
  >
70
135
  <SelectPrimitive.Trigger {...rest} className={selectTrigger({ surface, size, className })}>
71
136
  <span className="min-w-0 truncate">
72
137
  <SelectPrimitive.Value placeholder={placeholder} />
73
138
  </span>
74
139
  <SelectPrimitive.Icon className="text-fg-subtle">
75
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
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">
76
141
  <path d="m6 9 6 6 6-6" />
77
142
  </svg>
78
143
  </SelectPrimitive.Icon>
79
144
  </SelectPrimitive.Trigger>
80
145
 
81
146
  <SelectPrimitive.Portal>
82
- <SelectPrimitive.Content
83
- className="float text-fg rounded-r12 z-overlay
84
- min-w-(--radix-select-trigger-width) py-1
85
- open:animate-enter closed:animate-exit"
86
- position="popper"
87
- sideOffset={8}
147
+ <SelectPrimitive.Positioner
148
+ alignItemWithTrigger={false}
149
+ align="start"
150
+ sideOffset={arrow ? 14 : 8}
88
151
  arrowPadding={16}
152
+ className="z-overlay min-w-max will-change-transform"
89
153
  >
90
- <SelectPrimitive.ScrollUpButton className="text-fg-subtle flex h-6 cursor-default items-center justify-center">
91
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
92
- <path d="m18 15-6-6-6 6" />
93
- </svg>
94
- </SelectPrimitive.ScrollUpButton>
95
-
96
- <SelectPrimitive.Viewport className="flex flex-col gap-px px-1">
97
- {children}
98
- </SelectPrimitive.Viewport>
99
-
100
- <SelectPrimitive.ScrollDownButton className="text-fg-subtle flex h-6 cursor-default items-center justify-center">
101
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className="size-4">
102
- <path d="m6 9 6 6 6-6" />
103
- </svg>
104
- </SelectPrimitive.ScrollDownButton>
105
-
106
- {arrow && (
107
- <SelectPrimitive.Arrow asChild width={12} height={6}>
108
- <svg viewBox="0 0 12 6" preserveAspectRatio="none" className="fill-bg-panel -translate-y-px">
109
- <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" />
110
164
  </svg>
111
- </SelectPrimitive.Arrow>
112
- )}
113
- </SelectPrimitive.Content>
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>
114
186
  </SelectPrimitive.Portal>
115
187
  </SelectPrimitive.Root>
116
188
  )
@@ -118,17 +190,18 @@ export function Select({
118
190
 
119
191
  const selectGroup = tv({ base: 'flex flex-col gap-px' })
120
192
 
121
- export function SelectGroup({ label, className, children }: {
193
+ export type SelectGroupProps = Omit<ComponentProps<typeof SelectPrimitive.Group>, 'className'> & {
122
194
  label?: string
123
195
  className?: string
124
- children?: ReactNode
125
- }) {
196
+ }
197
+
198
+ export function SelectGroup({ label, className, children, ...rest }: SelectGroupProps) {
126
199
  return (
127
- <SelectPrimitive.Group className={selectGroup({ className })}>
200
+ <SelectPrimitive.Group {...rest} className={selectGroup({ className })}>
128
201
  {label && (
129
- <SelectPrimitive.Label className="text-caption text-fg-subtle px-2 pb-1 pt-2">
202
+ <SelectPrimitive.GroupLabel className="text-caption text-fg-subtle px-2 pb-1 pt-2">
130
203
  {label}
131
- </SelectPrimitive.Label>
204
+ </SelectPrimitive.GroupLabel>
132
205
  )}
133
206
  {children}
134
207
  </SelectPrimitive.Group>
@@ -142,18 +215,14 @@ const selectItem = tv({
142
215
  + ' data-disabled:text-fg-disabled data-disabled:pointer-events-none',
143
216
  })
144
217
 
145
- export function SelectItem({ value, disabled, className, children }: {
218
+ export type SelectItemProps = Omit<ComponentProps<typeof SelectPrimitive.Item>, 'className' | 'value'> & {
146
219
  value: string
147
- disabled?: boolean
148
220
  className?: string
149
- children?: ReactNode
150
- }) {
221
+ }
222
+
223
+ export function SelectItem({ className, children, ...rest }: SelectItemProps) {
151
224
  return (
152
- <SelectPrimitive.Item
153
- value={value}
154
- disabled={disabled}
155
- className={selectItem({ className })}
156
- >
225
+ <SelectPrimitive.Item {...rest} className={selectItem({ className })}>
157
226
  <span className="flex min-w-0 flex-col">
158
227
  {children}
159
228
  </span>
@@ -167,14 +236,12 @@ export function SelectItem({ value, disabled, className, children }: {
167
236
  )
168
237
  }
169
238
 
170
- const selectItemText = tv({ base: 'text-label text-fg' })
239
+ export type SelectItemTextProps = Omit<ComponentProps<typeof SelectPrimitive.ItemText>, 'className'> & {
240
+ className?: string
241
+ }
171
242
 
172
- export function SelectItemText({ className, children }: { className?: string, children?: ReactNode }) {
173
- return (
174
- <SelectPrimitive.ItemText className={selectItemText({ className })}>
175
- {children}
176
- </SelectPrimitive.ItemText>
177
- )
243
+ export function SelectItemText({ className: _className, render = <span />, ...rest }: SelectItemTextProps) {
244
+ return <SelectPrimitive.ItemText {...rest} render={render} />
178
245
  }
179
246
 
180
247
  const selectItemDescription = tv({ base: 'text-caption text-fg-subtle' })
@@ -189,6 +256,10 @@ export function SelectItemDescription({ className, children }: { className?: str
189
256
 
190
257
  const selectSeparator = tv({ base: 'bg-line -mx-1 my-1 h-px' })
191
258
 
192
- export function SelectSeparator({ className }: { className?: string }) {
193
- return <SelectPrimitive.Separator className={selectSeparator({ className })} />
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 })} />
194
265
  }
package/switch/index.tsx CHANGED
@@ -1,13 +1,13 @@
1
1
  'use client'
2
2
 
3
- import { Switch as SwitchPrimitive } from 'radix-ui'
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-bg-well ring-1 ring-inset ring-line-strong'
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 interface SwitchProps extends ComponentProps<typeof SwitchPrimitive.Root> {
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({ color, className, ...rest }: SwitchProps) {
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
@@ -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 transition-state'
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 }) {
@@ -3,13 +3,13 @@
3
3
  bottom: 0;
4
4
  height: 2px;
5
5
  background: var(--color-fg);
6
- width: var(--tabs-indicator-size);
7
- translate: var(--tabs-indicator-position) 0;
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-state="inactive"] { display: none; }
12
+ .content[data-hidden] { display: none; }
13
13
 
14
14
  @media (prefers-reduced-motion: reduce) {
15
15
  .underline { transition: none; }
package/tabs/index.tsx CHANGED
@@ -1,73 +1,31 @@
1
1
  'use client'
2
2
 
3
- import { Tabs as TabsPrimitive } from 'radix-ui'
4
- import { useEffect, useRef, type ReactNode } from 'react'
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
- const ACTIVE = '[data-state="active"]'
11
-
12
- export interface TabsProps {
13
- value?: string
14
- defaultValue?: string
15
- onValueChange?: (value: string) => void
16
- className?: string
17
- children?: ReactNode
18
- }
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({ value, defaultValue, onValueChange, className, children }: TabsProps) {
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 function TabsList({ className, children }: { className?: string, children?: ReactNode }) {
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
- className={tabsList({ className })}
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
- <span ref={bar} className={s.underline} />
28
+ <TabsPrimitive.Indicator className={s.underline} />
71
29
  </TabsPrimitive.List>
72
30
  </div>
73
31
  )
@@ -76,41 +34,29 @@ export function TabsList({ className, children }: { className?: string, children
76
34
  const tabsTrigger = tv({
77
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 enabled:hover:text-fg'
80
- + ' disabled:text-fg-disabled disabled:cursor-not-allowed'
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'
81
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 function TabsTrigger({ value, disabled, className, children }: {
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
- )
47
+ }
48
+
49
+ export function TabsTrigger({ className, ...rest }: TabsTriggerProps) {
50
+ return <TabsPrimitive.Tab {...rest} className={tabsTrigger({ className })} />
101
51
  }
102
52
 
103
53
  const tabsContent = tv({ base: s.content })
104
54
 
105
- export function TabsContent({ value, forceMount, className, children }: {
55
+ export type TabsContentProps = Omit<ComponentProps<typeof TabsPrimitive.Panel>, 'className' | 'value'> & {
106
56
  value: string
107
- forceMount?: boolean
108
57
  className?: string
109
- children?: ReactNode
110
- }) {
111
- return (
112
- <TabsPrimitive.Content value={value} forceMount={forceMount || undefined} className={tabsContent({ className })}>
113
- {children}
114
- </TabsPrimitive.Content>
115
- )
58
+ }
59
+
60
+ export function TabsContent({ className, ...rest }: TabsContentProps) {
61
+ return <TabsPrimitive.Panel {...rest} className={tabsContent({ className })} />
116
62
  }
@@ -1,4 +1,7 @@
1
- import type { TextareaHTMLAttributes } from 'react'
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
 
@@ -27,11 +30,20 @@ export const textarea = tv({
27
30
  defaultVariants: { surface: 'bare', size: 'md' },
28
31
  })
29
32
 
30
- export interface TextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
31
- surface?: 'bare' | 'well'
32
- size?: 'sm' | 'md' | 'lg'
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 <textarea {...rest} rows={rows} className={textarea({ surface, size, className })} />
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
  }