@fictjs/ui-primitives 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +181 -0
- package/dist/index.cjs +5091 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1123 -0
- package/dist/index.d.ts +1123 -0
- package/dist/index.js +4907 -0
- package/dist/index.js.map +1 -0
- package/docs/README.md +39 -0
- package/docs/accessibility.md +50 -0
- package/docs/api-reference.md +200 -0
- package/docs/architecture.md +113 -0
- package/docs/components/core/accessible-icon.md +23 -0
- package/docs/components/core/id.md +26 -0
- package/docs/components/core/portal.md +30 -0
- package/docs/components/core/presence.md +27 -0
- package/docs/components/core/primitive.md +22 -0
- package/docs/components/core/separator.md +25 -0
- package/docs/components/core/slot.md +25 -0
- package/docs/components/core/visually-hidden.md +21 -0
- package/docs/components/disclosure/accordion.md +33 -0
- package/docs/components/disclosure/collapsible.md +29 -0
- package/docs/components/disclosure/navigation-menu.md +43 -0
- package/docs/components/disclosure/tabs.md +35 -0
- package/docs/components/feedback/toast.md +60 -0
- package/docs/components/form/calendar.md +35 -0
- package/docs/components/form/controls.md +52 -0
- package/docs/components/form/date-picker.md +44 -0
- package/docs/components/form/form-field.md +39 -0
- package/docs/components/form/inputs.md +99 -0
- package/docs/components/interaction/dismissable-layer.md +28 -0
- package/docs/components/interaction/focus-scope.md +27 -0
- package/docs/components/interaction/live-region.md +26 -0
- package/docs/components/interaction/popper.md +30 -0
- package/docs/components/interaction/roving-focus.md +27 -0
- package/docs/components/interaction/scroll-lock.md +22 -0
- package/docs/components/layout/layout.md +61 -0
- package/docs/components/menu/context-menu.md +44 -0
- package/docs/components/menu/dropdown-menu.md +62 -0
- package/docs/components/menu/menubar.md +38 -0
- package/docs/components/overlay/alert-dialog.md +46 -0
- package/docs/components/overlay/command-palette.md +54 -0
- package/docs/components/overlay/dialog.md +69 -0
- package/docs/components/overlay/hover-card.md +25 -0
- package/docs/components/overlay/popover.md +36 -0
- package/docs/components/overlay/tooltip.md +28 -0
- package/docs/examples.md +155 -0
- package/docs/release.md +60 -0
- package/docs/testing.md +36 -0
- package/package.json +89 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# NavigationMenu
|
|
2
|
+
|
|
3
|
+
Application/navigation menu primitives.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `NavigationMenuRoot`
|
|
8
|
+
- `NavigationMenuList`
|
|
9
|
+
- `NavigationMenuItem`
|
|
10
|
+
- `NavigationMenuTrigger`
|
|
11
|
+
- `NavigationMenuContent`
|
|
12
|
+
- `NavigationMenuLink`
|
|
13
|
+
- `NavigationMenuIndicator`
|
|
14
|
+
- `NavigationMenuViewport`
|
|
15
|
+
|
|
16
|
+
## Minimal Example
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import {
|
|
20
|
+
NavigationMenuRoot,
|
|
21
|
+
NavigationMenuList,
|
|
22
|
+
NavigationMenuItem,
|
|
23
|
+
NavigationMenuTrigger,
|
|
24
|
+
NavigationMenuContent,
|
|
25
|
+
NavigationMenuLink,
|
|
26
|
+
} from '@fictjs/ui-primitives'
|
|
27
|
+
|
|
28
|
+
<NavigationMenuRoot>
|
|
29
|
+
<NavigationMenuList>
|
|
30
|
+
<NavigationMenuItem value="docs">
|
|
31
|
+
<NavigationMenuTrigger>Docs</NavigationMenuTrigger>
|
|
32
|
+
<NavigationMenuContent>
|
|
33
|
+
<NavigationMenuLink href="/guide">Guide</NavigationMenuLink>
|
|
34
|
+
</NavigationMenuContent>
|
|
35
|
+
</NavigationMenuItem>
|
|
36
|
+
</NavigationMenuList>
|
|
37
|
+
</NavigationMenuRoot>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Accessibility Notes
|
|
41
|
+
|
|
42
|
+
- Triggers should expose open/closed state and remain keyboard reachable.
|
|
43
|
+
- Content should contain real link targets, not only generic containers.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Tabs
|
|
2
|
+
|
|
3
|
+
Accessible tabs primitives with roving focus tab list.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `TabsRoot`
|
|
8
|
+
- `TabsList`
|
|
9
|
+
- `TabsTrigger`
|
|
10
|
+
- `TabsContent`
|
|
11
|
+
|
|
12
|
+
## Key APIs
|
|
13
|
+
|
|
14
|
+
- `TabsRoot` accepts `id?: string` to stabilize trigger/content id mapping
|
|
15
|
+
- `TabsTrigger` supports `asChild`
|
|
16
|
+
|
|
17
|
+
## Minimal Example
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { TabsRoot, TabsList, TabsTrigger, TabsContent } from '@fictjs/ui-primitives'
|
|
21
|
+
|
|
22
|
+
<TabsRoot defaultValue="overview">
|
|
23
|
+
<TabsList>
|
|
24
|
+
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
25
|
+
<TabsTrigger value="details">Details</TabsTrigger>
|
|
26
|
+
</TabsList>
|
|
27
|
+
<TabsContent value="overview">Overview panel</TabsContent>
|
|
28
|
+
<TabsContent value="details">Details panel</TabsContent>
|
|
29
|
+
</TabsRoot>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Accessibility Notes
|
|
33
|
+
|
|
34
|
+
- Keep `TabsTrigger`/`TabsContent` value pairs unique to avoid broken `aria-controls` links.
|
|
35
|
+
- Use meaningful trigger labels so screen-reader users can navigate tab options quickly.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Toast
|
|
2
|
+
|
|
3
|
+
Queued toast primitives with provider-managed lifecycle.
|
|
4
|
+
|
|
5
|
+
## Components / Hook
|
|
6
|
+
|
|
7
|
+
- `ToastProvider`
|
|
8
|
+
- `ToastViewport`
|
|
9
|
+
- `ToastRoot`
|
|
10
|
+
- `ToastTitle`
|
|
11
|
+
- `ToastDescription`
|
|
12
|
+
- `ToastAction`
|
|
13
|
+
- `ToastClose`
|
|
14
|
+
- `useToast()`
|
|
15
|
+
|
|
16
|
+
## Provider API
|
|
17
|
+
|
|
18
|
+
- `ToastProvider`
|
|
19
|
+
- `duration?: number` default timeout for queued toasts
|
|
20
|
+
- `useToast().show({ title, description, duration? })` enqueues a toast and returns `id`
|
|
21
|
+
- `useToast().dismiss(id)` removes a toast immediately
|
|
22
|
+
|
|
23
|
+
## Viewport Semantics
|
|
24
|
+
|
|
25
|
+
- `ToastViewport` renders queued toasts with `aria-live="polite"`
|
|
26
|
+
- Toasts auto-dismiss by duration (item duration overrides provider default)
|
|
27
|
+
|
|
28
|
+
## Toast Parts
|
|
29
|
+
|
|
30
|
+
- `ToastRoot` renders title/description and default close action
|
|
31
|
+
- `ToastAction` requires `altText` and maps it to accessible label
|
|
32
|
+
- `ToastClose` is a plain close button primitive for custom templates
|
|
33
|
+
|
|
34
|
+
## Minimal Example
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { ToastProvider, ToastViewport, useToast } from '@fictjs/ui-primitives'
|
|
38
|
+
|
|
39
|
+
function SaveButton() {
|
|
40
|
+
const toast = useToast()
|
|
41
|
+
return (
|
|
42
|
+
<button
|
|
43
|
+
type="button"
|
|
44
|
+
onClick={() => toast.show({ title: 'Saved', description: 'Changes synced.' })}
|
|
45
|
+
>
|
|
46
|
+
Save
|
|
47
|
+
</button>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
<ToastProvider duration={3000}>
|
|
52
|
+
<SaveButton />
|
|
53
|
+
<ToastViewport />
|
|
54
|
+
</ToastProvider>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Accessibility Notes
|
|
58
|
+
|
|
59
|
+
- Keep toast title/description concise so live-region announcements stay scannable.
|
|
60
|
+
- `ToastAction` must include meaningful `altText` for assistive technologies.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Calendar
|
|
2
|
+
|
|
3
|
+
Headless date grid primitive with month navigation and controlled/uncontrolled value.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `CalendarRoot` (alias: `Calendar`)
|
|
8
|
+
- `CalendarHeader`
|
|
9
|
+
- `CalendarTitle`
|
|
10
|
+
- `CalendarPrevButton`
|
|
11
|
+
- `CalendarNextButton`
|
|
12
|
+
- `CalendarGrid`
|
|
13
|
+
|
|
14
|
+
## Key APIs
|
|
15
|
+
|
|
16
|
+
- `value/defaultValue/onValueChange` for selected day
|
|
17
|
+
- `month/defaultMonth/onMonthChange` for visible month
|
|
18
|
+
- `showOutsideDays`, `weekStartsOn`, `weekdayFormat`, `locale`
|
|
19
|
+
- `disabled?: (date: Date) => boolean` to block specific dates
|
|
20
|
+
|
|
21
|
+
## Minimal Example
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { CalendarRoot } from '@fictjs/ui-primitives'
|
|
25
|
+
|
|
26
|
+
<CalendarRoot
|
|
27
|
+
defaultMonth={new Date(2026, 0, 1)}
|
|
28
|
+
onValueChange={date => console.log(date)}
|
|
29
|
+
/>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Accessibility Notes
|
|
33
|
+
|
|
34
|
+
- Calendar grid uses `role="grid"` / `role="gridcell"` with `aria-selected`.
|
|
35
|
+
- Keep day labels and surrounding form labels explicit for screen-reader context.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Form Controls
|
|
2
|
+
|
|
3
|
+
Core form-control primitives.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `Label`
|
|
8
|
+
- `Checkbox`
|
|
9
|
+
- `RadioGroup` / `RadioItem`
|
|
10
|
+
- `Switch` / `SwitchThumb`
|
|
11
|
+
- `Toggle` / `ToggleGroup` / `ToggleGroupItem`
|
|
12
|
+
|
|
13
|
+
## Minimal Example
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import {
|
|
17
|
+
Label,
|
|
18
|
+
Checkbox,
|
|
19
|
+
RadioGroup,
|
|
20
|
+
RadioItem,
|
|
21
|
+
Switch,
|
|
22
|
+
SwitchThumb,
|
|
23
|
+
Toggle,
|
|
24
|
+
ToggleGroup,
|
|
25
|
+
ToggleGroupItem,
|
|
26
|
+
} from '@fictjs/ui-primitives'
|
|
27
|
+
|
|
28
|
+
<Label htmlFor="tos">Accept terms</Label>
|
|
29
|
+
<Checkbox id="tos" name="tos" defaultChecked>Accept</Checkbox>
|
|
30
|
+
|
|
31
|
+
<RadioGroup name="density" defaultValue="comfortable">
|
|
32
|
+
<RadioItem value="compact">Compact</RadioItem>
|
|
33
|
+
<RadioItem value="comfortable">Comfortable</RadioItem>
|
|
34
|
+
</RadioGroup>
|
|
35
|
+
|
|
36
|
+
<Switch name="notifications" defaultChecked>
|
|
37
|
+
<SwitchThumb />
|
|
38
|
+
</Switch>
|
|
39
|
+
|
|
40
|
+
<Toggle defaultPressed>Bold</Toggle>
|
|
41
|
+
|
|
42
|
+
<ToggleGroup type="multiple" defaultValue={['left']}>
|
|
43
|
+
<ToggleGroupItem value="left">Left</ToggleGroupItem>
|
|
44
|
+
<ToggleGroupItem value="center">Center</ToggleGroupItem>
|
|
45
|
+
</ToggleGroup>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Accessibility Notes
|
|
49
|
+
|
|
50
|
+
- Keep textual labels adjacent to icon-only controls (`Checkbox`, `Switch`, `Toggle`).
|
|
51
|
+
- Radio groups should represent one conceptual choice and use clear item labels.
|
|
52
|
+
- `aria-checked` / `aria-pressed` states are built-in; ensure visual state mirrors them.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# DatePicker
|
|
2
|
+
|
|
3
|
+
Popover-based date picker composed from `Popover` + `Calendar`.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `DatePickerRoot`
|
|
8
|
+
- `DatePickerTrigger`
|
|
9
|
+
- `DatePickerValue`
|
|
10
|
+
- `DatePickerContent`
|
|
11
|
+
- `DatePickerCalendar`
|
|
12
|
+
|
|
13
|
+
## Key APIs
|
|
14
|
+
|
|
15
|
+
- `DatePickerRoot`: `value/defaultValue/onValueChange`, `open/defaultOpen/onOpenChange`
|
|
16
|
+
- `DatePickerTrigger`: supports polymorphism and `asChild` through inherited popover trigger semantics
|
|
17
|
+
- `DatePickerContent`: `closeOnSelect` defaults to `true`
|
|
18
|
+
- `DatePickerCalendar`: forwards calendar options while binding to date-picker value/open state
|
|
19
|
+
|
|
20
|
+
## Minimal Example
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import {
|
|
24
|
+
DatePickerRoot,
|
|
25
|
+
DatePickerTrigger,
|
|
26
|
+
DatePickerValue,
|
|
27
|
+
DatePickerContent,
|
|
28
|
+
DatePickerCalendar,
|
|
29
|
+
} from '@fictjs/ui-primitives'
|
|
30
|
+
|
|
31
|
+
<DatePickerRoot>
|
|
32
|
+
<DatePickerTrigger>
|
|
33
|
+
<DatePickerValue placeholder="Pick a date" />
|
|
34
|
+
</DatePickerTrigger>
|
|
35
|
+
<DatePickerContent>
|
|
36
|
+
<DatePickerCalendar />
|
|
37
|
+
</DatePickerContent>
|
|
38
|
+
</DatePickerRoot>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Accessibility Notes
|
|
42
|
+
|
|
43
|
+
- Trigger/content wiring is inherited from popover/dialog semantics.
|
|
44
|
+
- Keep explicit labels around date picker fields so users understand date context and format expectations.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Form Field Structure
|
|
2
|
+
|
|
3
|
+
Semantic form field primitives.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `Form`
|
|
8
|
+
- `FormField`
|
|
9
|
+
- `FormLabel`
|
|
10
|
+
- `FormControl`
|
|
11
|
+
- `FormDescription`
|
|
12
|
+
- `FormMessage`
|
|
13
|
+
|
|
14
|
+
## Minimal Example
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import {
|
|
18
|
+
Form,
|
|
19
|
+
FormField,
|
|
20
|
+
FormLabel,
|
|
21
|
+
FormControl,
|
|
22
|
+
FormDescription,
|
|
23
|
+
FormMessage,
|
|
24
|
+
} from '@fictjs/ui-primitives'
|
|
25
|
+
|
|
26
|
+
<Form>
|
|
27
|
+
<FormField name="email">
|
|
28
|
+
<FormLabel>Email</FormLabel>
|
|
29
|
+
<FormControl as="input" type="email" required />
|
|
30
|
+
<FormDescription>Use your work email.</FormDescription>
|
|
31
|
+
<FormMessage>Invalid email format.</FormMessage>
|
|
32
|
+
</FormField>
|
|
33
|
+
</Form>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Accessibility Notes
|
|
37
|
+
|
|
38
|
+
- `FormLabel` and `FormControl` id wiring is automatic; avoid overriding ids unless necessary.
|
|
39
|
+
- Keep `FormMessage` contextual and specific because it is announced with alert semantics.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Advanced Inputs
|
|
2
|
+
|
|
3
|
+
Interactive input primitives.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `Slider`
|
|
8
|
+
- `RangeSlider`
|
|
9
|
+
- `CalendarRoot` / `Calendar*`
|
|
10
|
+
- `DatePickerRoot` / `DatePicker*`
|
|
11
|
+
- `SelectRoot` / `SelectTrigger` / `SelectValue` / `SelectContent` / `SelectItem`
|
|
12
|
+
- `ComboboxRoot` / `ComboboxInput` / `ComboboxList` / `ComboboxItem`
|
|
13
|
+
|
|
14
|
+
## Slider / RangeSlider
|
|
15
|
+
|
|
16
|
+
- Controlled + uncontrolled support via `value/defaultValue/onValueChange`
|
|
17
|
+
- `Slider` maps to native range input semantics
|
|
18
|
+
- `RangeSlider` maintains ordered tuple `[start, end]`
|
|
19
|
+
|
|
20
|
+
## Select
|
|
21
|
+
|
|
22
|
+
- `SelectRoot` supports both controlled and uncontrolled `value` and `open`
|
|
23
|
+
- `SelectTrigger` toggles listbox visibility (`aria-haspopup="listbox"`)
|
|
24
|
+
- `SelectContent` supports `forceMount` for always-mounted popups
|
|
25
|
+
- `SelectItem` commits value and closes popup by default
|
|
26
|
+
|
|
27
|
+
## Combobox
|
|
28
|
+
|
|
29
|
+
- `ComboboxInput` drives query and opens list on focus/input
|
|
30
|
+
- `ComboboxItem` performs simple text filtering against current query
|
|
31
|
+
- Selecting an item commits value, syncs query, and closes list
|
|
32
|
+
|
|
33
|
+
## Calendar / DatePicker
|
|
34
|
+
|
|
35
|
+
- `CalendarRoot` manages selected date and visible month state
|
|
36
|
+
- `DatePickerRoot` composes popover trigger/content around calendar interactions
|
|
37
|
+
- `DatePickerCalendar` closes picker on selection by default (`closeOnSelect=true`)
|
|
38
|
+
|
|
39
|
+
## Minimal Example
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import {
|
|
43
|
+
Slider,
|
|
44
|
+
RangeSlider,
|
|
45
|
+
SelectRoot,
|
|
46
|
+
SelectTrigger,
|
|
47
|
+
SelectValue,
|
|
48
|
+
SelectContent,
|
|
49
|
+
SelectItem,
|
|
50
|
+
ComboboxRoot,
|
|
51
|
+
ComboboxInput,
|
|
52
|
+
ComboboxList,
|
|
53
|
+
ComboboxItem,
|
|
54
|
+
CalendarRoot,
|
|
55
|
+
DatePickerRoot,
|
|
56
|
+
DatePickerTrigger,
|
|
57
|
+
DatePickerValue,
|
|
58
|
+
DatePickerContent,
|
|
59
|
+
DatePickerCalendar,
|
|
60
|
+
} from '@fictjs/ui-primitives'
|
|
61
|
+
|
|
62
|
+
<Slider min={0} max={100} defaultValue={40} />
|
|
63
|
+
<RangeSlider min={0} max={100} defaultValue={[20, 80]} />
|
|
64
|
+
|
|
65
|
+
<SelectRoot defaultValue="apple">
|
|
66
|
+
<SelectTrigger>
|
|
67
|
+
<SelectValue placeholder="Pick one" />
|
|
68
|
+
</SelectTrigger>
|
|
69
|
+
<SelectContent>
|
|
70
|
+
<SelectItem value="apple">Apple</SelectItem>
|
|
71
|
+
<SelectItem value="orange">Orange</SelectItem>
|
|
72
|
+
</SelectContent>
|
|
73
|
+
</SelectRoot>
|
|
74
|
+
|
|
75
|
+
<ComboboxRoot>
|
|
76
|
+
<ComboboxInput placeholder="Search user" />
|
|
77
|
+
<ComboboxList>
|
|
78
|
+
<ComboboxItem value="alice">Alice</ComboboxItem>
|
|
79
|
+
<ComboboxItem value="bob">Bob</ComboboxItem>
|
|
80
|
+
</ComboboxList>
|
|
81
|
+
</ComboboxRoot>
|
|
82
|
+
|
|
83
|
+
<CalendarRoot defaultMonth={new Date(2026, 0, 1)} />
|
|
84
|
+
|
|
85
|
+
<DatePickerRoot>
|
|
86
|
+
<DatePickerTrigger>
|
|
87
|
+
<DatePickerValue placeholder="Pick a date" />
|
|
88
|
+
</DatePickerTrigger>
|
|
89
|
+
<DatePickerContent>
|
|
90
|
+
<DatePickerCalendar />
|
|
91
|
+
</DatePickerContent>
|
|
92
|
+
</DatePickerRoot>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Accessibility Notes
|
|
96
|
+
|
|
97
|
+
- For select/combobox, keep option text concise and distinct to avoid ambiguous announcements.
|
|
98
|
+
- If you force-mount popup content, keep hidden-state signaling consistent.
|
|
99
|
+
- Range-like controls should include external labels that describe scale and intent.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# DismissableLayer
|
|
2
|
+
|
|
3
|
+
Provides common dismissal semantics used by overlays.
|
|
4
|
+
|
|
5
|
+
## Events
|
|
6
|
+
|
|
7
|
+
- `onEscapeKeyDown`
|
|
8
|
+
- `onInteractOutside`
|
|
9
|
+
- `onPointerDownOutside`
|
|
10
|
+
- `onFocusOutside`
|
|
11
|
+
- `onDismiss`
|
|
12
|
+
|
|
13
|
+
Only the top-most mounted layer responds.
|
|
14
|
+
|
|
15
|
+
## Minimal Example
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
import { DismissableLayer } from '@fictjs/ui-primitives'
|
|
19
|
+
|
|
20
|
+
<DismissableLayer onDismiss={() => console.log('dismissed')}>
|
|
21
|
+
<div role="dialog">Layer content</div>
|
|
22
|
+
</DismissableLayer>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Accessibility Notes
|
|
26
|
+
|
|
27
|
+
- Dismiss paths should include explicit close controls in addition to outside/Escape behavior.
|
|
28
|
+
- Only the top-most layer responds; stack layered popups intentionally.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# FocusScope / FocusTrap
|
|
2
|
+
|
|
3
|
+
`FocusScope` manages keyboard focus within a subtree.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- optional focus trapping with Tab cycling
|
|
8
|
+
- optional auto focus on mount
|
|
9
|
+
- optional focus restore on unmount
|
|
10
|
+
|
|
11
|
+
`FocusTrap` is `FocusScope` with `trapped=true`.
|
|
12
|
+
|
|
13
|
+
## Minimal Example
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { FocusScope } from '@fictjs/ui-primitives'
|
|
17
|
+
|
|
18
|
+
<FocusScope trapped loop autoFocus restoreFocus>
|
|
19
|
+
<button type="button">First</button>
|
|
20
|
+
<button type="button">Second</button>
|
|
21
|
+
</FocusScope>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Accessibility Notes
|
|
25
|
+
|
|
26
|
+
- Use focus trapping only for modal contexts.
|
|
27
|
+
- Keep at least one tabbable control inside the scope for predictable keyboard flow.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# LiveRegion / Announce
|
|
2
|
+
|
|
3
|
+
Screen-reader announcement primitives.
|
|
4
|
+
|
|
5
|
+
## API
|
|
6
|
+
|
|
7
|
+
- `LiveRegionProvider`
|
|
8
|
+
- `useAnnouncer()`
|
|
9
|
+
- `Announce`
|
|
10
|
+
|
|
11
|
+
Two regions are maintained: polite and assertive.
|
|
12
|
+
|
|
13
|
+
## Minimal Example
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import { Announce, LiveRegionProvider } from '@fictjs/ui-primitives'
|
|
17
|
+
|
|
18
|
+
<LiveRegionProvider>
|
|
19
|
+
<Announce politeness="polite" message="Saved successfully" />
|
|
20
|
+
</LiveRegionProvider>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Accessibility Notes
|
|
24
|
+
|
|
25
|
+
- Use `polite` for non-urgent updates and `assertive` only for urgent interruptions.
|
|
26
|
+
- Keep announcements short and specific to reduce repeated verbosity in screen readers.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Popper
|
|
2
|
+
|
|
3
|
+
Lightweight floating positioning primitives.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `PopperRoot`
|
|
8
|
+
- `PopperAnchor`
|
|
9
|
+
- `PopperContent`
|
|
10
|
+
- `PopperArrow`
|
|
11
|
+
|
|
12
|
+
Supports side/alignment offsets and updates on resize/scroll.
|
|
13
|
+
|
|
14
|
+
## Minimal Example
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { PopperAnchor, PopperContent, PopperRoot } from '@fictjs/ui-primitives'
|
|
18
|
+
|
|
19
|
+
<PopperRoot>
|
|
20
|
+
<PopperAnchor>
|
|
21
|
+
<button type="button">Open</button>
|
|
22
|
+
</PopperAnchor>
|
|
23
|
+
<PopperContent side="bottom" align="start">Floating content</PopperContent>
|
|
24
|
+
</PopperRoot>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Accessibility Notes
|
|
28
|
+
|
|
29
|
+
- Popper only positions; you still need roles, labels, and dismissal semantics in the floating content.
|
|
30
|
+
- Keep anchor/content relationship explicit with `aria-controls` or `aria-describedby` where applicable.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# RovingFocusGroup
|
|
2
|
+
|
|
3
|
+
Implements roving `tabIndex` behavior for composite widgets.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `RovingFocusGroup`
|
|
8
|
+
- `RovingFocusItem`
|
|
9
|
+
|
|
10
|
+
Supports arrow navigation, `Home`, `End`, and optional looping.
|
|
11
|
+
|
|
12
|
+
## Minimal Example
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { RovingFocusGroup, RovingFocusItem } from '@fictjs/ui-primitives'
|
|
16
|
+
|
|
17
|
+
<RovingFocusGroup orientation="horizontal" loop>
|
|
18
|
+
<RovingFocusItem as="button">Bold</RovingFocusItem>
|
|
19
|
+
<RovingFocusItem as="button">Italic</RovingFocusItem>
|
|
20
|
+
<RovingFocusItem as="button">Underline</RovingFocusItem>
|
|
21
|
+
</RovingFocusGroup>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Accessibility Notes
|
|
25
|
+
|
|
26
|
+
- Use roving focus for composite widgets where one tab stop controls many arrow-key targets.
|
|
27
|
+
- Ensure orientation matches expected arrow-key behavior for assistive technology users.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# ScrollLock
|
|
2
|
+
|
|
3
|
+
Locks `document.body` scroll while mounted.
|
|
4
|
+
|
|
5
|
+
- supports nested locks with internal reference counting
|
|
6
|
+
- compensates scrollbar width via `padding-right`
|
|
7
|
+
|
|
8
|
+
## Minimal Example
|
|
9
|
+
|
|
10
|
+
```tsx
|
|
11
|
+
import { ScrollLock } from '@fictjs/ui-primitives'
|
|
12
|
+
|
|
13
|
+
<div>
|
|
14
|
+
<ScrollLock enabled />
|
|
15
|
+
<div role="dialog" aria-modal="true">Modal body</div>
|
|
16
|
+
</div>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Accessibility Notes
|
|
20
|
+
|
|
21
|
+
- Scroll lock is not a complete modal solution; combine with focus trap and dismissal handling.
|
|
22
|
+
- Verify nested overlays unlock scroll only after the final lock is removed.
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Layout and P2 Primitives
|
|
2
|
+
|
|
3
|
+
## Components
|
|
4
|
+
|
|
5
|
+
- `ScrollArea`, `ScrollAreaViewport`, `ScrollAreaScrollbar`, `ScrollAreaThumb`
|
|
6
|
+
- `ResizablePanelGroup`, `ResizablePanel`, `ResizableHandle`
|
|
7
|
+
- `AspectRatio`
|
|
8
|
+
- `Progress`, `Meter`
|
|
9
|
+
- `Skeleton`
|
|
10
|
+
- `KeyboardModeProvider`, `FocusVisible`, `useKeyboardMode`
|
|
11
|
+
|
|
12
|
+
## Minimal Example
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import {
|
|
16
|
+
ScrollArea,
|
|
17
|
+
ScrollAreaViewport,
|
|
18
|
+
ScrollAreaScrollbar,
|
|
19
|
+
ScrollAreaThumb,
|
|
20
|
+
ResizablePanelGroup,
|
|
21
|
+
ResizablePanel,
|
|
22
|
+
ResizableHandle,
|
|
23
|
+
AspectRatio,
|
|
24
|
+
Progress,
|
|
25
|
+
Meter,
|
|
26
|
+
Skeleton,
|
|
27
|
+
KeyboardModeProvider,
|
|
28
|
+
FocusVisible,
|
|
29
|
+
} from '@fictjs/ui-primitives'
|
|
30
|
+
|
|
31
|
+
<ScrollArea>
|
|
32
|
+
<ScrollAreaViewport style={{ maxHeight: '120px' }}>Long content...</ScrollAreaViewport>
|
|
33
|
+
<ScrollAreaScrollbar orientation="vertical">
|
|
34
|
+
<ScrollAreaThumb />
|
|
35
|
+
</ScrollAreaScrollbar>
|
|
36
|
+
</ScrollArea>
|
|
37
|
+
|
|
38
|
+
<ResizablePanelGroup direction="horizontal">
|
|
39
|
+
<ResizablePanel defaultSize={40}>Left</ResizablePanel>
|
|
40
|
+
<ResizableHandle withHandle />
|
|
41
|
+
<ResizablePanel defaultSize={60}>Right</ResizablePanel>
|
|
42
|
+
</ResizablePanelGroup>
|
|
43
|
+
|
|
44
|
+
<AspectRatio ratio={16 / 9}>
|
|
45
|
+
<img src="/placeholder.jpg" alt="Preview" />
|
|
46
|
+
</AspectRatio>
|
|
47
|
+
|
|
48
|
+
<Progress value={65} max={100} />
|
|
49
|
+
<Meter value={0.7} min={0} max={1} low={0.3} high={0.8} optimum={0.9} />
|
|
50
|
+
<Skeleton loading>Loading card...</Skeleton>
|
|
51
|
+
|
|
52
|
+
<KeyboardModeProvider>
|
|
53
|
+
<FocusVisible as="button">Focusable action</FocusVisible>
|
|
54
|
+
</KeyboardModeProvider>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Accessibility Notes
|
|
58
|
+
|
|
59
|
+
- Custom scroll/resize affordances should preserve keyboard access and visible focus states.
|
|
60
|
+
- `Progress` and `Meter` should have nearby textual context explaining value meaning.
|
|
61
|
+
- `Skeleton` placeholders are visual only; pair with status messaging for assistive tech when needed.
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# ContextMenu
|
|
2
|
+
|
|
3
|
+
Context menu primitives opened from right-click position.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `ContextMenuRoot`
|
|
8
|
+
- `ContextMenuTrigger`
|
|
9
|
+
- `ContextMenuContent`
|
|
10
|
+
- `ContextMenuItem`
|
|
11
|
+
- `ContextMenuSub`
|
|
12
|
+
- `ContextMenuSubTrigger`
|
|
13
|
+
- `ContextMenuSubContent`
|
|
14
|
+
|
|
15
|
+
## Key APIs
|
|
16
|
+
|
|
17
|
+
- `ContextMenuTrigger` and `ContextMenuItem` support `asChild`
|
|
18
|
+
- `ContextMenuContent` supports `onEscapeKeyDown`, `onPointerDownOutside`, `onFocusOutside`, `onInteractOutside`
|
|
19
|
+
- Outside handlers are interceptable: calling `event.preventDefault()` blocks dismissal
|
|
20
|
+
- `ContextMenuSub*` composes nested menus with side positioning (`right/start` by default)
|
|
21
|
+
|
|
22
|
+
## Minimal Example
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import {
|
|
26
|
+
ContextMenuRoot,
|
|
27
|
+
ContextMenuTrigger,
|
|
28
|
+
ContextMenuContent,
|
|
29
|
+
ContextMenuItem,
|
|
30
|
+
} from '@fictjs/ui-primitives'
|
|
31
|
+
|
|
32
|
+
<ContextMenuRoot>
|
|
33
|
+
<ContextMenuTrigger>Right-click here</ContextMenuTrigger>
|
|
34
|
+
<ContextMenuContent>
|
|
35
|
+
<ContextMenuItem>Rename</ContextMenuItem>
|
|
36
|
+
<ContextMenuItem>Delete</ContextMenuItem>
|
|
37
|
+
</ContextMenuContent>
|
|
38
|
+
</ContextMenuRoot>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Accessibility Notes
|
|
42
|
+
|
|
43
|
+
- Pair right-click entry with an alternate keyboard path in your product UX.
|
|
44
|
+
- Menu items should have clear action labels and predictable close behavior.
|