@uselets/ui 1.20.4 → 2.0.1
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 +309 -426
- package/dist/as-child-Dxg3Xo1G.d.ts +41 -0
- package/dist/cli/uselets.mjs +5 -4
- package/dist/cli/uselets.mjs.map +1 -1
- package/dist/components/accordion.d.ts +12 -8
- package/dist/components/accordion.js +1 -1
- package/dist/components/accordion.js.map +1 -1
- package/dist/components/alert-dialog.d.ts +22 -15
- package/dist/components/alert-dialog.js +1 -1
- package/dist/components/alert-dialog.js.map +1 -1
- package/dist/components/alert.d.ts +1 -1
- package/dist/components/anchor.d.ts +3 -5
- package/dist/components/anchor.js +1 -1
- package/dist/components/anchor.js.map +1 -1
- package/dist/components/area-chart.d.ts +54 -0
- package/dist/components/area-chart.js +1 -1
- package/dist/components/area-chart.js.map +1 -1
- package/dist/components/avatar.d.ts +12 -7
- package/dist/components/avatar.js +1 -1
- package/dist/components/avatar.js.map +1 -1
- package/dist/components/badge.d.ts +3 -5
- package/dist/components/badge.js +1 -1
- package/dist/components/badge.js.map +1 -1
- package/dist/components/bar-chart.d.ts +48 -0
- package/dist/components/bar-chart.js +1 -1
- package/dist/components/bar-chart.js.map +1 -1
- package/dist/components/bottom-nav.d.ts +7 -3
- package/dist/components/bottom-nav.js +1 -1
- package/dist/components/bottom-nav.js.map +1 -1
- package/dist/components/breadcrumb.d.ts +3 -4
- package/dist/components/breadcrumb.js +1 -1
- package/dist/components/breadcrumb.js.map +1 -1
- package/dist/components/button-group.d.ts +3 -4
- package/dist/components/button-group.js +1 -1
- package/dist/components/button-group.js.map +1 -1
- package/dist/components/button.d.ts +8 -4
- package/dist/components/button.js +1 -1
- package/dist/components/button.js.map +1 -1
- package/dist/components/calendar.d.ts +4 -0
- package/dist/components/calendar.js +1 -1
- package/dist/components/calendar.js.map +1 -1
- package/dist/components/card.d.ts +4 -0
- package/dist/components/card.js.map +1 -1
- package/dist/components/carousel.d.ts +11 -3
- package/dist/components/carousel.js +1 -1
- package/dist/components/carousel.js.map +1 -1
- package/dist/components/center.d.ts +1 -1
- package/dist/components/chart.js +4 -4
- package/dist/components/checkbox.d.ts +14 -4
- package/dist/components/checkbox.js +1 -1
- package/dist/components/checkbox.js.map +1 -1
- package/dist/components/collapsible.d.ts +8 -7
- package/dist/components/collapsible.js +1 -1
- package/dist/components/collapsible.js.map +1 -1
- package/dist/components/combobox.d.ts +48 -26
- package/dist/components/combobox.js +1 -1
- package/dist/components/combobox.js.map +1 -1
- package/dist/components/command.d.ts +16 -2
- package/dist/components/command.js +1 -1
- package/dist/components/command.js.map +1 -1
- package/dist/components/container.d.ts +1 -1
- package/dist/components/data-table.d.ts +39 -0
- package/dist/components/data-table.js +1 -1
- package/dist/components/data-table.js.map +1 -1
- package/dist/components/date-range-picker.d.ts +24 -2
- package/dist/components/date-range-picker.js +1 -1
- package/dist/components/date-range-picker.js.map +1 -1
- package/dist/components/dialog.d.ts +35 -15
- package/dist/components/dialog.js +1 -1
- package/dist/components/dialog.js.map +1 -1
- package/dist/components/direction.d.ts +7 -0
- package/dist/components/direction.js +1 -0
- package/dist/components/donut-chart.d.ts +12 -0
- package/dist/components/donut-chart.js +1 -1
- package/dist/components/donut-chart.js.map +1 -1
- package/dist/components/drawer.d.ts +33 -15
- package/dist/components/drawer.js +1 -1
- package/dist/components/drawer.js.map +1 -1
- package/dist/components/dropdown-menu.d.ts +36 -26
- package/dist/components/dropdown-menu.js +1 -1
- package/dist/components/dropdown-menu.js.map +1 -1
- package/dist/components/dropzone.d.ts +19 -0
- package/dist/components/dropzone.js +1 -1
- package/dist/components/dropzone.js.map +1 -1
- package/dist/components/field.d.ts +16 -4
- package/dist/components/field.js +1 -1
- package/dist/components/field.js.map +1 -1
- package/dist/components/flex.d.ts +1 -1
- package/dist/components/grid.d.ts +13 -0
- package/dist/components/grid.js.map +1 -1
- package/dist/components/group.d.ts +1 -1
- package/dist/components/heading.d.ts +3 -5
- package/dist/components/heading.js +1 -1
- package/dist/components/heading.js.map +1 -1
- package/dist/components/hover-card.d.ts +7 -6
- package/dist/components/hover-card.js +1 -1
- package/dist/components/hover-card.js.map +1 -1
- package/dist/components/input-group.d.ts +2 -2
- package/dist/components/input-group.js +1 -1
- package/dist/components/input-group.js.map +1 -1
- package/dist/components/input-otp.d.ts +4 -2
- package/dist/components/input-otp.js +1 -1
- package/dist/components/input-otp.js.map +1 -1
- package/dist/components/input.d.ts +1 -1
- package/dist/components/input.js +1 -1
- package/dist/components/input.js.map +1 -1
- package/dist/components/label.d.ts +3 -4
- package/dist/components/label.js +1 -1
- package/dist/components/label.js.map +1 -1
- package/dist/components/list.d.ts +8 -5
- package/dist/components/list.js +1 -1
- package/dist/components/list.js.map +1 -1
- package/dist/components/logo.d.ts +113 -0
- package/dist/components/marker.d.ts +4 -5
- package/dist/components/marker.js +1 -1
- package/dist/components/marker.js.map +1 -1
- package/dist/components/mask-input.d.ts +6 -0
- package/dist/components/mask-input.js.map +1 -1
- package/dist/components/native-select.d.ts +5 -1
- package/dist/components/native-select.js +1 -1
- package/dist/components/native-select.js.map +1 -1
- package/dist/components/navigation-menu.d.ts +19 -20
- package/dist/components/navigation-menu.js +1 -1
- package/dist/components/navigation-menu.js.map +1 -1
- package/dist/components/number-field.d.ts +18 -13
- package/dist/components/number-field.js +1 -1
- package/dist/components/number-field.js.map +1 -1
- package/dist/components/pagination.d.ts +9 -0
- package/dist/components/pagination.js +1 -1
- package/dist/components/pagination.js.map +1 -1
- package/dist/components/phone-input.d.ts +3 -0
- package/dist/components/phone-input.js +1 -1
- package/dist/components/phone-input.js.map +1 -1
- package/dist/components/pie-chart.d.ts +53 -0
- package/dist/components/pie-chart.js +1 -1
- package/dist/components/pie-chart.js.map +1 -1
- package/dist/components/popover.d.ts +13 -12
- package/dist/components/popover.js +1 -1
- package/dist/components/popover.js.map +1 -1
- package/dist/components/progress.d.ts +9 -3
- package/dist/components/progress.js +1 -1
- package/dist/components/progress.js.map +1 -1
- package/dist/components/radio-group.d.ts +20 -6
- package/dist/components/radio-group.js +1 -1
- package/dist/components/radio-group.js.map +1 -1
- package/dist/components/resizable.d.ts +1 -0
- package/dist/components/resizable.js.map +1 -1
- package/dist/components/ring-progress.d.ts +9 -1
- package/dist/components/ring-progress.js.map +1 -1
- package/dist/components/scroll-area.d.ts +3 -3
- package/dist/components/scroll-area.js +1 -1
- package/dist/components/scroll-area.js.map +1 -1
- package/dist/components/select.d.ts +25 -20
- package/dist/components/select.js +1 -1
- package/dist/components/select.js.map +1 -1
- package/dist/components/separator.d.ts +9 -4
- package/dist/components/separator.js +1 -1
- package/dist/components/separator.js.map +1 -1
- package/dist/components/sidebar.d.ts +63 -18
- package/dist/components/sidebar.js +1 -1
- package/dist/components/sidebar.js.map +1 -1
- package/dist/components/simple-grid.d.ts +7 -0
- package/dist/components/simple-grid.js +1 -1
- package/dist/components/simple-grid.js.map +1 -1
- package/dist/components/skeleton.d.ts +4 -0
- package/dist/components/skeleton.js.map +1 -1
- package/dist/components/slider.d.ts +2 -2
- package/dist/components/slider.js +1 -1
- package/dist/components/slider.js.map +1 -1
- package/dist/components/spinner.d.ts +1 -1
- package/dist/components/stack.d.ts +1 -1
- package/dist/components/stepper.d.ts +46 -25
- package/dist/components/stepper.js +1 -1
- package/dist/components/stepper.js.map +1 -1
- package/dist/components/switch.d.ts +17 -3
- package/dist/components/switch.js +1 -1
- package/dist/components/switch.js.map +1 -1
- package/dist/components/table.d.ts +10 -1
- package/dist/components/table.js +1 -1
- package/dist/components/table.js.map +1 -1
- package/dist/components/tabs.d.ts +14 -10
- package/dist/components/tabs.js +1 -1
- package/dist/components/tabs.js.map +1 -1
- package/dist/components/text.d.ts +3 -5
- package/dist/components/text.js +1 -1
- package/dist/components/text.js.map +1 -1
- package/dist/components/textarea.d.ts +1 -1
- package/dist/components/textarea.js +1 -1
- package/dist/components/textarea.js.map +1 -1
- package/dist/components/theme.d.ts +12 -0
- package/dist/components/theme.js +1 -1
- package/dist/components/theme.js.map +1 -1
- package/dist/components/timeline.d.ts +25 -1
- package/dist/components/timeline.js +1 -1
- package/dist/components/timeline.js.map +1 -1
- package/dist/components/toggle-group.d.ts +16 -6
- package/dist/components/toggle-group.js +1 -1
- package/dist/components/toggle-group.js.map +1 -1
- package/dist/components/toggle.d.ts +4 -4
- package/dist/components/toggle.js +1 -1
- package/dist/components/toggle.js.map +1 -1
- package/dist/components/tooltip.d.ts +15 -8
- package/dist/components/tooltip.js +1 -1
- package/dist/components/tooltip.js.map +1 -1
- package/dist/components/unstyled-button.d.ts +3 -5
- package/dist/components/unstyled-button.js +1 -1
- package/dist/components/unstyled-button.js.map +1 -1
- package/dist/components/visibility.d.ts +2 -0
- package/dist/components/visibility.js +1 -1
- package/dist/components/visibility.js.map +1 -1
- package/dist/hooks/use-breakpoint-value.d.ts +1 -0
- package/dist/hooks/use-breakpoint-value.js.map +1 -1
- package/dist/hooks/use-breakpoint.d.ts +1 -0
- package/dist/hooks/use-breakpoint.js.map +1 -1
- package/dist/hooks/use-disclosure.d.ts +3 -0
- package/dist/hooks/use-disclosure.js +1 -1
- package/dist/hooks/use-disclosure.js.map +1 -1
- package/dist/hooks/use-document-visibility.d.ts +1 -0
- package/dist/hooks/use-document-visibility.js.map +1 -1
- package/dist/hooks/use-hotkeys.d.ts +1 -1
- package/dist/hooks/use-idle.d.ts +9 -0
- package/dist/hooks/use-idle.js.map +1 -1
- package/dist/hooks/use-interval.d.ts +1 -0
- package/dist/hooks/use-interval.js.map +1 -1
- package/dist/hooks/use-local-storage.d.ts +6 -0
- package/dist/hooks/use-local-storage.js +1 -1
- package/dist/hooks/use-local-storage.js.map +1 -1
- package/dist/hooks/use-mask.d.ts +1 -0
- package/dist/hooks/use-mask.js.map +1 -1
- package/dist/hooks/use-mobile.d.ts +1 -0
- package/dist/hooks/use-mobile.js.map +1 -1
- package/dist/index.d.ts +7 -5
- package/dist/index.js +1 -1
- package/dist/lib/as-child.d.ts +2 -0
- package/dist/lib/as-child.js +2 -0
- package/dist/lib/as-child.js.map +1 -0
- package/dist/lib/utils.d.ts +1 -1
- package/dist/logo--R3_XDgA.js.map +1 -1
- package/dist/resolve-class-name-BurP8ngI.js +2 -0
- package/dist/resolve-class-name-BurP8ngI.js.map +1 -0
- package/dist/skills/uselets-ui/SKILL.md +8 -3
- package/dist/skills/uselets-ui/references/component-catalog.md +32 -17
- package/dist/skills/uselets-ui/references/migration-v1-to-v2.md +599 -0
- package/dist/skills/uselets-ui/references/package-guide.md +7 -7
- package/dist/styles/base.css +21 -0
- package/dist/{use-hotkeys-DOjcX9Tt.d.ts → use-hotkeys-D1_TEIwN.d.ts} +2 -1
- package/dist/use-hotkeys-YDJmGWX6.js.map +1 -1
- package/package.json +16 -17
- package/skills/uselets-ui/SKILL.md +8 -3
- package/skills/uselets-ui/references/component-catalog.md +32 -17
- package/skills/uselets-ui/references/migration-v1-to-v2.md +599 -0
- package/skills/uselets-ui/references/package-guide.md +7 -7
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
# Migrating `@uselets/ui` v1 → v2
|
|
2
|
+
|
|
3
|
+
v2 replaces `radix-ui` with Base UI (`@base-ui/react`) under every interactive component. Visual design and
|
|
4
|
+
runtime behavior are intended to stay the same; what changes is the **component API** wherever Radix and Base UI
|
|
5
|
+
differ, plus three intentional visual changes: the `Switch`/`Checkbox`/`RadioGroupItem` size scale (section 14),
|
|
6
|
+
the `Dialog` default size (section 17), and invalid `Checkbox`/`Switch`/`RadioGroupItem` no longer turning red
|
|
7
|
+
(section 19).
|
|
8
|
+
|
|
9
|
+
Use this guide when an app upgrades to `@uselets/ui@2`, or when you see v1-only APIs (the "Detect" patterns
|
|
10
|
+
below) in code that imports from `@uselets/ui`.
|
|
11
|
+
|
|
12
|
+
## Contents
|
|
13
|
+
|
|
14
|
+
1. [How to run the migration](#how-to-run-the-migration)
|
|
15
|
+
2. [Polymorphism: `asChild` only](#1-polymorphism-aschild-only)
|
|
16
|
+
3. [Change callbacks receive `eventDetails`](#2-change-callbacks-receive-eventdetails)
|
|
17
|
+
4. [Accordion](#3-accordion)
|
|
18
|
+
5. [ToggleGroup](#4-togglegroup)
|
|
19
|
+
6. [Checkbox indeterminate](#5-checkbox-indeterminate)
|
|
20
|
+
7. [Tooltip](#6-tooltip)
|
|
21
|
+
8. [Select](#7-select)
|
|
22
|
+
9. [Popover](#8-popover)
|
|
23
|
+
10. [NavigationMenu](#9-navigationmenu)
|
|
24
|
+
11. [DropdownMenu](#10-dropdownmenu)
|
|
25
|
+
12. [HoverCard](#11-hovercard)
|
|
26
|
+
13. [Progress](#12-progress)
|
|
27
|
+
14. [Slider](#13-slider)
|
|
28
|
+
15. [Switch, Checkbox and RadioGroupItem sizes](#14-switch-checkbox-and-radiogroupitem-sizes)
|
|
29
|
+
16. [Styling against component state (CSS selectors and variables)](#15-styling-against-component-state)
|
|
30
|
+
17. [Tests](#16-tests)
|
|
31
|
+
18. [Dialog default size](#17-dialog-default-size)
|
|
32
|
+
19. [Removed and renamed props](#18-removed-and-renamed-props)
|
|
33
|
+
20. [Other behavior changes](#19-other-behavior-changes)
|
|
34
|
+
21. [Unchanged APIs](#unchanged-apis)
|
|
35
|
+
|
|
36
|
+
## How to run the migration
|
|
37
|
+
|
|
38
|
+
1. Bump `@uselets/ui` to `^2`. Remove any direct `radix-ui` / `@radix-ui/*` dependency the app only had for
|
|
39
|
+
`@uselets/ui` (the package no longer depends on it).
|
|
40
|
+
2. Run the app's typecheck first. Most breaking changes are prop renames or type changes, so `tsc` points at
|
|
41
|
+
almost every call site. Fix those using the sections below.
|
|
42
|
+
3. Then grep for the patterns listed under each **Detect** heading — some changes still type-check but behave
|
|
43
|
+
differently (for example `SelectValue` showing the raw value, `data-[state=...]` selectors silently no longer
|
|
44
|
+
matching, or tests that open menus with `pointerDown`).
|
|
45
|
+
4. Run the app's tests and check the affected screens visually (popovers, menus, dialogs, selects, switches).
|
|
46
|
+
|
|
47
|
+
Do the mechanical rewrites exactly as shown; don't re-architect the surrounding code while migrating.
|
|
48
|
+
|
|
49
|
+
## 1. Polymorphism: `asChild` only
|
|
50
|
+
|
|
51
|
+
`asChild` is supported on triggers, closes, items, links, labels, titles/descriptions, contents/popups, overlays,
|
|
52
|
+
`Label`, `Toggle`, `ToggleGroupItem`, `TabsTrigger`, `AccordionTrigger`, `SelectTrigger`, `NavigationMenuTrigger`,
|
|
53
|
+
`AlertDialogAction`/`AlertDialogCancel`, and so on. Semantics: pass exactly one child element; the part's props,
|
|
54
|
+
classes, event handlers and ref are merged into it, and the child replaces the part's content.
|
|
55
|
+
`AlertDialogAction`/`AlertDialogCancel` with `asChild` keep the `Button` variant/color/size styles on the child.
|
|
56
|
+
|
|
57
|
+
Parts whose internal structure is required for them to work no longer accept `asChild`: `ScrollArea`, `ScrollBar`, `SelectContent`, `SelectItem`, `AccordionContent`, `Checkbox`, `Switch`, `RadioGroupItem`, `Progress`, `Slider`, `NavigationMenu` and `NumberFieldScrubArea`. In v1
|
|
58
|
+
these accepted `asChild` through Radix but broke or ignored the child when it was used. Render the part normally
|
|
59
|
+
and put your element inside it (or around it) instead.
|
|
60
|
+
|
|
61
|
+
- **Detect:** `asChild` on any of the parts above.
|
|
62
|
+
|
|
63
|
+
Base UI's own `render` prop is **not** part of the public API — don't introduce it. This now includes `Combobox`
|
|
64
|
+
and `NumberField`, which were already Base UI-based in v1 and accepted `render`: their parts take `asChild`
|
|
65
|
+
instead (`ComboboxTrigger`, `ComboboxItem`, `ComboboxList`, `ComboboxGroup`, `ComboboxLabel`, `ComboboxEmpty`,
|
|
66
|
+
`ComboboxSeparator`, `ComboboxContent`, `ComboboxChips`, `ComboboxChip`, `NumberFieldGroup`,
|
|
67
|
+
`NumberFieldIncrement`, `NumberFieldDecrement`). `ComboboxInput`, `ComboboxClear`, `ComboboxChipsInput`,
|
|
68
|
+
`NumberField`, `NumberFieldInput` and `NumberFieldScrubArea` accept neither.
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
// v1
|
|
72
|
+
<ComboboxTrigger render={<Button variant="outline" />} />
|
|
73
|
+
// v2
|
|
74
|
+
<ComboboxTrigger asChild>
|
|
75
|
+
<Button variant="outline">…</Button>
|
|
76
|
+
</ComboboxTrigger>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
- **Detect:** `render=` on any `@uselets/ui` component.
|
|
80
|
+
|
|
81
|
+
New gotcha: parts that render a native `<button>` (`DialogTrigger`, `PopoverTrigger`, `DropdownMenuTrigger`,
|
|
82
|
+
`CollapsibleTrigger`, `AlertDialogTrigger`, `DrawerTrigger`, `DialogClose`, `TabsTrigger`, `AccordionTrigger`,
|
|
83
|
+
`Toggle`, `ToggleGroupItem`, `SelectTrigger`, `AlertDialogAction`/`AlertDialogCancel`, …) assume their `asChild`
|
|
84
|
+
child is a `<button>`. If the child is something else (`<a>`, `<div>`, a router `Link`), also pass
|
|
85
|
+
`nativeButton={false}` so the part adds the right keyboard/ARIA behavior. `TooltipTrigger` and `HoverCardTrigger`
|
|
86
|
+
don't need it.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
// v2
|
|
90
|
+
<PopoverTrigger asChild nativeButton={false}>
|
|
91
|
+
<a href="#details">Details</a>
|
|
92
|
+
</PopoverTrigger>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
- **Detect:** `asChild` on a part whose child is not a `<button>`/`Button`/`UnstyledButton`.
|
|
96
|
+
|
|
97
|
+
The `asChild` child must be the element that should receive the part's props. A wrapper component that doesn't
|
|
98
|
+
forward its props to the element it renders (for example `Tooltip`, which passes extra props to its popup) swallows
|
|
99
|
+
the trigger's handlers and ref, so the part never opens. Nest the wrapper outside the part instead:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
// v1
|
|
103
|
+
<DialogTrigger asChild>
|
|
104
|
+
<Tooltip label="Test data">
|
|
105
|
+
<Button size="icon">…</Button>
|
|
106
|
+
</Tooltip>
|
|
107
|
+
</DialogTrigger>
|
|
108
|
+
|
|
109
|
+
// v2
|
|
110
|
+
<Tooltip label="Test data">
|
|
111
|
+
<DialogTrigger asChild>
|
|
112
|
+
<Button size="icon">…</Button>
|
|
113
|
+
</DialogTrigger>
|
|
114
|
+
</Tooltip>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
- **Detect:** `asChild` whose child is `Tooltip` or another wrapper component instead of the clickable element.
|
|
118
|
+
|
|
119
|
+
## 2. Change callbacks receive `eventDetails`
|
|
120
|
+
|
|
121
|
+
`onOpenChange`, `onValueChange`, `onCheckedChange`, `onPressedChange`, etc. of Base UI-backed components now
|
|
122
|
+
receive `(value, eventDetails)`. Package-level callbacks that aren't Base UI parts (`DateRangePicker`,
|
|
123
|
+
`SidebarProvider`, `Stepper`) keep their v1 signatures. Passing a state setter directly still works when the
|
|
124
|
+
state already has the v2 value type; the extra argument is ignored. It no longer type-checks where the value type
|
|
125
|
+
changed: `Select` (`string | null`, section 7), `Accordion` and `ToggleGroup` (`string[]`, sections 3 and 4) and
|
|
126
|
+
`Slider` (`number | readonly number[]`, section 13).
|
|
127
|
+
|
|
128
|
+
```tsx
|
|
129
|
+
const [reason, setReason] = useState('')
|
|
130
|
+
// v1
|
|
131
|
+
<Select value={reason} onValueChange={setReason}>…</Select>
|
|
132
|
+
// v2
|
|
133
|
+
<Select value={reason} onValueChange={(value) => value !== null && setReason(value)}>…</Select>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Otherwise, only code that relies on the argument count, or tests asserting exact arguments, need updates:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
// v1
|
|
140
|
+
expect(onValueChange).toHaveBeenCalledWith('banana')
|
|
141
|
+
// v2
|
|
142
|
+
expect(onValueChange).toHaveBeenCalledWith('banana', expect.anything())
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`eventDetails.reason` (`'escape-key'`, `'outside-press'`, `'trigger-press'`, …) and `eventDetails.cancel()` let
|
|
146
|
+
you veto a change. You rarely need this: `Dialog`/`Drawer` still have `dismissible={false}`. To replace a v1
|
|
147
|
+
`onInteractOutside={(event) => event.preventDefault()}`, cancel both outside reasons:
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
<Popover
|
|
151
|
+
onOpenChange={(open, eventDetails) => {
|
|
152
|
+
if (!open && ['outside-press', 'focus-out'].includes(eventDetails.reason)) eventDetails.cancel()
|
|
153
|
+
}}
|
|
154
|
+
>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## 3. Accordion
|
|
158
|
+
|
|
159
|
+
`type="single" | "multiple"` and `collapsible` are gone. The accordion is single-item by default (items can
|
|
160
|
+
always be collapsed). Set `multiple` to allow several open items. `value`/`defaultValue` are **always arrays**.
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
// v1
|
|
164
|
+
<Accordion type="single" collapsible defaultValue="item-1">…</Accordion>
|
|
165
|
+
<Accordion type="multiple" defaultValue={['a', 'b']}>…</Accordion>
|
|
166
|
+
|
|
167
|
+
// v2
|
|
168
|
+
<Accordion defaultValue={['item-1']}>…</Accordion>
|
|
169
|
+
<Accordion multiple defaultValue={['a', 'b']}>…</Accordion>
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
- **Detect:** `<Accordion` with `type=` or `collapsible`; a string `value`/`defaultValue`.
|
|
173
|
+
- A disabled `AccordionTrigger` now uses `aria-disabled="true"` instead of the native `disabled` attribute.
|
|
174
|
+
- Arrow keys, `Home` and `End` no longer move focus between triggers; `Tab` moves through them like regular
|
|
175
|
+
buttons. `orientation` and `loopFocus` still type-check but are deprecated by Base UI and do nothing: remove them.
|
|
176
|
+
- **Detect:** `orientation=`, `loop` or `loopFocus` on `<Accordion`.
|
|
177
|
+
|
|
178
|
+
## 4. ToggleGroup
|
|
179
|
+
|
|
180
|
+
Same shape as Accordion: no `type`. Single selection is the default, and `multiple` enables multi-selection.
|
|
181
|
+
`value`/`defaultValue`/`onValueChange` always use `string[]`.
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
// v1
|
|
185
|
+
const [view, setView] = useState('board')
|
|
186
|
+
<ToggleGroup type="single" value={view} onValueChange={setView}>…</ToggleGroup>
|
|
187
|
+
|
|
188
|
+
// v2
|
|
189
|
+
const [view, setView] = useState(['board'])
|
|
190
|
+
<ToggleGroup value={view} onValueChange={setView}>…</ToggleGroup>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
- **Detect:** `<ToggleGroup` with `type=`; string state passed to `value`.
|
|
194
|
+
- Items are now toggle buttons (`aria-pressed`, `data-pressed`). They no longer use `role="radio"`/`aria-checked`
|
|
195
|
+
or `data-state="on"`.
|
|
196
|
+
|
|
197
|
+
## 5. Checkbox indeterminate
|
|
198
|
+
|
|
199
|
+
`checked="indeterminate"` / `defaultChecked="indeterminate"` is no longer accepted. Use the boolean
|
|
200
|
+
`indeterminate` prop next to a boolean `checked`. `onCheckedChange` always receives a boolean, so drop
|
|
201
|
+
`Boolean(value)` / `value === true` coercions.
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
// v1
|
|
205
|
+
<Checkbox
|
|
206
|
+
checked={allSelected || (someSelected && 'indeterminate')}
|
|
207
|
+
onCheckedChange={(value) => toggleAll(Boolean(value))}
|
|
208
|
+
/>
|
|
209
|
+
|
|
210
|
+
// v2
|
|
211
|
+
<Checkbox
|
|
212
|
+
checked={allSelected}
|
|
213
|
+
indeterminate={someSelected && !allSelected}
|
|
214
|
+
onCheckedChange={(value) => toggleAll(value)}
|
|
215
|
+
/>
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
- **Detect:** `'indeterminate'` string literal near `Checkbox`; `data-state="indeterminate"`.
|
|
219
|
+
|
|
220
|
+
## 6. Tooltip
|
|
221
|
+
|
|
222
|
+
| v1 | v2 |
|
|
223
|
+
| --- | --- |
|
|
224
|
+
| `<TooltipProvider delayDuration={n}>` | `<TooltipProvider delay={n}>` (also `closeDelay`, `timeout`) |
|
|
225
|
+
| `<Tooltip delayDuration={n}>` | `<Tooltip delay={n} closeDelay={m}>` (forwarded to the trigger) |
|
|
226
|
+
| `<Tooltip disableHoverableContent>` | `<Tooltip disableHoverablePopup>` |
|
|
227
|
+
| `<TooltipRoot delayDuration={n}>` | put `delay` on `TooltipTrigger` instead |
|
|
228
|
+
|
|
229
|
+
`Tooltip label`, `disabled`, `TooltipRoot`, `TooltipTrigger asChild`, and `TooltipContent`
|
|
230
|
+
(`side`/`sideOffset`/`align`/`alignOffset`) are unchanged.
|
|
231
|
+
|
|
232
|
+
- **Detect:** `delayDuration`, `disableHoverableContent`.
|
|
233
|
+
|
|
234
|
+
## 7. Select
|
|
235
|
+
|
|
236
|
+
- `SelectValue` renders the **raw value** unless `Select` receives `items`. Whenever an item's label differs from
|
|
237
|
+
its value, pass `items` (array of `{ value, label }` or a `value → label` record). If value and label are
|
|
238
|
+
identical (for example page sizes `'10'`, `'20'`), nothing to do. Neither `tsc` nor tests flag a missing
|
|
239
|
+
`items`: the trigger just shows `READY_FOR_PICKUP` instead of `Ready for pickup`. Review **every** `<Select`.
|
|
240
|
+
- `SelectContent`'s `position="item-aligned" | "popper"` → `alignItemWithTrigger` (default `true`, the old
|
|
241
|
+
item-aligned behavior). `position="popper"` → `alignItemWithTrigger={false}`.
|
|
242
|
+
- `onValueChange` may receive `null`. Guard before converting: `value !== null && setPageSize(Number(value))`.
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
// v1
|
|
246
|
+
<Select defaultValue="design-system">
|
|
247
|
+
…<SelectItem value="design-system">Design System</SelectItem>…
|
|
248
|
+
</Select>
|
|
249
|
+
|
|
250
|
+
// v2: keep one { value, label } list and use it for both `items` and the rendered items
|
|
251
|
+
const categories = [{ value: 'design-system', label: 'Design System' }]
|
|
252
|
+
<Select items={categories} defaultValue="design-system">
|
|
253
|
+
<SelectTrigger>
|
|
254
|
+
<SelectValue />
|
|
255
|
+
</SelectTrigger>
|
|
256
|
+
<SelectContent>
|
|
257
|
+
{categories.map((category) => (
|
|
258
|
+
<SelectItem key={category.value} value={category.value}>
|
|
259
|
+
{category.label}
|
|
260
|
+
</SelectItem>
|
|
261
|
+
))}
|
|
262
|
+
</SelectContent>
|
|
263
|
+
</Select>
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
When the list is built inline or the label is derived (`{item.displayName}`, `{format(item)}`), map it to
|
|
267
|
+
`{ value, label }` for `items` with the same expression used in the `SelectItem`.
|
|
268
|
+
|
|
269
|
+
- **Detect:** `<Select` without `items` whose `SelectItem` content is anything other than the literal value
|
|
270
|
+
(`{item.label}`, `{option.name}`, a hard-coded label); `position=` on `SelectContent`; `Number(value)` / string
|
|
271
|
+
methods on `onValueChange` values.
|
|
272
|
+
|
|
273
|
+
## 8. Popover
|
|
274
|
+
|
|
275
|
+
`PopoverAnchor` was removed. To position against something other than the trigger, pass `anchor` (element,
|
|
276
|
+
ref, or virtual element) to `PopoverContent`.
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
// v1
|
|
280
|
+
<Popover>
|
|
281
|
+
<PopoverAnchor><div ref={…}>Anchor</div></PopoverAnchor>
|
|
282
|
+
<PopoverTrigger>Open</PopoverTrigger>
|
|
283
|
+
<PopoverContent>…</PopoverContent>
|
|
284
|
+
</Popover>
|
|
285
|
+
|
|
286
|
+
// v2
|
|
287
|
+
const anchorRef = useRef<HTMLDivElement>(null)
|
|
288
|
+
<div ref={anchorRef}>Anchor</div>
|
|
289
|
+
<Popover>
|
|
290
|
+
<PopoverTrigger>Open</PopoverTrigger>
|
|
291
|
+
<PopoverContent anchor={anchorRef}>…</PopoverContent>
|
|
292
|
+
</Popover>
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`PopoverTitle`/`PopoverDescription` now wire `aria-labelledby`/`aria-describedby` on the popup automatically.
|
|
296
|
+
|
|
297
|
+
`modal` only traps focus when a `PopoverClose` is rendered inside `PopoverContent`. Without one, it still blocks
|
|
298
|
+
outside clicks and page scroll, but `Tab` moves focus out of the popup. Add a `PopoverClose` (visually hidden if the
|
|
299
|
+
design has no close button):
|
|
300
|
+
|
|
301
|
+
```tsx
|
|
302
|
+
<Popover modal>
|
|
303
|
+
<PopoverTrigger>Open</PopoverTrigger>
|
|
304
|
+
<PopoverContent>
|
|
305
|
+
…
|
|
306
|
+
<PopoverClose className="sr-only">Close</PopoverClose>
|
|
307
|
+
</PopoverContent>
|
|
308
|
+
</Popover>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
- **Detect:** `PopoverAnchor`; `<Popover modal` whose content has no `PopoverClose`.
|
|
312
|
+
|
|
313
|
+
## 9. NavigationMenu
|
|
314
|
+
|
|
315
|
+
- The `viewport` prop and the `NavigationMenuViewport` export were removed. Content always renders through the
|
|
316
|
+
new `NavigationMenuPositioner` (portaled, added automatically by `NavigationMenu`). `NavigationMenu` accepts
|
|
317
|
+
`align` and forwards it to the positioner.
|
|
318
|
+
- `NavigationMenuIndicator` must be placed **inside** a `NavigationMenuItem` (next to its trigger). It throws
|
|
319
|
+
when rendered directly in the list.
|
|
320
|
+
- `NavigationMenuLink` keeps `active` (`data-active` + `aria-current="page"`) and supports `asChild` for router
|
|
321
|
+
links.
|
|
322
|
+
- `NavigationMenuLink` no longer closes the menu when clicked (`closeOnClick` defaults to `false`). With full page
|
|
323
|
+
loads nothing changes, but in client-side routing the popup stays open over the new page: pass `closeOnClick`.
|
|
324
|
+
`onSelect` still type-checks (it is the DOM text-selection event) but never fires on click: move its logic to
|
|
325
|
+
`onClick`, and drop `onSelect={(event) => event.preventDefault()}` (the menu already stays open).
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
// v1
|
|
329
|
+
<NavigationMenuLink asChild onSelect={() => track('nav')}>
|
|
330
|
+
<Link href="/orders">Orders</Link>
|
|
331
|
+
</NavigationMenuLink>
|
|
332
|
+
|
|
333
|
+
// v2
|
|
334
|
+
<NavigationMenuLink asChild closeOnClick onClick={() => track('nav')}>
|
|
335
|
+
<Link href="/orders">Orders</Link>
|
|
336
|
+
</NavigationMenuLink>
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
- Hover timing: `delayDuration` (default 200ms) → `delay` (default 50ms); `skipDelayDuration` was removed;
|
|
340
|
+
`closeDelay` (default 50ms) is new. Pass `delay={200}` to keep the v1 opening delay.
|
|
341
|
+
- The closed `value` is `null`, not `''`, and `onValueChange` receives `null` when the menu closes. Any non-`null`
|
|
342
|
+
value counts as open, so a controlled `value=""` keeps the menu open:
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
// v1
|
|
346
|
+
const [value, setValue] = useState('')
|
|
347
|
+
<NavigationMenu value={value} onValueChange={setValue}>…</NavigationMenu>
|
|
348
|
+
setValue('') // close
|
|
349
|
+
|
|
350
|
+
// v2
|
|
351
|
+
const [value, setValue] = useState<string | null>(null)
|
|
352
|
+
<NavigationMenu value={value} onValueChange={setValue}>…</NavigationMenu>
|
|
353
|
+
setValue(null) // close
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
- **Detect:** `NavigationMenuViewport`, `viewport={`, `NavigationMenuIndicator` outside an item; every
|
|
357
|
+
`NavigationMenuLink` used with client-side routing; `onSelect=` on `NavigationMenuLink`; `delayDuration` /
|
|
358
|
+
`skipDelayDuration` on `NavigationMenu`; a controlled `NavigationMenu` `value` that uses `''` for closed.
|
|
359
|
+
|
|
360
|
+
## 10. DropdownMenu
|
|
361
|
+
|
|
362
|
+
- `DropdownMenuItem` has no `onSelect`. Use `onClick`. The menu still closes after the click.
|
|
363
|
+
- `DropdownMenuCheckboxItem`/`DropdownMenuRadioItem` now keep the menu open when toggled (v1 closed it). Drop
|
|
364
|
+
any v1 `onSelect={(event) => event.preventDefault()}` used to keep it open; pass `closeOnClick` where the menu
|
|
365
|
+
should close after toggling.
|
|
366
|
+
- `DropdownMenuSub` / `DropdownMenuSubTrigger` / `DropdownMenuSubContent` keep their names.
|
|
367
|
+
- `DropdownMenuLabel` works inside or outside `DropdownMenuGroup`/`DropdownMenuRadioGroup`. Inside a group it
|
|
368
|
+
also labels the group for assistive tech.
|
|
369
|
+
- `DropdownMenuContent` keeps `side`, `sideOffset`, `align`, `alignOffset`.
|
|
370
|
+
|
|
371
|
+
```tsx
|
|
372
|
+
// v1
|
|
373
|
+
<DropdownMenuItem onSelect={() => logout()}>Sair</DropdownMenuItem>
|
|
374
|
+
// v2
|
|
375
|
+
<DropdownMenuItem onClick={() => logout()}>Sair</DropdownMenuItem>
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
- **Detect:** `onSelect=` on `DropdownMenuItem`/`CheckboxItem`/`RadioItem`.
|
|
379
|
+
|
|
380
|
+
## 11. HoverCard
|
|
381
|
+
|
|
382
|
+
Open/close timing moved from the root to the trigger: `<HoverCard openDelay closeDelay>` →
|
|
383
|
+
`<HoverCardTrigger delay closeDelay>`. The defaults stay at the v1 timing: `delay` `700`, `closeDelay` `300`.
|
|
384
|
+
|
|
385
|
+
- **Detect:** `openDelay` / `closeDelay` on `<HoverCard`.
|
|
386
|
+
|
|
387
|
+
## 12. Progress
|
|
388
|
+
|
|
389
|
+
Omitting `value` or passing `null` now renders the **indeterminate** state (`data-indeterminate`, no width on the
|
|
390
|
+
indicator), matching Radix semantics. v1 silently treated it as `0`. Pass `value={0}` if you want an empty
|
|
391
|
+
determinate bar.
|
|
392
|
+
|
|
393
|
+
## 13. Slider
|
|
394
|
+
|
|
395
|
+
`onValueChange` receives `number | readonly number[]` (an array when `value`/`defaultValue` is an array). A
|
|
396
|
+
`useState<number[]>` setter no longer type-checks directly:
|
|
397
|
+
|
|
398
|
+
```tsx
|
|
399
|
+
<Slider
|
|
400
|
+
value={value}
|
|
401
|
+
onValueChange={(next) => setValue(Array.isArray(next) ? [...next] : [next])}
|
|
402
|
+
/>
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## 14. Switch, Checkbox and RadioGroupItem sizes
|
|
406
|
+
|
|
407
|
+
`Switch` now uses the same height scale as `Checkbox` (14 / 16 / 20px). Every switch size moved **down one
|
|
408
|
+
step**:
|
|
409
|
+
|
|
410
|
+
| v1 size | rendered track | v2 size for the same look |
|
|
411
|
+
| --- | --- | --- |
|
|
412
|
+
| `sm` | 34×16px | `md` (new default) |
|
|
413
|
+
| `md` (default) | 42×20px | `lg` |
|
|
414
|
+
| `lg` | 48×22px | removed: use `lg` (42×20px) |
|
|
415
|
+
| — | — | new `sm` is 30×14px |
|
|
416
|
+
|
|
417
|
+
To keep a v1 screen pixel-identical: `size="sm"` → `size="md"` (or drop it), and no `size` / `size="md"` →
|
|
418
|
+
`size="lg"`. If the v1 code was just using defaults with no visual requirement, prefer the new default (`md`). It
|
|
419
|
+
now lines up with a default `Checkbox`.
|
|
420
|
+
|
|
421
|
+
Composed label text is now the same across `Switch`, `Checkbox` and `RadioGroupItem`: `sm` 12px, `md` 14px,
|
|
422
|
+
`lg` 18px. Before, checkbox/radio `lg` was 20px and switch labels were one step larger.
|
|
423
|
+
|
|
424
|
+
- **Detect:** `<Switch` (every usage: decide per the table above).
|
|
425
|
+
|
|
426
|
+
## 15. Styling against component state
|
|
427
|
+
|
|
428
|
+
If the app styles `@uselets/ui` parts by state (custom `className`, global CSS, or `group-*`/`peer-*`
|
|
429
|
+
variants), update the selectors. `data-open`, `data-closed`, `data-checked`, `data-unchecked`, `data-active`,
|
|
430
|
+
`data-disabled`, `data-horizontal` and `data-vertical` Tailwind variants keep working, because the package's
|
|
431
|
+
`shadcn/tailwind.css` maps them, but only on parts that set those attributes:
|
|
432
|
+
|
|
433
|
+
- `data-open:` / `data-closed:` match contents, popups and panels, **not triggers**. A trigger only gets
|
|
434
|
+
`data-popup-open` or `data-panel-open` while open and has no closed marker, so `data-open:rotate-180` on a
|
|
435
|
+
trigger silently stops matching. Use `data-popup-open:` / `data-panel-open:`, or `aria-expanded:`.
|
|
436
|
+
- `data-checked:` matches checkboxes, radios, switches and menu checkbox/radio items, **not `SelectItem`**. The
|
|
437
|
+
selected `SelectItem` gets an empty `data-selected` attribute, which the `data-selected:` variant (it expects
|
|
438
|
+
`data-selected="true"`) does not match. Use `data-[selected]:` or `aria-selected:`.
|
|
439
|
+
|
|
440
|
+
Literal `data-[state=…]` selectors and Radix CSS variables do not keep working:
|
|
441
|
+
|
|
442
|
+
| v1 (Radix) | v2 (Base UI) |
|
|
443
|
+
| --- | --- |
|
|
444
|
+
| `data-[state=open]` on a trigger | `data-popup-open` (popover, menu, select, dialog, alert dialog, drawer, tooltip and hover card triggers), `data-panel-open` (collapsible/accordion triggers) |
|
|
445
|
+
| `data-[state=open]` / `[state=closed]` on content | `data-open` / `data-closed` |
|
|
446
|
+
| `data-[state=checked]` / `[state=unchecked]` | `data-checked` / `data-unchecked` |
|
|
447
|
+
| `data-[state=checked]` on `SelectItem` | `data-[selected]` |
|
|
448
|
+
| `data-[state=indeterminate]` | `data-indeterminate` |
|
|
449
|
+
| `data-[state=on]` (Toggle, ToggleGroupItem) | `data-pressed` |
|
|
450
|
+
| `data-[state=active]` (TabsTrigger) | `data-active` |
|
|
451
|
+
| `data-[state=delayed-open]` / `[state=instant-open]` (Tooltip) | `data-open`; an instant open also sets `data-instant` |
|
|
452
|
+
| `data-[state=active]` / `[state=inactive]` (TabsContent) | inactive panels get `data-hidden`; the active one has no marker |
|
|
453
|
+
| `data-[state=loading]` / `[state=complete]` (Progress) | `data-progressing` / `data-complete` |
|
|
454
|
+
| `data-value`, `data-max` (Progress) | removed; style from your own `value` |
|
|
455
|
+
| `data-disabled` (Collapsible parts) | removed; `CollapsibleTrigger` still gets the native `disabled` attribute (`disabled:`) |
|
|
456
|
+
| `data-orientation` (NavigationMenu, NavigationMenuList, NavigationMenuItem) | removed |
|
|
457
|
+
| `data-[state=visible]` / `[state=hidden]` (ScrollBar) | `data-hovering` / `data-scrolling` while shown |
|
|
458
|
+
| `data-motion` (NavigationMenuContent) | `data-activation-direction="left" \| "right"` with `data-starting-style` / `data-ending-style` |
|
|
459
|
+
| `focus:` on menu/select items | still works. `data-highlighted` is also set |
|
|
460
|
+
| `disabled:` on Checkbox/Radio/Switch roots | `data-disabled:` (roots are `<span>`, not native inputs) |
|
|
461
|
+
| `peer-disabled:` on a label next to those controls | `peer-data-disabled:` |
|
|
462
|
+
| `--radix-*-content-transform-origin` | `--transform-origin` |
|
|
463
|
+
| `--radix-*-content-available-height` | `--available-height` |
|
|
464
|
+
| `--radix-*-content-available-width` | `--available-width` |
|
|
465
|
+
| `--radix-*-trigger-width` | `--anchor-width` |
|
|
466
|
+
| `--radix-*-trigger-height` | `--anchor-height` |
|
|
467
|
+
| `--radix-accordion-content-height` / `-width` | `--accordion-panel-height` / `--accordion-panel-width` |
|
|
468
|
+
| `--radix-collapsible-content-height` / `-width` | `--collapsible-panel-height` / `--collapsible-panel-width` |
|
|
469
|
+
| `--radix-navigation-menu-viewport-width/height` | `--popup-width` / `--popup-height` |
|
|
470
|
+
|
|
471
|
+
- **Detect:** `data-open:`/`data-closed:` on a trigger className, `data-checked:` on `SelectItem`. Also
|
|
472
|
+
`data-[state=`, `--radix-`, `[data-state=` in CSS/TSX. Ignore matches that aren't Radix state:
|
|
473
|
+
`TableRow data-state="selected"` (and `data-[state=selected]` table styles) and the Sidebar's own
|
|
474
|
+
`data-state="expanded" | "collapsed"` are package attributes and keep working unchanged.
|
|
475
|
+
|
|
476
|
+
## 16. Tests
|
|
477
|
+
|
|
478
|
+
Base UI reacts to different DOM events than Radix. In `@testing-library/react` with `fireEvent`:
|
|
479
|
+
|
|
480
|
+
- Menus (`DropdownMenuTrigger`, `ModeToggle`) open with `fireEvent.click(trigger)`, not
|
|
481
|
+
`pointerDown`/`pointerUp`.
|
|
482
|
+
- Select options need `fireEvent.pointerDown(option)` followed by `fireEvent.click(option)`. Open the select
|
|
483
|
+
with `click` or `keyDown` `ArrowDown` on the trigger (`Enter` relies on the browser's native button click,
|
|
484
|
+
which jsdom doesn't synthesize).
|
|
485
|
+
- Tabs activate on `click`, not `mouseDown`.
|
|
486
|
+
- `role="listbox"` lives on an inner list. Use `closest('[data-slot="select-content"]')` to reach the content
|
|
487
|
+
slot.
|
|
488
|
+
- Disabled `AccordionTrigger` and `TabsTrigger` expose `aria-disabled="true"`, not `disabled`: assert
|
|
489
|
+
`toHaveAttribute('aria-disabled', 'true')` instead of `toBeDisabled()`.
|
|
490
|
+
- Assertions on `data-state="..."` must switch to the attributes in section 15
|
|
491
|
+
(`hasAttribute('data-checked')`, `hasAttribute('data-pressed')`, …).
|
|
492
|
+
- Callback assertions need the extra `eventDetails` argument (`expect.anything()`).
|
|
493
|
+
- Floating parts still need a `ResizeObserver` stub in jsdom.
|
|
494
|
+
|
|
495
|
+
## 17. Dialog default size
|
|
496
|
+
|
|
497
|
+
`Dialog` without a `size` now defaults to `md` (v1 rendered `sm`, `sm:max-w-sm`). `CommandDialog` still defaults
|
|
498
|
+
to `sm`. To keep a v1 dialog pixel-identical, pass `size="sm"`.
|
|
499
|
+
|
|
500
|
+
```tsx
|
|
501
|
+
// v1 (implicit sm) → v2
|
|
502
|
+
<Dialog>…</Dialog> → <Dialog size="sm">…</Dialog>
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
- **Detect:** `<Dialog` without `size=`.
|
|
506
|
+
|
|
507
|
+
## 18. Removed and renamed props
|
|
508
|
+
|
|
509
|
+
| v1 (Radix) | v2 |
|
|
510
|
+
| --- | --- |
|
|
511
|
+
| `Slider onValueCommit` | `onValueCommitted` |
|
|
512
|
+
| `Tabs activationMode="automatic" \| "manual"` | `TabsList activateOnFocus` (default `false` = manual: arrow keys only move focus, `Enter`/`Space` or a click selects). v1 defaulted to automatic, so pass `activateOnFocus` to keep it; `activationMode="manual"` → just remove it |
|
|
513
|
+
| `AvatarFallback delayMs` | `delay` |
|
|
514
|
+
| `forceMount` on `AccordionContent` / `CollapsibleContent` / `TabsContent` / `NavigationMenuContent` | `keepMounted` |
|
|
515
|
+
| `forceMount` on overlay contents (`DialogContent`, `PopoverContent`, `DropdownMenuContent`, …) | `keepMounted` on the same content (forwarded to its portal). `SelectContent` has no equivalent |
|
|
516
|
+
| `onOpenAutoFocus` / `onCloseAutoFocus` on contents | `initialFocus` / `finalFocus` (on `DialogContent`, `DrawerContent`, `PopoverContent`, `AlertDialogContent`); `SelectContent` and `DropdownMenuContent` only have `finalFocus` |
|
|
517
|
+
| `onOpenAutoFocus={(event) => event.preventDefault()}` | `initialFocus={false}` |
|
|
518
|
+
| `forceMount` on `DialogPortal` / `AlertDialogPortal` / `DrawerPortal` / `DropdownMenuPortal` | `keepMounted` |
|
|
519
|
+
| `forceMount` on `DialogOverlay` / `AlertDialogOverlay` / `DrawerOverlay` | removed (the overlay follows its portal) |
|
|
520
|
+
| `ScrollBar forceMount` | `keepMounted` |
|
|
521
|
+
| `textValue` on `SelectItem`, `DropdownMenuItem` / `CheckboxItem` / `RadioItem`, `DropdownMenuSubTrigger` | `label` (typeahead text) |
|
|
522
|
+
| `DropdownMenuCheckboxItem checked="indeterminate"` | removed; `checked` is boolean only |
|
|
523
|
+
| `DropdownMenu loop` | `loopFocus` on `DropdownMenu`, default `true` (focus now wraps; v1 stopped at the ends, so pass `loopFocus={false}` to keep it) |
|
|
524
|
+
| `DropdownMenuContent onFocusOutside` | removed; use the root `onOpenChange` with `eventDetails.reason === 'focus-out'` |
|
|
525
|
+
| `loop` on `TabsList` / `ToggleGroup` | `loopFocus` (default `true`) |
|
|
526
|
+
| `ToggleGroup rovingFocus` | removed (roving focus is always on) |
|
|
527
|
+
| `Slider minStepsBetweenThumbs` | `minStepsBetweenValues` |
|
|
528
|
+
| `Slider inverted` | removed |
|
|
529
|
+
| `Progress getValueLabel(value, max)` | `getAriaValueText(formattedValue, value)` |
|
|
530
|
+
| `NavigationMenu delayDuration` | `delay` (default `50`; v1 default was `200`) |
|
|
531
|
+
| `NavigationMenu skipDelayDuration` | removed |
|
|
532
|
+
| `TooltipProvider skipDelayDuration` | `timeout` (default `400`; v1 default was `300`) |
|
|
533
|
+
| `TooltipProvider disableHoverableContent` | `disableHoverablePopup` on each `Tooltip` / `TooltipRoot` |
|
|
534
|
+
| `dir="rtl"` on `Tabs`, `ToggleGroup`, `RadioGroup`, `DropdownMenu`, `Slider`, `ScrollArea`, `Select` | still renders the attribute but no longer flips keyboard navigation or positioning; wrap the subtree in `<DirectionProvider direction="rtl">` from `@uselets/ui/direction` |
|
|
535
|
+
| `onEscapeKeyDown`, `onPointerDownOutside`, `onInteractOutside` on contents | removed; use the root `onOpenChange(open, eventDetails)` and check `eventDetails.reason`, calling `eventDetails.cancel()` to keep it open. `onEscapeKeyDown` → `'escape-key'`; `onPointerDownOutside` → `'outside-press'`; `onInteractOutside` → `'outside-press'` **and** `'focus-out'` (Radix fired it for focus leaving the popup too). For dialogs/drawers, `dismissible={false}` covers the common case |
|
|
536
|
+
| `onEscapeKeyDown`, `onPointerDownOutside`, `onFocusOutside`, `onInteractOutside` on `NavigationMenuContent` | removed; `NavigationMenu` has no `onOpenChange`: use its `onValueChange(value, eventDetails)` with `eventDetails.reason` (`'escape-key'`, `'outside-press'`, `'focus-out'`) and `eventDetails.cancel()` |
|
|
537
|
+
| `onChange` on `Switch` / `Checkbox` | `onCheckedChange` (v1 accepted `onChange` because the root was a `<button>`, but it never fired on toggle) |
|
|
538
|
+
| `sticky="partial" \| "always"` on popover/menu/select/tooltip/hover-card contents | `sticky?: boolean` (default `false`): `true` keeps the popup in the viewport after the trigger scrolls away (the Radix values are not accepted) |
|
|
539
|
+
| `collisionPadding`, `avoidCollisions`, `collisionBoundary`, `arrowPadding`, `hideWhenDetached` on those contents | not exposed |
|
|
540
|
+
| `ScrollArea type` / `scrollHideDelay` | removed; the scrollbar shows while hovering or scrolling |
|
|
541
|
+
| `PopoverAnchor`, `PopoverAnchorProps` | removed (section 8) |
|
|
542
|
+
| `NavigationMenuViewport`, `NavigationMenuViewportProps`, `NavigationMenu viewport` | removed (section 9) |
|
|
543
|
+
|
|
544
|
+
- **Detect:** each v1 name in the left column.
|
|
545
|
+
|
|
546
|
+
## 19. Other behavior changes
|
|
547
|
+
|
|
548
|
+
- Anchored popups (`PopoverContent`, `SelectContent`, `DropdownMenuContent`, `TooltipContent`, `HoverCardContent`)
|
|
549
|
+
now keep 5px from the viewport edge when they flip or shift (v1: 0px, and 10px for `SelectContent`), and the
|
|
550
|
+
`Tooltip` arrow keeps 5px from the popup corners. Nothing to change in code; only screenshots of popups near the
|
|
551
|
+
edge differ.
|
|
552
|
+
- A range `Slider` (two thumbs) now pushes the other thumb when one is dragged into it. In v1 the thumbs crossed
|
|
553
|
+
and swapped places. Values passed to `onValueChange` stay sorted either way. Pass
|
|
554
|
+
`thumbCollisionBehavior="swap"` to keep the v1 behavior (or `"none"` to stop at the other thumb).
|
|
555
|
+
- **Detect:** `<Slider` with a two-value `value`/`defaultValue`.
|
|
556
|
+
- `Checkbox`, `RadioGroupItem` and `Switch` render a `<span>` root plus a hidden `<input>`. The `id` prop (and the
|
|
557
|
+
`Field` control id) lands on the hidden input, not on the visible element. Code that does
|
|
558
|
+
`document.getElementById(id)` and expects the visible control, or styles `#id`, must target
|
|
559
|
+
`[data-slot="checkbox"]` / `[data-slot="switch"]` / `[data-slot="radio-group-item"]` instead.
|
|
560
|
+
- `Progress` renders a root plus an inner track: `className` is applied to the track (the visible bar), and the
|
|
561
|
+
root wraps it.
|
|
562
|
+
- `Checkbox`, `Switch` and `RadioGroupItem` no longer draw a red border/ring for `aria-invalid` (the attribute is
|
|
563
|
+
still set for assistive tech). Show the error through the `Field`: `<Field invalid>` colors the label and
|
|
564
|
+
`FieldError` shows the message. Nothing to change in code; only screenshots of invalid checkboxes/switches differ.
|
|
565
|
+
- **Detect:** `aria-invalid` on `<Checkbox`, `<Switch` or `<RadioGroupItem`.
|
|
566
|
+
- `Field`'s `data-invalid` is now typed `boolean | 'true' | 'false'`. Passing anything else, such as a
|
|
567
|
+
react-hook-form `FieldError` object, no longer type-checks (in v1 it rendered `data-invalid="[object Object]"` and
|
|
568
|
+
the invalid styles never matched). Pass a boolean, preferably through `invalid`:
|
|
569
|
+
`<Field invalid={!!errors.identifier}>`.
|
|
570
|
+
- **Detect:** `data-invalid={` on `<Field` with a non-boolean value (`errors.x`, `fieldState.error`).
|
|
571
|
+
|
|
572
|
+
## Unchanged APIs
|
|
573
|
+
|
|
574
|
+
No migration is needed for: `Button`, `Badge`, `Anchor`, `Heading`, `Text`, `Marker`, `List`, `UnstyledButton`,
|
|
575
|
+
`Breadcrumb`, `ButtonGroup`, `BottomNav`, `Sidebar` parts (`asChild` still supported everywhere it was),
|
|
576
|
+
`Dialog`/`Drawer` (`direction`, `dismissible`, `showCloseButton`, `overlayRef`/`overlayProps`,
|
|
577
|
+
`DialogBody`/`DrawerBody`; only the `Dialog` default size changed, section 17), `AlertDialog` (`size`,
|
|
578
|
+
`AlertDialogAction` still closes the dialog; see the note below), `Tabs` (`variant`, `grow`, string `value`; see
|
|
579
|
+
the note below and section 18 for `activationMode`), `Separator` (`decorative`, `orientation`), `Collapsible`,
|
|
580
|
+
`RadioGroup` (string `value`; see the note below),
|
|
581
|
+
`ScrollArea` (see section 18), `Avatar` (see section 18), `Combobox` and `NumberField` (except `render`, see section 1), and every non-interactive
|
|
582
|
+
component.
|
|
583
|
+
|
|
584
|
+
These keep their props but changed behavior:
|
|
585
|
+
|
|
586
|
+
- `AlertDialog`: opening focuses the first focusable element in the content, not `AlertDialogCancel`. If a link or
|
|
587
|
+
`AlertDialogAction` comes before the cancel button, pass a ref to the element to focus:
|
|
588
|
+
`<AlertDialogContent initialFocus={cancelRef}>` with `<AlertDialogCancel ref={cancelRef}>`.
|
|
589
|
+
- **Detect:** `<AlertDialogContent` whose first focusable child is not `AlertDialogCancel`.
|
|
590
|
+
- `Tabs`: without `value` or `defaultValue`, the first enabled tab is now selected on mount (v1 selected none) and
|
|
591
|
+
`onValueChange` fires once with `eventDetails.reason === 'initial'`. Pass `defaultValue`, or ignore that reason in
|
|
592
|
+
handlers with side effects (tracking, fetching).
|
|
593
|
+
- **Detect:** `<Tabs` with `onValueChange` but neither `value` nor `defaultValue`.
|
|
594
|
+
- `TabsTrigger disabled` now sets `aria-disabled="true"` instead of the native `disabled` attribute. The tab still
|
|
595
|
+
can't be selected (click and `Enter` do nothing), but arrow keys now stop on it instead of skipping it, so screen
|
|
596
|
+
reader users can discover it. Style it with `aria-disabled:` / `data-disabled:` (not `disabled:`).
|
|
597
|
+
- **Detect:** `disabled:` classes on `TabsTrigger`; tests asserting `toBeDisabled()` on a tab.
|
|
598
|
+
- `RadioGroup`: `orientation`, `loop` and `dir` are no longer supported; arrow-key focus always wraps.
|
|
599
|
+
- **Detect:** `orientation=`, `loop` or `dir=` on `<RadioGroup`.
|
|
@@ -21,8 +21,8 @@ If the package is not installed yet, use the bundled references as a snapshot an
|
|
|
21
21
|
|
|
22
22
|
## Requirements
|
|
23
23
|
|
|
24
|
-
- React
|
|
25
|
-
- React DOM
|
|
24
|
+
- React `^19.2`
|
|
25
|
+
- React DOM `^19.2`
|
|
26
26
|
- Tailwind CSS 4
|
|
27
27
|
|
|
28
28
|
## Installation
|
|
@@ -234,7 +234,7 @@ Examples:
|
|
|
234
234
|
import { Tooltip, TooltipProvider } from '@uselets/ui/tooltip'
|
|
235
235
|
|
|
236
236
|
export function AppRoot({ children }: { children: React.ReactNode }) {
|
|
237
|
-
return <TooltipProvider
|
|
237
|
+
return <TooltipProvider delay={200}>{children}</TooltipProvider>
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
export function SaveButton() {
|
|
@@ -318,13 +318,13 @@ Additional tokens:
|
|
|
318
318
|
|
|
319
319
|
The `Button` component can apply those palette families directly through `color: UIColor`, with `primary` as the default. Its public variants are `solid`, `secondary`, `outline`, `subtle`, `ghost`, and `link`, with `solid` as the default. Its public sizes are `xs`, `sm`, `md`, `lg`, `xl`, `icon-xs`, `icon-sm`, `icon`, `icon-lg`, and `icon-xl`; `loading` shows a centered spinner, keeps native button content invisible to preserve size, and disables the button without dimming it.
|
|
320
320
|
|
|
321
|
-
The `Checkbox` component can be used as a bare checkbox or with `label?: React.ReactNode`. It supports `size: 'sm' | 'md' | 'lg'` and `color: UIColor`, with `primary` as the default color. When `label` is provided, it composes the package `Label`, accepts `labelProps` for that label, and generates an accessible `id`/`htmlFor` pair when no `id` prop is passed. Composed label text follows checkbox size: `sm` 12px, `md` 14px, and `lg`
|
|
321
|
+
The `Checkbox` component can be used as a bare checkbox or with `label?: React.ReactNode`. It supports `size: 'sm' | 'md' | 'lg'` and `color: UIColor`, with `primary` as the default color. When `label` is provided, it composes the package `Label`, accepts `labelProps` for that label, and generates an accessible `id`/`htmlFor` pair when no `id` prop is passed. Composed label text follows checkbox size: `sm` 12px, `md` 14px, and `lg` 18px. Pass `description?: React.ReactNode` (and `descriptionProps`) to render muted helper text (a `<p>`) below the label, linked to the control via `aria-describedby`; description text is `sm` 12px, `md` 12px, `lg` 14px.
|
|
322
322
|
|
|
323
|
-
The `Switch` component can be used as a bare switch or with `label?: React.ReactNode`. It supports `size: 'sm' | 'md' | 'lg'` and `color: UIColor`, with `green` as the default color. The `color` prop controls only the checked track and indicator color; unchecked remains neutral. Track size is `sm`
|
|
323
|
+
The `Switch` component can be used as a bare switch or with `label?: React.ReactNode`. It supports `size: 'sm' | 'md' | 'lg'` and `color: UIColor`, with `green` as the default color. The `color` prop controls only the checked track and indicator color; unchecked remains neutral. Track size is `sm` 30x14px, `md` 34x16px, and `lg` 42x20px (control heights match `Checkbox`). The thumb and its internal indicator scale with the selected size. Composed label text follows switch size: `sm` 12px, `md` 14px, and `lg` 18px. `description?: React.ReactNode` (and `descriptionProps`) works the same as on `Checkbox`.
|
|
324
324
|
|
|
325
325
|
The `TabsList` component supports `variant: 'line' | 'enclosed'`, with `line` as the default, and `grow?: boolean` for equal-width horizontal tabs. The `line` variant keeps a neutral border line across tabs and uses the `primary` color for the active indicator; use `enclosed` for the filled container treatment.
|
|
326
326
|
|
|
327
|
-
The `RadioGroup` component supports `color: UIColor`, with `primary` as the default group color. `RadioGroupItem` can be used as a bare radio item inside `RadioGroup` or with `label?: React.ReactNode`; it supports `size: 'sm' | 'md' | 'lg'` and optional `color: UIColor` item overrides. Composed label text follows item size: `sm` 12px, `md` 14px, and `lg`
|
|
327
|
+
The `RadioGroup` component supports `color: UIColor`, with `primary` as the default group color. `RadioGroupItem` can be used as a bare radio item inside `RadioGroup` or with `label?: React.ReactNode`; it supports `size: 'sm' | 'md' | 'lg'` and optional `color: UIColor` item overrides. Composed label text follows item size: `sm` 12px, `md` 14px, and `lg` 18px. `description?: React.ReactNode` (and `descriptionProps`) works the same as on `Checkbox`.
|
|
328
328
|
|
|
329
329
|
The `DateRangePicker` component is a compound popover date range picker built on `Calendar` range mode. Use `DateRange | null` for controlled values; applying an empty draft or resetting emits `null` through `onValueChange`. Fixed presets, relative-day inputs, date inputs, and the calendar all edit the same draft, and `DateRangePickerPreset` displays a selected state when its range matches the current draft by calendar day.
|
|
330
330
|
|
|
@@ -365,7 +365,7 @@ import { Table, TableContainer } from '@uselets/ui/table'
|
|
|
365
365
|
</DataTableRoot>
|
|
366
366
|
```
|
|
367
367
|
|
|
368
|
-
`DataTableRoot` accepts either `{ columns, data, ...options }` (uncontrolled — creates the `table` instance internally) or `{ table }` (controlled — table instance comes from `useDataTable` called by the consumer). Use the controlled form whenever you need access to the `table` instance in the same scope as the JSX (e.g. for virtualization or external action buttons).
|
|
368
|
+
`DataTableRoot` accepts either `{ columns, data, ...options }` (uncontrolled — creates the `table` instance internally) or `{ table }` (controlled — table instance comes from `useDataTable` called by the consumer). In the uncontrolled form, pagination is on while a `<DataTablePagination />` is rendered inside the root and off otherwise (all rows); an explicit `enablePagination` overrides that. In the controlled form, `useDataTable` defaults to `enablePagination: false` — pass `true` when rendering `<DataTablePagination />`. Use the controlled form whenever you need access to the `table` instance in the same scope as the JSX (e.g. for virtualization or external action buttons).
|
|
369
369
|
|
|
370
370
|
### Virtualized lists (bring your own virtualizer)
|
|
371
371
|
|