diff --git a/docs/V1-migration.md b/docs/V1-migration.md new file mode 100644 index 000000000..fc012f43f --- /dev/null +++ b/docs/V1-migration.md @@ -0,0 +1,1400 @@ +# Apsara v1.0.0 Migration Guide: Radix UI / Ariakit -> Base UI + +This guide covers all breaking changes when upgrading from the last stable Radix-based release to the current Base UI-based v1.0.0 + +--- + +## Table of Contents + +- [Apsara v1.0.0 Migration Guide: Radix UI / Ariakit -\> Base UI](#apsara-v100-migration-guide-radix-ui--ariakit---base-ui) + - [Table of Contents](#table-of-contents) + - [Prerequisites](#prerequisites) + - [Cross-Cutting Changes](#cross-cutting-changes) + - [`asChild` Replaced by `render`](#aschild-replaced-by-render) + - [Callback Signatures](#callback-signatures) + - [Data Attributes](#data-attributes) + - [CSS Variables](#css-variables) + - [Component Migration](#component-migration) + - [Accordion](#accordion) + - [New Props](#new-props) + - [Avatar](#avatar) + - [Breadcrumb](#breadcrumb) + - [Button](#button) + - [Checkbox](#checkbox) + - [New: `Checkbox.Group`](#new-checkboxgroup) + - [Combobox](#combobox) + - [New Features](#new-features) + - [Data Table](#data-table) + - [Dialog](#dialog) + - [New Features](#new-features-1) + - [DropdownMenu -\> Menu](#dropdownmenu---menu) + - [New Features](#new-features-2) + - [Flex](#flex) + - [Grid](#grid) + - [Popover](#popover) + - [New Features](#new-features-3) + - [Radio](#radio) + - [ScrollArea](#scrollarea) + - [Select](#select) + - [New Features](#new-features-4) + - [Separator](#separator) + - [Sheet -\> Drawer](#sheet---drawer) + - [New Features](#new-features-5) + - [Sidebar](#sidebar) + - [New Features](#new-features-6) + - [Slider](#slider) + - [New Features](#new-features-7) + - [Switch](#switch) + - [Tabs](#tabs) + - [New Features](#new-features-8) + - [Toast](#toast) + - [New Features](#new-features-9) + - [Tooltip](#tooltip) + - [New Features](#new-features-10) + - [New Components](#new-components) + - [Removed Exports](#removed-exports) + - [| `RadioItem` | `Radio` | See Radio |](#-radioitem--radio--see-radio-) + - [Migration Checklist](#migration-checklist) + +--- + +## Prerequisites + +- **React 19 required.** The peer dependency changed from `^18 || ^19` to `^19` only. React 18 is no longer supported. +- The library no longer uses `radix-ui`, `@ariakit/react`, or `sonner`. It now uses `@base-ui/react` and `@base-ui/utils` internally. These are installed automatically as dependencies of the package. +- `@radix-ui/react-icons` is still used. +--- + +## Cross-Cutting Changes + +These patterns apply across many components. Address them globally before tackling individual components. + +### `asChild` Replaced by `render` + +Radix's `asChild` composition pattern is gone. Use the `render` prop instead. Note that with `render`, children text goes on the parent, not inside the rendered element. + +```tsx +// Before — asChild merges props onto the child element + + +// After — render specifies the element, children go on the wrapper + +``` + +```tsx +// Before — Trigger with asChild + + + + +// After — Trigger with render +}> + Open Dialog + +``` + +```tsx +// Before — Close with asChild + + + + +// After — Close with render (entire element in render) +Cancel} /> +``` + +Affected components: Button, Grid, Grid.Item, Popover.Trigger, Menu.Trigger, Drawer.Trigger, Dialog.Trigger, Dialog.Close, AlertDialog.Trigger, Breadcrumb.Item, Tooltip.Trigger. + +### Callback Signatures + +Most `onValueChange`, `onOpenChange`, and `onCheckedChange` callbacks now receive an optional second `eventDetails` argument: + +```tsx +// Before + { + console.log(value); +}} /> + +// After — second arg added, but it's optional + { + console.log(value); +}} /> +``` + +```tsx +// Before + setEnabled(checked)} /> + +// After + setEnabled(checked)} /> +``` + +Existing handlers that ignore extra args still work at runtime, but TypeScript types may require updating. + +### Data Attributes + +If you target Apsara component data attributes in custom CSS, these have changed globally: + +| Old (Radix) | New (Base UI) | +|-------------|---------------| +| `data-state="open"` | `data-open` | +| `data-state="closed"` | `data-closed` | +| `data-state="checked"` | `data-checked` | +| `data-state="unchecked"` | `data-unchecked` | +| `data-state="active"` | `data-active` | +| `data-active-item="true"` | `data-highlighted` | +| `data-disabled="true"` | `data-disabled` (no value) | + +```css +/* Before */ +.my-panel[data-state="open"] { opacity: 1; } +.my-panel[data-state="closed"] { opacity: 0; } +.my-checkbox[data-state="checked"] { background: blue; } +.my-item[data-active-item="true"] { background: gray; } +.my-switch[data-disabled="true"] { opacity: 0.5; } + +/* After */ +.my-panel[data-open] { opacity: 1; } +.my-panel[data-closed] { opacity: 0; } +.my-checkbox[data-checked] { background: blue; } +.my-item[data-highlighted] { background: gray; } +.my-switch[data-disabled] { opacity: 0.5; } +``` + +Animation data attributes also changed: + +```css +/* Before — Radix keyframe animation */ +.overlay[data-state="open"] { animation: fadeIn 200ms; } +.overlay[data-state="closed"] { animation: fadeOut 200ms; } + +/* After — Base UI CSS transitions */ +.overlay { transition: opacity 200ms; } +.overlay[data-starting-style] { opacity: 0; } +.overlay[data-ending-style] { opacity: 0; } +``` + +### CSS Variables + +If you reference these CSS variables in custom styles: + +| Old | New | +|-----|-----| +| `--radix-popover-trigger-width` | `--anchor-width` | +| `--radix-select-trigger-width` | `--anchor-width` | +| `--radix-accordion-content-height` | `--accordion-panel-height` | +| `--radix-collapsible-content-*` | Removed | + +```css +/* Before */ +.dropdown { min-width: var(--radix-popover-trigger-width); } +.accordion-panel { height: var(--radix-accordion-content-height); } + +/* After */ +.dropdown { min-width: var(--anchor-width); } +.accordion-panel { height: var(--accordion-panel-height); } +``` + +--- + +## Component Migration + +### Accordion + +1. **`type` prop replaced with `multiple` boolean**, `collapsible` prop removed (always collapsible): + +```tsx +// Before + + + Section 1 + Content 1 + + + Section 2 + Content 2 + + + +// After + + + Section 1 + Content 1 + + + Section 2 + Content 2 + + +``` + +```tsx +// Before — multiple mode + + +// After — multiple mode + +``` + +2. **`onValueChange` in single mode** now receives `string | undefined` (not just `string`): + +```tsx +// Before + setValue(value)}> + +// After — value can be undefined when all items are collapsed + setValue(value)}> +``` + +3. **`forceMount` replaced by `keepMounted`:** + +```tsx +// Before +Always in DOM + +// After — on root (applies to all) or individual Content + + + Title + Always in DOM + + +``` + +4. **`dir` prop removed.** + +5. **Type exports removed** -- `AccordionItemProps`, `AccordionTriggerProps`, `AccordionContentProps` are no longer exported. + +#### New Props + +- `loopFocus` -- loop keyboard navigation from last to first item +- `hiddenUntilFound` -- content is searchable by browser find-in-page even when collapsed + +--- + +### Avatar + +1. **`asChild` prop removed:** + +```tsx +// Before + + + + +// After — compose by wrapping instead + + + +``` + +2. **`delayMs` and other Radix-specific root props** no longer recognized: + +```tsx +// Before — Radix-specific prop + + +// After — delayMs removed, no equivalent + +``` + +Unchanged: `size`, `radius`, `variant`, `color`, `fallback`, `src`, `alt`, `className`, `AvatarGroup` with `max`, and `getAvatarColor`. + +--- + +### Breadcrumb + +1. **`as` prop renamed to `render`** on `BreadcrumbItem`: + +```tsx +// Before + + }>Home + }>Settings + Profile + + +// After + + }>Home + }>Settings + Profile + +``` + +2. **`current` items now render `` instead of ``.** If you had CSS targeting `a` elements for current breadcrumb items, update to target `span`. + +3. **`dropdownItems` shape changed** -- `label` -> `children`: + +```tsx +// Before + + More + + +// After + + More + +``` + +--- + +### Button + +**`asChild` removed** -- use `render` prop: + +```tsx +// Before — rendering as a link + + +// After + +``` + +```tsx +// Before — rendering as a router Link + + +// After + +``` + +--- + +### Checkbox + +1. **Indeterminate API changed** -- `checked="indeterminate"` replaced by separate `indeterminate` boolean: + +```tsx +// Before + + + +// After + + +``` + +2. **`onCheckedChange` now receives 2 args** -- `(checked: boolean, eventDetails)`. The `CheckedState` type (`boolean | 'indeterminate'`) no longer exists: + +```tsx +// Before + { + if (val === "indeterminate") { /* handle */ } + else { setChecked(val); } + }} +/> + +// After — val is always boolean, indeterminate is a separate prop + { + setChecked(val); + }} +/> +``` + +3. **`CheckboxProps` type export removed.** + +#### New: `Checkbox.Group` + +```tsx + + + + +``` + +--- + +### Combobox + +1. **`onOpenChange` signature changed** -- now `(open, eventDetails)` (2 args). `onValueChange` and `onInputValueChange` are unchanged (1 arg): + +```tsx +// Before + setIsOpen(open)}> + +// After + setIsOpen(open)}> +``` + +2. **Content props removed** -- `align`, `onOpenAutoFocus`, `onInteractOutside`, `onFocusOutside` no longer accepted. New: `initialFocus`, `finalFocus`: + +```tsx +// Before + e.preventDefault()} + onInteractOutside={handleOutside} + onFocusOutside={handleFocusOut} +> + +// After + +``` + +3. **`modal` prop removed** -- always modal. + +4. **`defaultInputValue` prop removed** -- use controlled `inputValue`. + +5. **`focusOnHover` prop removed from Item.** + +6. **Backspace-to-remove-last-chip removed** in multiple mode. + +**Full before/after example:** + +```tsx +// Before + setOpen(open)} + onValueChange={(value) => setValue(value)} +> + + + Apple + Banana + Cherry + + + +// After + setOpen(open)} + onValueChange={(value) => setValue(value)} +> + + + Apple + Banana + Cherry + + +``` + +#### New Features + +- `Combobox.useFilter` and `Combobox.useFilteredItems` hooks for declarative filtering +- Generic value type support: `>` +- `items` prop on Root for built-in filtering +- `render` prop on Content and Item + +--- + +### Data Table + +```tsx +// Before — defaultSort was optional + + +// After — defaultSort is effectively required for reset/empty state detection + +``` + +- New `totalRowCount` prop -- when provided in `mode='server'`, shows a "N items hidden by filters" message: + +```tsx + +``` + +--- + +### Dialog + +1. **`DialogContent` props rewritten:** + - Removed: `ariaLabel`, `ariaDescription`, `overlayBlur`, `overlayClassName`, `overlayStyle`, `scrollableOverlay` + - New: `showCloseButton` (default `true`), `overlay` (object with `blur?: boolean`, `className`, `style`), `showNestedAnimation` + +2. **CloseButton is now auto-rendered** inside Content. Remove manual `` from headers. Pass `showCloseButton={false}` to suppress. + +```tsx +// Before + + + + + + + Settings + + + + Configure your preferences. +
{/* form fields */}
+
+ + + + + + +
+
+ +// After + + }>Open Settings + + + Settings + {/* CloseButton auto-rendered by Content — remove manual placement */} + + + Configure your preferences. +
{/* form fields */}
+
+ + Cancel} /> + + +
+
+``` + +To suppress the auto close button: + +```tsx + +``` + +#### New Features + +- `Dialog.createHandle` for imperative open/close +- `showNestedAnimation` for stacked dialog animations +- Header/Footer/Body accept all Flex props + +--- + +### DropdownMenu -> Menu + +**Export renamed: `DropdownMenu` -> `Menu`** + +**Full before/after example:** + +```tsx +// Before +import { DropdownMenu } from '@raystack/apsara'; + + console.log(open)} +> + + + + + File + + }>Edit + Copy + + + + Export as... + + CSV + PDF + + + + + +// After +import { Menu } from '@raystack/apsara'; + + console.log(open)} +> + }>Actions + + + File + }>Edit + Copy + + + + Export as... + + CSV + PDF + + + + +``` + +**Summary of prop renames:** + +| Old (`DropdownMenu`) | New (`Menu`) | +|---------------------|-------------| +| `searchValue` | `inputValue` | +| `onSearch` | `onInputValueChange` | +| `defaultSearchValue` | `defaultInputValue` | +| `focusLoop` | `loopFocus` (default flipped: was `true`, now `false`) | +| `Content gutter={N}` | `Content sideOffset={N}` | +| `Content portal` | Removed (always portaled) | +| `Content portalElement` | Removed | +| `Content unmountOnHide` | Removed | +| `DropdownMenu.TriggerItem` | `Menu.SubmenuTrigger` (inside `Menu.Submenu`) | + +#### New Features + +- `Menu.Submenu`, `Menu.SubmenuTrigger`, `Menu.SubmenuContent` +- `Menu.createHandle` for imperative control +- Menubar integration + +--- + +### Flex + +```tsx +// Before — Flex always rendered as
+content + +// After — unchanged for basic usage, but now supports render prop +content + +// New — render as a different element +} gap="3" align="center">content +} gap="3" direction="column">links +``` + +Type changed to `useRender.ComponentProps<'div'>` -- may cause TypeScript errors if you typed Flex props explicitly. + +--- + +### Grid + +**`asChild` removed** from both `Grid` and `Grid.Item`. Use `render`: + +```tsx +// Before + +
+ +
Wide content
+
+ Narrow content +
+
+ +// After +} columns="3" gap="4"> + } colSpan="2">Wide content + Narrow content + +``` + +--- + +### Popover + +1. **`asChild` removed from Trigger** -- use `render` prop or pass children directly: + +```tsx +// Before + + + + + +

