@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
package/docs/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Documentation Index
|
|
2
|
+
|
|
3
|
+
Start here for all package documentation.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
- `README.md`: package overview, install, quick start, and command map
|
|
8
|
+
- `docs/examples.md`: copyable composition snippets
|
|
9
|
+
- `examples/README.md`: executable demo app and screenshot baseline workflow
|
|
10
|
+
|
|
11
|
+
## Component Documentation
|
|
12
|
+
|
|
13
|
+
- `docs/components/core`
|
|
14
|
+
- `docs/components/interaction`
|
|
15
|
+
- `docs/components/overlay`
|
|
16
|
+
- `docs/components/menu`
|
|
17
|
+
- `docs/components/feedback`
|
|
18
|
+
- `docs/components/disclosure`
|
|
19
|
+
- `docs/components/form`
|
|
20
|
+
- `docs/components/layout`
|
|
21
|
+
|
|
22
|
+
Each component file includes:
|
|
23
|
+
|
|
24
|
+
- minimal example
|
|
25
|
+
- accessibility notes
|
|
26
|
+
- behavior/API summary
|
|
27
|
+
|
|
28
|
+
## Engineering and Quality Docs
|
|
29
|
+
|
|
30
|
+
- `docs/api-reference.md`: full export index
|
|
31
|
+
- `docs/architecture.md`: implementation design and internal patterns
|
|
32
|
+
- `docs/testing.md`: testing strategy and expectations
|
|
33
|
+
- `docs/accessibility.md`: accessibility verification checklist
|
|
34
|
+
- `docs/release.md`: release and publish checklist
|
|
35
|
+
- `CONTRIBUTING.md`: contributor workflow and PR quality expectations
|
|
36
|
+
|
|
37
|
+
## Source Planning
|
|
38
|
+
|
|
39
|
+
- `ui-primitives.md`: source planning/scope document used during implementation
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Accessibility Checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist when building or reviewing primitives.
|
|
4
|
+
|
|
5
|
+
## Global checks
|
|
6
|
+
|
|
7
|
+
1. Every interactive element is keyboard reachable (`Tab`, `Shift+Tab`, `Enter`, `Space`, arrow keys where applicable).
|
|
8
|
+
2. Focus is always visible and never trapped unless intentionally scoped (`FocusScope`, modal overlays).
|
|
9
|
+
3. Interactive controls expose semantic roles and states (`role`, `aria-*`, `data-state`).
|
|
10
|
+
4. Dismiss flows are complete: Escape, outside click (when expected), and explicit close action.
|
|
11
|
+
5. Dynamic regions (toast/live messages) use polite or assertive live-region semantics.
|
|
12
|
+
6. Hidden helper text is exposed only to assistive tech (`VisuallyHidden`, `AccessibleIcon` labels).
|
|
13
|
+
|
|
14
|
+
## Overlay and popups
|
|
15
|
+
|
|
16
|
+
- `Dialog` / `AlertDialog`: trigger uses `aria-haspopup`, content has dialog role, title/description IDs are wired, focus enters content and restores on close.
|
|
17
|
+
- `Popover` / `HoverCard` / `Tooltip`: trigger/content relationships are explicit (`aria-describedby` for tooltip), and hover/focus behavior has deterministic open/close.
|
|
18
|
+
- Modal overlays lock background scroll and block outside interaction where expected.
|
|
19
|
+
|
|
20
|
+
## Menus and navigation
|
|
21
|
+
|
|
22
|
+
- Menu roots render proper container roles (`menu`, `menubar`) and items expose matching item roles.
|
|
23
|
+
- Checkbox/radio menu items expose checked state (`aria-checked`) and maintain controlled/uncontrolled behavior.
|
|
24
|
+
- Roving focus handles arrow-key traversal and loop behavior consistently.
|
|
25
|
+
- Navigation content open state is exposed through `data-state` and trigger state attributes.
|
|
26
|
+
|
|
27
|
+
## Tabs and disclosure
|
|
28
|
+
|
|
29
|
+
- Tabs: triggers use `role="tab"` + `aria-controls`, content uses `role="tabpanel"` + `aria-labelledby`.
|
|
30
|
+
- Accordion/collapsible triggers expose expanded state and toggle content visibility predictably.
|
|
31
|
+
- `forceMount` variants keep semantic state in sync while hidden (`data-state="closed"` / inactive).
|
|
32
|
+
|
|
33
|
+
## Form primitives
|
|
34
|
+
|
|
35
|
+
- Labels are explicitly associated with controls (`for` / `id`).
|
|
36
|
+
- Descriptions and messages are connected through `aria-describedby`.
|
|
37
|
+
- Error messages use alert semantics when they need immediate announcement.
|
|
38
|
+
- Select/combobox/listbox controls provide role/state attributes for active and selected options.
|
|
39
|
+
|
|
40
|
+
## Feedback primitives
|
|
41
|
+
|
|
42
|
+
- Toast viewport has live region semantics and close/action controls have explicit labels.
|
|
43
|
+
- Auto-dismiss timing can be overridden and manual dismissal is always possible.
|
|
44
|
+
|
|
45
|
+
## Verification workflow
|
|
46
|
+
|
|
47
|
+
1. Run `pnpm test` to verify behavioral and semantic assertions.
|
|
48
|
+
2. Run through keyboard-only flows for each updated primitive.
|
|
49
|
+
3. Validate screen-reader announcements for dialogs, form errors, and toasts.
|
|
50
|
+
4. Use the runnable demo (`pnpm examples:dev`) for manual interaction walkthroughs.
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# API Reference Index
|
|
2
|
+
|
|
3
|
+
This document lists runtime exports from `@fictjs/ui-primitives`.
|
|
4
|
+
|
|
5
|
+
Note: each module also exports related TypeScript interfaces/types for props.
|
|
6
|
+
|
|
7
|
+
## Core
|
|
8
|
+
|
|
9
|
+
- `Primitive`
|
|
10
|
+
- `PrimitiveElements`
|
|
11
|
+
- `Slot`
|
|
12
|
+
- `Presence`
|
|
13
|
+
- `Portal`
|
|
14
|
+
- `PortalHost`
|
|
15
|
+
- `VisuallyHidden`
|
|
16
|
+
- `Separator`
|
|
17
|
+
- `AccessibleIcon`
|
|
18
|
+
- `IdProvider`
|
|
19
|
+
- `useId`
|
|
20
|
+
|
|
21
|
+
## Interaction
|
|
22
|
+
|
|
23
|
+
- `FocusScope`
|
|
24
|
+
- `FocusTrap`
|
|
25
|
+
- `DismissableLayer`
|
|
26
|
+
- `RovingFocusGroup`
|
|
27
|
+
- `RovingFocusItem`
|
|
28
|
+
- `ScrollLock`
|
|
29
|
+
- `LiveRegionProvider`
|
|
30
|
+
- `useAnnouncer`
|
|
31
|
+
- `Announce`
|
|
32
|
+
- `PopperRoot`
|
|
33
|
+
- `PopperAnchor`
|
|
34
|
+
- `PopperContent`
|
|
35
|
+
- `PopperArrow`
|
|
36
|
+
|
|
37
|
+
## Overlay
|
|
38
|
+
|
|
39
|
+
- `DialogRoot`
|
|
40
|
+
- `DialogPortal`
|
|
41
|
+
- `DialogTrigger`
|
|
42
|
+
- `DialogOverlay`
|
|
43
|
+
- `DialogContent`
|
|
44
|
+
- `DialogClose`
|
|
45
|
+
- `DialogTitle`
|
|
46
|
+
- `DialogDescription`
|
|
47
|
+
- `AlertDialogRoot`
|
|
48
|
+
- `AlertDialogTrigger`
|
|
49
|
+
- `AlertDialogPortal`
|
|
50
|
+
- `AlertDialogOverlay`
|
|
51
|
+
- `AlertDialogContent`
|
|
52
|
+
- `AlertDialogTitle`
|
|
53
|
+
- `AlertDialogDescription`
|
|
54
|
+
- `AlertDialogAction`
|
|
55
|
+
- `AlertDialogCancel`
|
|
56
|
+
- `PopoverRoot`
|
|
57
|
+
- `PopoverTrigger`
|
|
58
|
+
- `PopoverContent`
|
|
59
|
+
- `PopoverClose`
|
|
60
|
+
- `TooltipProvider`
|
|
61
|
+
- `TooltipRoot`
|
|
62
|
+
- `TooltipTrigger`
|
|
63
|
+
- `TooltipContent`
|
|
64
|
+
- `HoverCardRoot`
|
|
65
|
+
- `HoverCardTrigger`
|
|
66
|
+
- `HoverCardContent`
|
|
67
|
+
- `CommandPaletteRoot`
|
|
68
|
+
- `CommandPaletteTrigger`
|
|
69
|
+
- `CommandPaletteContent`
|
|
70
|
+
- `CommandPaletteInput`
|
|
71
|
+
- `CommandPaletteList`
|
|
72
|
+
- `CommandPaletteItem`
|
|
73
|
+
- `CommandPaletteEmpty`
|
|
74
|
+
- `CommandPaletteGroup`
|
|
75
|
+
- `CommandPaletteSeparator`
|
|
76
|
+
- `CommandPaletteClose`
|
|
77
|
+
|
|
78
|
+
## Menu
|
|
79
|
+
|
|
80
|
+
- `DropdownMenuRoot`
|
|
81
|
+
- `DropdownMenuTrigger`
|
|
82
|
+
- `DropdownMenuContent`
|
|
83
|
+
- `DropdownMenuItem`
|
|
84
|
+
- `DropdownMenuCheckboxItem`
|
|
85
|
+
- `DropdownMenuRadioGroup`
|
|
86
|
+
- `DropdownMenuRadioItem`
|
|
87
|
+
- `DropdownMenuSub`
|
|
88
|
+
- `DropdownMenuSubTrigger`
|
|
89
|
+
- `DropdownMenuSubContent`
|
|
90
|
+
- `DropdownMenuLabel`
|
|
91
|
+
- `DropdownMenuSeparator`
|
|
92
|
+
- `ContextMenuRoot`
|
|
93
|
+
- `ContextMenuTrigger`
|
|
94
|
+
- `ContextMenuContent`
|
|
95
|
+
- `ContextMenuItem`
|
|
96
|
+
- `ContextMenuSub`
|
|
97
|
+
- `ContextMenuSubTrigger`
|
|
98
|
+
- `ContextMenuSubContent`
|
|
99
|
+
- `MenubarRoot`
|
|
100
|
+
- `MenubarMenu`
|
|
101
|
+
- `MenubarTrigger`
|
|
102
|
+
- `MenubarContent`
|
|
103
|
+
- `MenubarItem`
|
|
104
|
+
|
|
105
|
+
## Feedback
|
|
106
|
+
|
|
107
|
+
- `ToastProvider`
|
|
108
|
+
- `ToastViewport`
|
|
109
|
+
- `ToastRoot`
|
|
110
|
+
- `ToastTitle`
|
|
111
|
+
- `ToastDescription`
|
|
112
|
+
- `ToastAction`
|
|
113
|
+
- `ToastClose`
|
|
114
|
+
- `useToast`
|
|
115
|
+
|
|
116
|
+
## Disclosure and Navigation
|
|
117
|
+
|
|
118
|
+
- `CollapsibleRoot`
|
|
119
|
+
- `CollapsibleTrigger`
|
|
120
|
+
- `CollapsibleContent`
|
|
121
|
+
- `AccordionRoot`
|
|
122
|
+
- `AccordionItem`
|
|
123
|
+
- `AccordionTrigger`
|
|
124
|
+
- `AccordionContent`
|
|
125
|
+
- `TabsRoot`
|
|
126
|
+
- `TabsList`
|
|
127
|
+
- `TabsTrigger`
|
|
128
|
+
- `TabsContent`
|
|
129
|
+
- `NavigationMenuRoot`
|
|
130
|
+
- `NavigationMenuList`
|
|
131
|
+
- `NavigationMenuItem`
|
|
132
|
+
- `NavigationMenuTrigger`
|
|
133
|
+
- `NavigationMenuContent`
|
|
134
|
+
- `NavigationMenuLink`
|
|
135
|
+
- `NavigationMenuIndicator`
|
|
136
|
+
- `NavigationMenuViewport`
|
|
137
|
+
|
|
138
|
+
## Form and Inputs
|
|
139
|
+
|
|
140
|
+
- `Label`
|
|
141
|
+
- `Checkbox`
|
|
142
|
+
- `RadioGroup`
|
|
143
|
+
- `RadioItem`
|
|
144
|
+
- `Switch`
|
|
145
|
+
- `SwitchThumb`
|
|
146
|
+
- `Toggle`
|
|
147
|
+
- `ToggleGroup`
|
|
148
|
+
- `ToggleGroupItem`
|
|
149
|
+
- `Slider`
|
|
150
|
+
- `RangeSlider`
|
|
151
|
+
- `CalendarRoot`
|
|
152
|
+
- `Calendar`
|
|
153
|
+
- `CalendarHeader`
|
|
154
|
+
- `CalendarTitle`
|
|
155
|
+
- `CalendarPrevButton`
|
|
156
|
+
- `CalendarNextButton`
|
|
157
|
+
- `CalendarGrid`
|
|
158
|
+
- `DatePickerRoot`
|
|
159
|
+
- `DatePickerTrigger`
|
|
160
|
+
- `DatePickerValue`
|
|
161
|
+
- `DatePickerContent`
|
|
162
|
+
- `DatePickerCalendar`
|
|
163
|
+
- `SelectRoot`
|
|
164
|
+
- `SelectTrigger`
|
|
165
|
+
- `SelectValue`
|
|
166
|
+
- `SelectContent`
|
|
167
|
+
- `SelectItem`
|
|
168
|
+
- `ComboboxRoot`
|
|
169
|
+
- `ComboboxInput`
|
|
170
|
+
- `ComboboxList`
|
|
171
|
+
- `ComboboxItem`
|
|
172
|
+
- `Form`
|
|
173
|
+
- `FormField`
|
|
174
|
+
- `FormLabel`
|
|
175
|
+
- `FormControl`
|
|
176
|
+
- `FormDescription`
|
|
177
|
+
- `FormMessage`
|
|
178
|
+
|
|
179
|
+
## Layout
|
|
180
|
+
|
|
181
|
+
- `ScrollArea`
|
|
182
|
+
- `ScrollAreaViewport`
|
|
183
|
+
- `ScrollAreaScrollbar`
|
|
184
|
+
- `ScrollAreaThumb`
|
|
185
|
+
- `ResizablePanelGroup`
|
|
186
|
+
- `ResizablePanel`
|
|
187
|
+
- `ResizableHandle`
|
|
188
|
+
- `AspectRatio`
|
|
189
|
+
- `Progress`
|
|
190
|
+
- `Meter`
|
|
191
|
+
- `Skeleton`
|
|
192
|
+
- `KeyboardModeProvider`
|
|
193
|
+
- `FocusVisible`
|
|
194
|
+
- `useKeyboardMode`
|
|
195
|
+
|
|
196
|
+
## Import Pattern
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import { DialogRoot, TabsRoot, ToastProvider } from '@fictjs/ui-primitives'
|
|
200
|
+
```
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Architecture Notes
|
|
2
|
+
|
|
3
|
+
This document describes the design and implementation structure of `@fictjs/ui-primitives`.
|
|
4
|
+
|
|
5
|
+
## Goals
|
|
6
|
+
|
|
7
|
+
- Provide headless primitives with minimal assumptions about styling
|
|
8
|
+
- Keep accessibility and interaction semantics explicit and testable
|
|
9
|
+
- Support controlled and uncontrolled patterns consistently
|
|
10
|
+
- Compose small primitives into larger compound components
|
|
11
|
+
|
|
12
|
+
## Repository Structure
|
|
13
|
+
|
|
14
|
+
- `src/components/core`: foundational composition primitives
|
|
15
|
+
- `src/components/interaction`: focus, dismissal, positioning, live regions
|
|
16
|
+
- `src/components/overlay`: dialog/popover/tooltip/hover-card/command-palette
|
|
17
|
+
- `src/components/menu`: dropdown/context/menubar patterns
|
|
18
|
+
- `src/components/disclosure`: tabs/accordion/collapsible/navigation menu
|
|
19
|
+
- `src/components/form`: form controls, calendar/date-picker, and structured field helpers
|
|
20
|
+
- `src/components/layout`: layout and visual utility primitives
|
|
21
|
+
- `src/internal`: shared internals used across components
|
|
22
|
+
|
|
23
|
+
## Core Implementation Patterns
|
|
24
|
+
|
|
25
|
+
### Runtime and hooks foundation
|
|
26
|
+
|
|
27
|
+
- Components are built on top of `@fictjs/runtime` signals/effects/context.
|
|
28
|
+
- Cross-cutting event and timing behavior is standardized with `@fictjs/hooks` (for example `useEventListener` and `useDebounceFn`).
|
|
29
|
+
- This keeps lifecycle cleanup centralized and reduces duplicated low-level DOM/timer logic.
|
|
30
|
+
|
|
31
|
+
### Compound component + context
|
|
32
|
+
|
|
33
|
+
Many features are implemented as root + parts using context.
|
|
34
|
+
|
|
35
|
+
Examples:
|
|
36
|
+
|
|
37
|
+
- `DialogRoot` + `DialogTrigger` + `DialogContent`
|
|
38
|
+
- `TabsRoot` + `TabsList` + `TabsTrigger` + `TabsContent`
|
|
39
|
+
- `SelectRoot` + `SelectTrigger` + `SelectContent` + `SelectItem`
|
|
40
|
+
|
|
41
|
+
### Controlled and uncontrolled state
|
|
42
|
+
|
|
43
|
+
Shared helper:
|
|
44
|
+
|
|
45
|
+
- `src/internal/state.ts` -> `createControllableState`
|
|
46
|
+
|
|
47
|
+
This ensures consistent behavior for `value/defaultValue/onChange` style APIs.
|
|
48
|
+
|
|
49
|
+
### Accessor-safe reads
|
|
50
|
+
|
|
51
|
+
Shared helper:
|
|
52
|
+
|
|
53
|
+
- `src/internal/accessor.ts` -> `read`
|
|
54
|
+
|
|
55
|
+
This unifies plain values and accessor functions (`T | () => T`) across props.
|
|
56
|
+
|
|
57
|
+
### Id and ref composition
|
|
58
|
+
|
|
59
|
+
Shared helpers:
|
|
60
|
+
|
|
61
|
+
- `src/internal/ids.ts` -> `createId`, `useId`, and `IdProvider` for deterministic aria wiring
|
|
62
|
+
- `src/internal/ref.ts` -> composed refs across parent and child consumers
|
|
63
|
+
|
|
64
|
+
Most root primitives accept explicit `id` props so teams can force stable SSR/hydration wiring.
|
|
65
|
+
|
|
66
|
+
## Interaction Stack Design
|
|
67
|
+
|
|
68
|
+
### Focus management
|
|
69
|
+
|
|
70
|
+
- `FocusScope` handles auto-focus, trap/loop behavior, and restore-focus semantics.
|
|
71
|
+
- Modal overlays use focus scoping as part of their lifecycle.
|
|
72
|
+
|
|
73
|
+
### Dismiss behavior
|
|
74
|
+
|
|
75
|
+
- `DismissableLayer` centralizes Escape, outside pointer, and outside focus dismissal.
|
|
76
|
+
- Only the top-most active layer can dismiss to avoid nested-layer conflicts.
|
|
77
|
+
- Outside interactions are interceptable (`onEscapeKeyDown`, `onPointerDownOutside`, `onFocusOutside`, `onInteractOutside`).
|
|
78
|
+
|
|
79
|
+
### Positioning
|
|
80
|
+
|
|
81
|
+
- `PopperRoot`, `PopperAnchor`, and `PopperContent` provide anchor-based placement.
|
|
82
|
+
- Overlay and menu primitives compose on top of Popper where needed.
|
|
83
|
+
|
|
84
|
+
### Portal strategy
|
|
85
|
+
|
|
86
|
+
- `Portal` and `PortalHost` support default body portaling or scoped containers.
|
|
87
|
+
- Overlay components default to portal rendering but can opt into inline rendering.
|
|
88
|
+
|
|
89
|
+
## Accessibility Strategy
|
|
90
|
+
|
|
91
|
+
- Prefer native semantics first, then add ARIA for composite widgets.
|
|
92
|
+
- Maintain explicit state attributes (`aria-*`, `data-state`) for testability.
|
|
93
|
+
- Include keyboard/pointer parity for open/close and navigation behavior.
|
|
94
|
+
- Use live regions for async feedback (`Toast`, `LiveRegionProvider`).
|
|
95
|
+
- Ensure `asChild` composition keeps semantics explicit at call sites.
|
|
96
|
+
|
|
97
|
+
See `docs/accessibility.md` for review checklist details.
|
|
98
|
+
|
|
99
|
+
## Testing Strategy
|
|
100
|
+
|
|
101
|
+
- Behavior-level tests live in `test/` (Vitest + JSDOM).
|
|
102
|
+
- Focus on state transitions, keyboard/pointer interactions, and semantic attributes.
|
|
103
|
+
- Avoid snapshot-only confidence; assert behavior and cleanup paths directly.
|
|
104
|
+
|
|
105
|
+
See `docs/testing.md` for contributor expectations.
|
|
106
|
+
|
|
107
|
+
## Demo and Baseline Screenshots
|
|
108
|
+
|
|
109
|
+
- Executable demo app lives in `examples/`.
|
|
110
|
+
- Automated baseline capture script lives in `scripts/capture-examples.mjs`.
|
|
111
|
+
- Baselines are stored in `examples/screenshots/baseline`.
|
|
112
|
+
|
|
113
|
+
This supports visual sanity checks alongside behavioral tests.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# AccessibleIcon
|
|
2
|
+
|
|
3
|
+
Wraps an icon with a hidden text label.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
- `label`: required accessible text
|
|
8
|
+
- `children`: icon node
|
|
9
|
+
|
|
10
|
+
## Minimal Example
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { AccessibleIcon } from '@fictjs/ui-primitives'
|
|
14
|
+
|
|
15
|
+
<AccessibleIcon label="Close dialog">
|
|
16
|
+
<svg width="16" height="16" aria-hidden="true"><path d="M2 2 L14 14 M14 2 L2 14" /></svg>
|
|
17
|
+
</AccessibleIcon>
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Accessibility Notes
|
|
21
|
+
|
|
22
|
+
- `label` is required and should describe the icon action, not visual shape.
|
|
23
|
+
- Keep the nested icon decorative (`aria-hidden="true"`) so only one accessible name is announced.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Id Utilities
|
|
2
|
+
|
|
3
|
+
`useId` and `IdProvider` provide deterministic id generation for aria wiring and SSR-safe composition.
|
|
4
|
+
|
|
5
|
+
## API
|
|
6
|
+
|
|
7
|
+
- `useId(id?: string, prefix?: string): string`
|
|
8
|
+
- `IdProvider` with `prefix?: string`
|
|
9
|
+
|
|
10
|
+
## Minimal Example
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { IdProvider, useId } from '@fictjs/ui-primitives'
|
|
14
|
+
|
|
15
|
+
function Field() {
|
|
16
|
+
const id = useId(undefined, 'profile-field')
|
|
17
|
+
return <input id={id} aria-describedby={`${id}-hint`} />
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
<IdProvider prefix="settings">{<Field />}</IdProvider>
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Notes
|
|
24
|
+
|
|
25
|
+
- Prefer explicit `id` on root primitives when you need hard guarantees for SSR/hydration parity.
|
|
26
|
+
- `IdProvider` helps keep generated ids deterministic within a subtree.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Portal / PortalHost
|
|
2
|
+
|
|
3
|
+
- `Portal` renders subtree into a target container (default `document.body`)
|
|
4
|
+
- `PortalHost` sets a container context for nested portals
|
|
5
|
+
|
|
6
|
+
## Props
|
|
7
|
+
|
|
8
|
+
### Portal
|
|
9
|
+
|
|
10
|
+
- `container`: HTMLElement or accessor
|
|
11
|
+
- `disabled`: render inline when true
|
|
12
|
+
|
|
13
|
+
### PortalHost
|
|
14
|
+
|
|
15
|
+
- `container`: HTMLElement or accessor
|
|
16
|
+
|
|
17
|
+
## Minimal Example
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import { Portal } from '@fictjs/ui-primitives'
|
|
21
|
+
|
|
22
|
+
<Portal>
|
|
23
|
+
<div role="dialog" aria-modal="true">Portaled content</div>
|
|
24
|
+
</Portal>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Accessibility Notes
|
|
28
|
+
|
|
29
|
+
- Portaling changes DOM position, not semantics; always provide explicit role/aria on the content.
|
|
30
|
+
- Ensure focus entry/restore and dismiss behavior are handled by surrounding primitives (e.g. `Dialog`).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Presence
|
|
2
|
+
|
|
3
|
+
`Presence` conditionally mounts children based on `present`, while supporting force mount.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
- `present`: boolean/accessor, default `true`
|
|
8
|
+
- `forceMount`: boolean/accessor, default `false`
|
|
9
|
+
- `children`: node or render function receiving `{ present }`
|
|
10
|
+
|
|
11
|
+
## Minimal Example
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { createSignal } from '@fictjs/runtime/advanced'
|
|
15
|
+
import { Presence } from '@fictjs/ui-primitives'
|
|
16
|
+
|
|
17
|
+
const open = createSignal(false)
|
|
18
|
+
|
|
19
|
+
<Presence present={() => open()}>
|
|
20
|
+
<div data-state={() => (open() ? 'open' : 'closed')}>Conditional panel</div>
|
|
21
|
+
</Presence>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Accessibility Notes
|
|
25
|
+
|
|
26
|
+
- When `present` is false, content is unmounted and removed from the accessibility tree.
|
|
27
|
+
- If using `forceMount`, also expose hidden/closed state (`data-state`, `aria-hidden`, etc.) intentionally.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Primitive
|
|
2
|
+
|
|
3
|
+
`Primitive` is the polymorphic base element wrapper for all headless components.
|
|
4
|
+
|
|
5
|
+
## API
|
|
6
|
+
|
|
7
|
+
- `as`: intrinsic element tag, default `div`
|
|
8
|
+
- `asChild`: when `true`, merges props into the child vnode via `Slot`
|
|
9
|
+
|
|
10
|
+
## Minimal Example
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
<Primitive as="button" type="button">Open</Primitive>
|
|
14
|
+
<Primitive asChild>
|
|
15
|
+
<a href="/docs">Docs</a>
|
|
16
|
+
</Primitive>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Accessibility Notes
|
|
20
|
+
|
|
21
|
+
- `as` and `asChild` can change semantics; choose an element with correct native role/keyboard behavior.
|
|
22
|
+
- Avoid replacing native buttons/links with generic tags unless you fully re-implement accessibility semantics.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Separator
|
|
2
|
+
|
|
3
|
+
Semantic separator primitive.
|
|
4
|
+
|
|
5
|
+
## Props
|
|
6
|
+
|
|
7
|
+
- `orientation`: `horizontal | vertical`
|
|
8
|
+
- `decorative`: when true, uses presentation semantics
|
|
9
|
+
|
|
10
|
+
## Minimal Example
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Separator } from '@fictjs/ui-primitives'
|
|
14
|
+
|
|
15
|
+
<div>
|
|
16
|
+
<span>Section A</span>
|
|
17
|
+
<Separator orientation="horizontal" />
|
|
18
|
+
<span>Section B</span>
|
|
19
|
+
</div>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Accessibility Notes
|
|
23
|
+
|
|
24
|
+
- Use non-decorative separators for meaningful structure so assistive tech can announce boundaries.
|
|
25
|
+
- Set `decorative` for purely visual dividers to avoid extra noise for screen readers.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Slot
|
|
2
|
+
|
|
3
|
+
`Slot` provides `asChild` behavior by cloning a single child vnode and merging props.
|
|
4
|
+
|
|
5
|
+
Merged behavior:
|
|
6
|
+
|
|
7
|
+
- event handlers are composed (`child` then `slot`)
|
|
8
|
+
- `class` / `className` values are concatenated
|
|
9
|
+
- `style` objects are shallow-merged
|
|
10
|
+
- refs are composed
|
|
11
|
+
|
|
12
|
+
## Minimal Example
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { Slot } from '@fictjs/ui-primitives'
|
|
16
|
+
|
|
17
|
+
<Slot class="button-like" onClick={() => {}}>
|
|
18
|
+
<a href="/docs">Open docs</a>
|
|
19
|
+
</Slot>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Accessibility Notes
|
|
23
|
+
|
|
24
|
+
- The child keeps its native semantics; verify merged props do not conflict with existing keyboard behavior.
|
|
25
|
+
- Keep a single interactive target inside `Slot` to avoid nested-focusable anti-patterns.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# VisuallyHidden
|
|
2
|
+
|
|
3
|
+
Renders accessible text for assistive tech while keeping it visually hidden.
|
|
4
|
+
|
|
5
|
+
Supports `as` to change element tag.
|
|
6
|
+
|
|
7
|
+
## Minimal Example
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { VisuallyHidden } from '@fictjs/ui-primitives'
|
|
11
|
+
|
|
12
|
+
<button type="button">
|
|
13
|
+
<svg aria-hidden="true" />
|
|
14
|
+
<VisuallyHidden>Search</VisuallyHidden>
|
|
15
|
+
</button>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Accessibility Notes
|
|
19
|
+
|
|
20
|
+
- Use for supplemental text, not large hidden content blocks.
|
|
21
|
+
- Pair with visible affordances so sighted keyboard users still understand the control.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Accordion
|
|
2
|
+
|
|
3
|
+
Accordion primitives built on collapsible behavior.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `AccordionRoot`
|
|
8
|
+
- `AccordionItem`
|
|
9
|
+
- `AccordionTrigger`
|
|
10
|
+
- `AccordionContent`
|
|
11
|
+
|
|
12
|
+
## Minimal Example
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import {
|
|
16
|
+
AccordionRoot,
|
|
17
|
+
AccordionItem,
|
|
18
|
+
AccordionTrigger,
|
|
19
|
+
AccordionContent,
|
|
20
|
+
} from '@fictjs/ui-primitives'
|
|
21
|
+
|
|
22
|
+
<AccordionRoot type="single" defaultValue="account" collapsible>
|
|
23
|
+
<AccordionItem value="account">
|
|
24
|
+
<AccordionTrigger>Account</AccordionTrigger>
|
|
25
|
+
<AccordionContent>Account details</AccordionContent>
|
|
26
|
+
</AccordionItem>
|
|
27
|
+
</AccordionRoot>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Accessibility Notes
|
|
31
|
+
|
|
32
|
+
- `AccordionTrigger` should remain a button-like control so `aria-expanded` semantics stay valid.
|
|
33
|
+
- Preserve heading hierarchy around triggers/content when embedding in structured pages.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Collapsible
|
|
2
|
+
|
|
3
|
+
Expandable/collapsible section primitives.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `CollapsibleRoot`
|
|
8
|
+
- `CollapsibleTrigger`
|
|
9
|
+
- `CollapsibleContent`
|
|
10
|
+
|
|
11
|
+
## Minimal Example
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import {
|
|
15
|
+
CollapsibleRoot,
|
|
16
|
+
CollapsibleTrigger,
|
|
17
|
+
CollapsibleContent,
|
|
18
|
+
} from '@fictjs/ui-primitives'
|
|
19
|
+
|
|
20
|
+
<CollapsibleRoot defaultOpen>
|
|
21
|
+
<CollapsibleTrigger>Toggle section</CollapsibleTrigger>
|
|
22
|
+
<CollapsibleContent>Collapsible body</CollapsibleContent>
|
|
23
|
+
</CollapsibleRoot>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Accessibility Notes
|
|
27
|
+
|
|
28
|
+
- Trigger state should be exposed via `aria-expanded` (provided by primitive).
|
|
29
|
+
- When using `forceMount`, keep hidden-state signaling consistent (`data-state`, optional `aria-hidden`).
|