@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.
Files changed (50) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +181 -0
  3. package/dist/index.cjs +5091 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +1123 -0
  6. package/dist/index.d.ts +1123 -0
  7. package/dist/index.js +4907 -0
  8. package/dist/index.js.map +1 -0
  9. package/docs/README.md +39 -0
  10. package/docs/accessibility.md +50 -0
  11. package/docs/api-reference.md +200 -0
  12. package/docs/architecture.md +113 -0
  13. package/docs/components/core/accessible-icon.md +23 -0
  14. package/docs/components/core/id.md +26 -0
  15. package/docs/components/core/portal.md +30 -0
  16. package/docs/components/core/presence.md +27 -0
  17. package/docs/components/core/primitive.md +22 -0
  18. package/docs/components/core/separator.md +25 -0
  19. package/docs/components/core/slot.md +25 -0
  20. package/docs/components/core/visually-hidden.md +21 -0
  21. package/docs/components/disclosure/accordion.md +33 -0
  22. package/docs/components/disclosure/collapsible.md +29 -0
  23. package/docs/components/disclosure/navigation-menu.md +43 -0
  24. package/docs/components/disclosure/tabs.md +35 -0
  25. package/docs/components/feedback/toast.md +60 -0
  26. package/docs/components/form/calendar.md +35 -0
  27. package/docs/components/form/controls.md +52 -0
  28. package/docs/components/form/date-picker.md +44 -0
  29. package/docs/components/form/form-field.md +39 -0
  30. package/docs/components/form/inputs.md +99 -0
  31. package/docs/components/interaction/dismissable-layer.md +28 -0
  32. package/docs/components/interaction/focus-scope.md +27 -0
  33. package/docs/components/interaction/live-region.md +26 -0
  34. package/docs/components/interaction/popper.md +30 -0
  35. package/docs/components/interaction/roving-focus.md +27 -0
  36. package/docs/components/interaction/scroll-lock.md +22 -0
  37. package/docs/components/layout/layout.md +61 -0
  38. package/docs/components/menu/context-menu.md +44 -0
  39. package/docs/components/menu/dropdown-menu.md +62 -0
  40. package/docs/components/menu/menubar.md +38 -0
  41. package/docs/components/overlay/alert-dialog.md +46 -0
  42. package/docs/components/overlay/command-palette.md +54 -0
  43. package/docs/components/overlay/dialog.md +69 -0
  44. package/docs/components/overlay/hover-card.md +25 -0
  45. package/docs/components/overlay/popover.md +36 -0
  46. package/docs/components/overlay/tooltip.md +28 -0
  47. package/docs/examples.md +155 -0
  48. package/docs/release.md +60 -0
  49. package/docs/testing.md +36 -0
  50. 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.