Popover content here

+ Close +
+
+ +// After + + }>Open Popover + +

Popover content here

+ Close +
+
+``` + +2. **`ariaLabel` custom prop removed** -- use standard `aria-label` instead. + +Positioning props (`side`, `align`, `sideOffset`, `collisionPadding`) are preserved. + +#### New Features + +- `Popover.createHandle` for imperative control +- `initialFocus` / `finalFocus` for focus management + +--- + +### Radio + +**Component hierarchy inverted** -- the root/item relationship is swapped: + +```tsx +// Before +import { Radio, RadioItem } from '@raystack/apsara'; + + setSelected(value)} + orientation="vertical" + aria-label="Choose plan" +> + + + + + +// After +import { Radio } from '@raystack/apsara'; + + setSelected(value)} + aria-label="Choose plan" +> + + + + +``` + +Key changes: +- `` (was group root) -> `` +- `` (was individual item) -> `` +- `RadioItem` named export removed +- `RadioItemProps` type export removed +- `onValueChange` now receives 2 args +- `orientation` prop removed + +--- + +### ScrollArea + +```tsx +// Before — type="auto" was the default + +
Scrollable content...
+
+ +// After — type="auto" removed, default is now "hover" + +
Scrollable content...
+
+``` + +1. **`type="auto"` removed.** Remaining options: `'always'`, `'hover'`, `'scroll'`. +2. **Default `type` changed** from `'auto'` to `'hover'`. +3. **`ScrollAreaRootProps` renamed** to `ScrollAreaProps`. + +--- + +### Select + +1. **Sub-component renames:** + +```tsx +// Before + + +// After + +``` + +Summary of renames: +- `Select.ScrollUpButton` -> `Select.ScrollUpArrow` +- `Select.ScrollDownButton` -> `Select.ScrollDownArrow` +- `Select.Viewport` -> removed (no longer needed, remove the wrapper) +- `position="popper"` -> removed (always popper-style) + +2. **`SelectContent` props removed** -- `position`, `asChild`, `onEscapeKeyDown`, `onPointerDownOutside`. + +3. **`SelectItem` uses `render` prop** instead of `asChild`. + +#### New Features + +- `items` prop for external filtering +- Explicit `disabled`, `required`, `name` props on Root + +--- + +### Separator + +```tsx +// Before — decorative was accepted, aria-label auto-generated + +{/* auto-generated aria-label="horizontal separator" */} + +// After — decorative removed, no auto aria-label + +{/* add aria-label manually if needed */} + +``` + +1. **`decorative` prop removed.** +2. **Default `aria-label` removed** -- no longer auto-generates `"horizontal separator"`. Add explicitly if needed. + +--- + +### Sheet -> Drawer + +**Export renamed: `Sheet` -> `Drawer`** + +```tsx +// Before +import { Sheet } from '@raystack/apsara'; + + + + + + + Settings + Manage your preferences +
{/* form fields */}
+
+
+ +// After +import { Drawer } from '@raystack/apsara'; + + + }>Open Panel + + + Settings + Manage your preferences + + +
{/* form fields */}
+
+ + + +
+
+``` + +Key changes: +- All `Sheet.*` -> `Drawer.*` +- `asChild` -> `render` on Trigger +- `side` must be on both `` root AND `` +- `close` prop -> `showCloseButton` (default changed from `false` to `true`) +- New structured layout: `Drawer.Header`, `Drawer.Body`, `Drawer.Footer` + +#### New Features + +- Swipe-to-dismiss +- `Drawer.createHandle` for drag handle + +--- + +### Sidebar + +1. **`disabled` prop replaced by `collapsible={false}`:** + +```tsx +// Before — disabled prevents toggling but still shows trigger + + +// After — collapsible={false} hides the resize handle entirely + +``` + +2. **`asChild` removed from Root** -- always renders `