@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,62 @@
|
|
|
1
|
+
# DropdownMenu
|
|
2
|
+
|
|
3
|
+
Compound dropdown menu built on Popover + roving focus.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `DropdownMenuRoot`
|
|
8
|
+
- `DropdownMenuTrigger`
|
|
9
|
+
- `DropdownMenuContent`
|
|
10
|
+
- `DropdownMenuItem`
|
|
11
|
+
- `DropdownMenuCheckboxItem`
|
|
12
|
+
- `DropdownMenuRadioGroup`
|
|
13
|
+
- `DropdownMenuRadioItem`
|
|
14
|
+
- `DropdownMenuSub`
|
|
15
|
+
- `DropdownMenuSubTrigger`
|
|
16
|
+
- `DropdownMenuSubContent`
|
|
17
|
+
- `DropdownMenuLabel`
|
|
18
|
+
- `DropdownMenuSeparator`
|
|
19
|
+
|
|
20
|
+
## Root API
|
|
21
|
+
|
|
22
|
+
- `open?: boolean | () => boolean`
|
|
23
|
+
- `defaultOpen?: boolean`
|
|
24
|
+
- `onOpenChange?: (open: boolean) => void`
|
|
25
|
+
|
|
26
|
+
## Item Semantics
|
|
27
|
+
|
|
28
|
+
- `DropdownMenuItem` defaults to `role="menuitem"` and closes menu unless `keepOpen`
|
|
29
|
+
- `DropdownMenuCheckboxItem` defaults `keepOpen=true` and exposes `data-checked`
|
|
30
|
+
- `DropdownMenuRadioGroup` + `DropdownMenuRadioItem` provide single-select semantics (`role="menuitemradio"`)
|
|
31
|
+
|
|
32
|
+
## Content Behavior
|
|
33
|
+
|
|
34
|
+
- `DropdownMenuContent` renders as `role="menu"` with vertical roving focus
|
|
35
|
+
- Supports `portal?: boolean` through inherited popover content props
|
|
36
|
+
- `DropdownMenuSub*` composes nested menus with side positioning (`right/start` by default)
|
|
37
|
+
|
|
38
|
+
## Minimal Example
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
import {
|
|
42
|
+
DropdownMenuRoot,
|
|
43
|
+
DropdownMenuTrigger,
|
|
44
|
+
DropdownMenuContent,
|
|
45
|
+
DropdownMenuItem,
|
|
46
|
+
DropdownMenuSeparator,
|
|
47
|
+
} from '@fictjs/ui-primitives'
|
|
48
|
+
|
|
49
|
+
<DropdownMenuRoot>
|
|
50
|
+
<DropdownMenuTrigger>Actions</DropdownMenuTrigger>
|
|
51
|
+
<DropdownMenuContent side="bottom" align="end">
|
|
52
|
+
<DropdownMenuItem>Edit</DropdownMenuItem>
|
|
53
|
+
<DropdownMenuSeparator />
|
|
54
|
+
<DropdownMenuItem>Archive</DropdownMenuItem>
|
|
55
|
+
</DropdownMenuContent>
|
|
56
|
+
</DropdownMenuRoot>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Accessibility Notes
|
|
60
|
+
|
|
61
|
+
- Keep menu item roles aligned with behavior (`menuitem`, `menuitemcheckbox`, `menuitemradio`).
|
|
62
|
+
- Use roving focus with visible focus styles so keyboard users can track current item.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Menubar
|
|
2
|
+
|
|
3
|
+
Desktop-style menubar primitives with top-level menu activation.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `MenubarRoot`
|
|
8
|
+
- `MenubarMenu`
|
|
9
|
+
- `MenubarTrigger`
|
|
10
|
+
- `MenubarContent`
|
|
11
|
+
- `MenubarItem`
|
|
12
|
+
|
|
13
|
+
## Minimal Example
|
|
14
|
+
|
|
15
|
+
```tsx
|
|
16
|
+
import {
|
|
17
|
+
MenubarRoot,
|
|
18
|
+
MenubarMenu,
|
|
19
|
+
MenubarTrigger,
|
|
20
|
+
MenubarContent,
|
|
21
|
+
MenubarItem,
|
|
22
|
+
} from '@fictjs/ui-primitives'
|
|
23
|
+
|
|
24
|
+
<MenubarRoot>
|
|
25
|
+
<MenubarMenu value="file">
|
|
26
|
+
<MenubarTrigger>File</MenubarTrigger>
|
|
27
|
+
<MenubarContent>
|
|
28
|
+
<MenubarItem>New</MenubarItem>
|
|
29
|
+
<MenubarItem>Open</MenubarItem>
|
|
30
|
+
</MenubarContent>
|
|
31
|
+
</MenubarMenu>
|
|
32
|
+
</MenubarRoot>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Accessibility Notes
|
|
36
|
+
|
|
37
|
+
- Top-level triggers should remain in a single horizontal tab stop group (`menubar` semantics).
|
|
38
|
+
- Opening one menu should not trap focus permanently; users must escape or choose an item.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# AlertDialog
|
|
2
|
+
|
|
3
|
+
Alert dialog wrappers built on top of `Dialog` with modal semantics and `role="alertdialog"` defaults.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `AlertDialogRoot`
|
|
8
|
+
- `AlertDialogTrigger`
|
|
9
|
+
- `AlertDialogPortal`
|
|
10
|
+
- `AlertDialogOverlay`
|
|
11
|
+
- `AlertDialogContent`
|
|
12
|
+
- `AlertDialogTitle`
|
|
13
|
+
- `AlertDialogDescription`
|
|
14
|
+
- `AlertDialogAction`
|
|
15
|
+
- `AlertDialogCancel`
|
|
16
|
+
|
|
17
|
+
## Minimal Example
|
|
18
|
+
|
|
19
|
+
```tsx
|
|
20
|
+
import {
|
|
21
|
+
AlertDialogRoot,
|
|
22
|
+
AlertDialogTrigger,
|
|
23
|
+
AlertDialogOverlay,
|
|
24
|
+
AlertDialogContent,
|
|
25
|
+
AlertDialogTitle,
|
|
26
|
+
AlertDialogDescription,
|
|
27
|
+
AlertDialogCancel,
|
|
28
|
+
AlertDialogAction,
|
|
29
|
+
} from '@fictjs/ui-primitives'
|
|
30
|
+
|
|
31
|
+
<AlertDialogRoot>
|
|
32
|
+
<AlertDialogTrigger>Delete</AlertDialogTrigger>
|
|
33
|
+
<AlertDialogOverlay />
|
|
34
|
+
<AlertDialogContent>
|
|
35
|
+
<AlertDialogTitle>Delete item?</AlertDialogTitle>
|
|
36
|
+
<AlertDialogDescription>This action cannot be undone.</AlertDialogDescription>
|
|
37
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
38
|
+
<AlertDialogAction>Confirm</AlertDialogAction>
|
|
39
|
+
</AlertDialogContent>
|
|
40
|
+
</AlertDialogRoot>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Accessibility Notes
|
|
44
|
+
|
|
45
|
+
- Use concise, high-signal titles/descriptions because `alertdialog` is announced with higher urgency.
|
|
46
|
+
- Keep both cancel and confirm actions keyboard accessible in logical focus order.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# CommandPalette
|
|
2
|
+
|
|
3
|
+
Dialog-based command palette for searchable action lists.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `CommandPaletteRoot`
|
|
8
|
+
- `CommandPaletteTrigger`
|
|
9
|
+
- `CommandPaletteContent`
|
|
10
|
+
- `CommandPaletteInput`
|
|
11
|
+
- `CommandPaletteList`
|
|
12
|
+
- `CommandPaletteItem`
|
|
13
|
+
- `CommandPaletteEmpty`
|
|
14
|
+
- `CommandPaletteGroup`
|
|
15
|
+
- `CommandPaletteSeparator`
|
|
16
|
+
- `CommandPaletteClose`
|
|
17
|
+
|
|
18
|
+
## Key APIs
|
|
19
|
+
|
|
20
|
+
- `CommandPaletteRoot`: controlled/uncontrolled `open`, `value`, and `query` state
|
|
21
|
+
- `CommandPaletteInput`: drives filtering query and supports keyboard entry into list
|
|
22
|
+
- `CommandPaletteItem`: supports `as/asChild`, `keywords`, `keepOpen`, and selection callbacks
|
|
23
|
+
- `CommandPaletteEmpty`: renders only when no items match current query
|
|
24
|
+
|
|
25
|
+
## Minimal Example
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import {
|
|
29
|
+
CommandPaletteRoot,
|
|
30
|
+
CommandPaletteTrigger,
|
|
31
|
+
CommandPaletteContent,
|
|
32
|
+
CommandPaletteInput,
|
|
33
|
+
CommandPaletteList,
|
|
34
|
+
CommandPaletteItem,
|
|
35
|
+
CommandPaletteEmpty,
|
|
36
|
+
} from '@fictjs/ui-primitives'
|
|
37
|
+
|
|
38
|
+
<CommandPaletteRoot>
|
|
39
|
+
<CommandPaletteTrigger>Open Command Menu</CommandPaletteTrigger>
|
|
40
|
+
<CommandPaletteContent>
|
|
41
|
+
<CommandPaletteInput placeholder="Type a command" />
|
|
42
|
+
<CommandPaletteList>
|
|
43
|
+
<CommandPaletteItem value="profile">Profile</CommandPaletteItem>
|
|
44
|
+
<CommandPaletteItem value="settings">Settings</CommandPaletteItem>
|
|
45
|
+
<CommandPaletteEmpty>No results</CommandPaletteEmpty>
|
|
46
|
+
</CommandPaletteList>
|
|
47
|
+
</CommandPaletteContent>
|
|
48
|
+
</CommandPaletteRoot>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Accessibility Notes
|
|
52
|
+
|
|
53
|
+
- Built on dialog semantics, so focus and dismissal behavior follows overlay contracts.
|
|
54
|
+
- Keep command labels concise and unique for predictable keyboard filtering.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Dialog
|
|
2
|
+
|
|
3
|
+
Compound modal/non-modal dialog primitives.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `DialogRoot`
|
|
8
|
+
- `DialogTrigger`
|
|
9
|
+
- `DialogPortal`
|
|
10
|
+
- `DialogOverlay`
|
|
11
|
+
- `DialogContent`
|
|
12
|
+
- `DialogTitle`
|
|
13
|
+
- `DialogDescription`
|
|
14
|
+
- `DialogClose`
|
|
15
|
+
|
|
16
|
+
Supports controlled/uncontrolled state via `open/defaultOpen/onOpenChange`.
|
|
17
|
+
|
|
18
|
+
## Root API
|
|
19
|
+
|
|
20
|
+
- `DialogRoot`
|
|
21
|
+
- `id?: string` optional deterministic base id for content/title/description wiring
|
|
22
|
+
- `open?: boolean | () => boolean` controlled state
|
|
23
|
+
- `defaultOpen?: boolean` uncontrolled initial state
|
|
24
|
+
- `onOpenChange?: (open: boolean) => void`
|
|
25
|
+
- `modal?: boolean` default `true`
|
|
26
|
+
|
|
27
|
+
## Composition API
|
|
28
|
+
|
|
29
|
+
- `DialogTrigger` / `DialogClose` support `asChild`
|
|
30
|
+
- `DialogContent` supports `onEscapeKeyDown`, `onPointerDownOutside`, `onFocusOutside`, `onInteractOutside`
|
|
31
|
+
- Outside handlers are interceptable: calling `event.preventDefault()` blocks dismissal
|
|
32
|
+
|
|
33
|
+
## Content Behavior
|
|
34
|
+
|
|
35
|
+
- `DialogContent` defaults to `role="dialog"`
|
|
36
|
+
- `forceMount` keeps content mounted while closed (state reflects via `data-state`)
|
|
37
|
+
- `portal?: boolean` defaults to `true`; set `false` for inline rendering/testing
|
|
38
|
+
- Escape and outside interactions are handled via `DismissableLayer`
|
|
39
|
+
- Modal mode enables `ScrollLock` + focus trap semantics
|
|
40
|
+
|
|
41
|
+
## Accessibility Contract
|
|
42
|
+
|
|
43
|
+
- Trigger exposes `aria-haspopup="dialog"` and `aria-controls`
|
|
44
|
+
- Content wires `aria-labelledby` and `aria-describedby` to `DialogTitle` / `DialogDescription`
|
|
45
|
+
- `DialogClose` emits close semantics through `onOpenChange`
|
|
46
|
+
|
|
47
|
+
## Minimal Example
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import {
|
|
51
|
+
DialogRoot,
|
|
52
|
+
DialogTrigger,
|
|
53
|
+
DialogOverlay,
|
|
54
|
+
DialogContent,
|
|
55
|
+
DialogTitle,
|
|
56
|
+
DialogDescription,
|
|
57
|
+
DialogClose,
|
|
58
|
+
} from '@fictjs/ui-primitives'
|
|
59
|
+
|
|
60
|
+
<DialogRoot>
|
|
61
|
+
<DialogTrigger>Open settings</DialogTrigger>
|
|
62
|
+
<DialogOverlay />
|
|
63
|
+
<DialogContent>
|
|
64
|
+
<DialogTitle>Settings</DialogTitle>
|
|
65
|
+
<DialogDescription>Update your preferences.</DialogDescription>
|
|
66
|
+
<DialogClose>Done</DialogClose>
|
|
67
|
+
</DialogContent>
|
|
68
|
+
</DialogRoot>
|
|
69
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# HoverCard
|
|
2
|
+
|
|
3
|
+
Interactive hover content with separate open/close delays.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `HoverCardRoot`
|
|
8
|
+
- `HoverCardTrigger`
|
|
9
|
+
- `HoverCardContent`
|
|
10
|
+
|
|
11
|
+
## Minimal Example
|
|
12
|
+
|
|
13
|
+
```tsx
|
|
14
|
+
import { HoverCardRoot, HoverCardTrigger, HoverCardContent } from '@fictjs/ui-primitives'
|
|
15
|
+
|
|
16
|
+
<HoverCardRoot openDelay={100} closeDelay={150}>
|
|
17
|
+
<HoverCardTrigger>@fictjs</HoverCardTrigger>
|
|
18
|
+
<HoverCardContent side="bottom">Project profile preview</HoverCardContent>
|
|
19
|
+
</HoverCardRoot>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Accessibility Notes
|
|
23
|
+
|
|
24
|
+
- Hover-only disclosure is insufficient; this primitive also opens on focus for keyboard users.
|
|
25
|
+
- Keep hover card content supplemental and non-blocking for primary task flow.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Popover
|
|
2
|
+
|
|
3
|
+
Floating, dismissable content anchored to a trigger.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `PopoverRoot`
|
|
8
|
+
- `PopoverTrigger`
|
|
9
|
+
- `PopoverContent`
|
|
10
|
+
- `PopoverClose`
|
|
11
|
+
|
|
12
|
+
## Key APIs
|
|
13
|
+
|
|
14
|
+
- `PopoverRoot` accepts `id?: string` for deterministic trigger/content aria wiring
|
|
15
|
+
- `PopoverTrigger` and `PopoverClose` support `asChild`
|
|
16
|
+
- `PopoverContent` supports `onEscapeKeyDown`, `onPointerDownOutside`, `onFocusOutside`, `onInteractOutside`
|
|
17
|
+
- Outside handlers are interceptable: calling `event.preventDefault()` blocks dismissal
|
|
18
|
+
|
|
19
|
+
## Minimal Example
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { PopoverRoot, PopoverTrigger, PopoverContent, PopoverClose } from '@fictjs/ui-primitives'
|
|
23
|
+
|
|
24
|
+
<PopoverRoot>
|
|
25
|
+
<PopoverTrigger>Open filters</PopoverTrigger>
|
|
26
|
+
<PopoverContent side="bottom" align="start">
|
|
27
|
+
Filter options
|
|
28
|
+
<PopoverClose>Close</PopoverClose>
|
|
29
|
+
</PopoverContent>
|
|
30
|
+
</PopoverRoot>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Accessibility Notes
|
|
34
|
+
|
|
35
|
+
- Popover content should expose an appropriate role (`dialog` by default in this implementation).
|
|
36
|
+
- Provide a deterministic close action in addition to outside dismissal.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Tooltip
|
|
2
|
+
|
|
3
|
+
Hover/focus tooltip primitives with open delay.
|
|
4
|
+
|
|
5
|
+
## Components
|
|
6
|
+
|
|
7
|
+
- `TooltipProvider`
|
|
8
|
+
- `TooltipRoot`
|
|
9
|
+
- `TooltipTrigger`
|
|
10
|
+
- `TooltipContent`
|
|
11
|
+
|
|
12
|
+
## Minimal Example
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
import { TooltipProvider, TooltipRoot, TooltipTrigger, TooltipContent } from '@fictjs/ui-primitives'
|
|
16
|
+
|
|
17
|
+
<TooltipProvider delayDuration={200}>
|
|
18
|
+
<TooltipRoot>
|
|
19
|
+
<TooltipTrigger>Hover me</TooltipTrigger>
|
|
20
|
+
<TooltipContent side="top">Helpful hint</TooltipContent>
|
|
21
|
+
</TooltipRoot>
|
|
22
|
+
</TooltipProvider>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Accessibility Notes
|
|
26
|
+
|
|
27
|
+
- Tooltip text should supplement, not replace, the control's accessible name.
|
|
28
|
+
- Keep tooltip content short and avoid interactive controls inside tooltip surfaces.
|
package/docs/examples.md
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# Usage Examples
|
|
2
|
+
|
|
3
|
+
Examples below use TSX syntax with `@fictjs/ui-primitives`.
|
|
4
|
+
|
|
5
|
+
For a runnable end-to-end demo, see `examples/README.md`.
|
|
6
|
+
|
|
7
|
+
## Dialog (controlled)
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { createSignal } from '@fictjs/runtime/advanced'
|
|
11
|
+
import {
|
|
12
|
+
DialogRoot,
|
|
13
|
+
DialogTrigger,
|
|
14
|
+
DialogOverlay,
|
|
15
|
+
DialogContent,
|
|
16
|
+
DialogTitle,
|
|
17
|
+
DialogDescription,
|
|
18
|
+
DialogClose,
|
|
19
|
+
} from '@fictjs/ui-primitives'
|
|
20
|
+
|
|
21
|
+
const open = createSignal(false)
|
|
22
|
+
|
|
23
|
+
<DialogRoot open={() => open()} onOpenChange={next => open(next)}>
|
|
24
|
+
<DialogTrigger>Edit profile</DialogTrigger>
|
|
25
|
+
<DialogOverlay class="overlay" />
|
|
26
|
+
<DialogContent>
|
|
27
|
+
<DialogTitle>Profile</DialogTitle>
|
|
28
|
+
<DialogDescription>Update your display settings.</DialogDescription>
|
|
29
|
+
<DialogClose>Done</DialogClose>
|
|
30
|
+
</DialogContent>
|
|
31
|
+
</DialogRoot>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Dropdown Menu (checkbox + radio)
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import {
|
|
38
|
+
DropdownMenuRoot,
|
|
39
|
+
DropdownMenuTrigger,
|
|
40
|
+
DropdownMenuContent,
|
|
41
|
+
DropdownMenuCheckboxItem,
|
|
42
|
+
DropdownMenuRadioGroup,
|
|
43
|
+
DropdownMenuRadioItem,
|
|
44
|
+
} from '@fictjs/ui-primitives'
|
|
45
|
+
|
|
46
|
+
<DropdownMenuRoot>
|
|
47
|
+
<DropdownMenuTrigger>Preferences</DropdownMenuTrigger>
|
|
48
|
+
<DropdownMenuContent side="bottom" align="start">
|
|
49
|
+
<DropdownMenuCheckboxItem defaultChecked>Show line numbers</DropdownMenuCheckboxItem>
|
|
50
|
+
<DropdownMenuRadioGroup defaultValue="compact">
|
|
51
|
+
<DropdownMenuRadioItem value="compact">Compact</DropdownMenuRadioItem>
|
|
52
|
+
<DropdownMenuRadioItem value="comfortable">Comfortable</DropdownMenuRadioItem>
|
|
53
|
+
</DropdownMenuRadioGroup>
|
|
54
|
+
</DropdownMenuContent>
|
|
55
|
+
</DropdownMenuRoot>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Toast (hook + viewport)
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { ToastProvider, ToastViewport, useToast } from '@fictjs/ui-primitives'
|
|
62
|
+
|
|
63
|
+
function SaveButton() {
|
|
64
|
+
const toast = useToast()
|
|
65
|
+
return (
|
|
66
|
+
<button
|
|
67
|
+
type="button"
|
|
68
|
+
onClick={() => toast.show({ title: 'Saved', description: 'Changes were persisted.' })}
|
|
69
|
+
>
|
|
70
|
+
Save
|
|
71
|
+
</button>
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
<ToastProvider duration={4000}>
|
|
76
|
+
<SaveButton />
|
|
77
|
+
<ToastViewport />
|
|
78
|
+
</ToastProvider>
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Tabs (controlled)
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { createSignal } from '@fictjs/runtime/advanced'
|
|
85
|
+
import { TabsRoot, TabsList, TabsTrigger, TabsContent } from '@fictjs/ui-primitives'
|
|
86
|
+
|
|
87
|
+
const tab = createSignal('account')
|
|
88
|
+
|
|
89
|
+
<TabsRoot value={() => tab()} onValueChange={next => tab(next)}>
|
|
90
|
+
<TabsList>
|
|
91
|
+
<TabsTrigger value="account">Account</TabsTrigger>
|
|
92
|
+
<TabsTrigger value="security">Security</TabsTrigger>
|
|
93
|
+
</TabsList>
|
|
94
|
+
<TabsContent value="account">Account settings</TabsContent>
|
|
95
|
+
<TabsContent value="security" forceMount>
|
|
96
|
+
Security settings
|
|
97
|
+
</TabsContent>
|
|
98
|
+
</TabsRoot>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Form Field (label + message wiring)
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
import {
|
|
105
|
+
Form,
|
|
106
|
+
FormField,
|
|
107
|
+
FormLabel,
|
|
108
|
+
FormControl,
|
|
109
|
+
FormDescription,
|
|
110
|
+
FormMessage,
|
|
111
|
+
} from '@fictjs/ui-primitives'
|
|
112
|
+
|
|
113
|
+
<Form>
|
|
114
|
+
<FormField name="email">
|
|
115
|
+
<FormLabel>Email</FormLabel>
|
|
116
|
+
<FormControl as="input" type="email" required />
|
|
117
|
+
<FormDescription>Use your work email.</FormDescription>
|
|
118
|
+
<FormMessage>Invalid email format.</FormMessage>
|
|
119
|
+
</FormField>
|
|
120
|
+
</Form>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Select + Combobox
|
|
124
|
+
|
|
125
|
+
```tsx
|
|
126
|
+
import {
|
|
127
|
+
SelectRoot,
|
|
128
|
+
SelectTrigger,
|
|
129
|
+
SelectValue,
|
|
130
|
+
SelectContent,
|
|
131
|
+
SelectItem,
|
|
132
|
+
ComboboxRoot,
|
|
133
|
+
ComboboxInput,
|
|
134
|
+
ComboboxList,
|
|
135
|
+
ComboboxItem,
|
|
136
|
+
} from '@fictjs/ui-primitives'
|
|
137
|
+
|
|
138
|
+
<SelectRoot defaultValue="apple">
|
|
139
|
+
<SelectTrigger>
|
|
140
|
+
<SelectValue placeholder="Choose fruit" />
|
|
141
|
+
</SelectTrigger>
|
|
142
|
+
<SelectContent>
|
|
143
|
+
<SelectItem value="apple">Apple</SelectItem>
|
|
144
|
+
<SelectItem value="orange">Orange</SelectItem>
|
|
145
|
+
</SelectContent>
|
|
146
|
+
</SelectRoot>
|
|
147
|
+
|
|
148
|
+
<ComboboxRoot>
|
|
149
|
+
<ComboboxInput placeholder="Search assignee" />
|
|
150
|
+
<ComboboxList>
|
|
151
|
+
<ComboboxItem value="alice">Alice</ComboboxItem>
|
|
152
|
+
<ComboboxItem value="bob">Bob</ComboboxItem>
|
|
153
|
+
</ComboboxList>
|
|
154
|
+
</ComboboxRoot>
|
|
155
|
+
```
|
package/docs/release.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Release Checklist
|
|
2
|
+
|
|
3
|
+
Use this checklist for publishing `@fictjs/ui-primitives`.
|
|
4
|
+
|
|
5
|
+
## 1. Pre-release validation
|
|
6
|
+
|
|
7
|
+
Run all quality gates:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm lint
|
|
11
|
+
pnpm typecheck
|
|
12
|
+
pnpm test
|
|
13
|
+
pnpm build
|
|
14
|
+
pnpm examples:build
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If visual or interaction behavior changed:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm examples:screenshots
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 2. Documentation validation
|
|
24
|
+
|
|
25
|
+
Confirm docs are updated for behavior/API changes:
|
|
26
|
+
|
|
27
|
+
- `README.md`
|
|
28
|
+
- `docs/components/*`
|
|
29
|
+
- `docs/api-reference.md` (if exports changed)
|
|
30
|
+
- `docs/testing.md` / `docs/accessibility.md` where relevant
|
|
31
|
+
- `examples/README.md` if demo workflow changed
|
|
32
|
+
|
|
33
|
+
## 3. Versioning
|
|
34
|
+
|
|
35
|
+
Bump version in `package.json` using your preferred release process.
|
|
36
|
+
|
|
37
|
+
Example (patch):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pnpm version patch
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 4. Publish
|
|
44
|
+
|
|
45
|
+
Publish from a clean git state.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm publish --access public --provenance --no-git-checks
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Notes:
|
|
52
|
+
|
|
53
|
+
- Package already includes `publishConfig.access=public` and `provenance=true`.
|
|
54
|
+
- Remove `--no-git-checks` if your release environment enforces clean checks automatically.
|
|
55
|
+
|
|
56
|
+
## 5. Post-publish
|
|
57
|
+
|
|
58
|
+
- Verify package on npm registry
|
|
59
|
+
- Verify install in a clean consumer app
|
|
60
|
+
- Tag release in git and attach release notes
|
package/docs/testing.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Testing Notes
|
|
2
|
+
|
|
3
|
+
This package uses Vitest + JSDOM and focuses on behavior-level tests instead of snapshot-only assertions.
|
|
4
|
+
|
|
5
|
+
## Current coverage focus
|
|
6
|
+
|
|
7
|
+
- Controlled vs uncontrolled state for core compound components
|
|
8
|
+
- Open/close semantics for overlay/menu/disclosure primitives
|
|
9
|
+
- Accessibility state attributes (`aria-*`, `role`, `data-state`)
|
|
10
|
+
- Keyboard and pointer dismissal boundaries
|
|
11
|
+
- Queue and timeout lifecycle for Toast
|
|
12
|
+
|
|
13
|
+
## Run locally
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm test
|
|
17
|
+
pnpm test:coverage
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
For demo-level visual checks:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm examples:build
|
|
24
|
+
pnpm examples:screenshots
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
See also:
|
|
28
|
+
|
|
29
|
+
- `docs/accessibility.md` for manual a11y verification contracts
|
|
30
|
+
- `docs/examples.md` for copyable composition patterns
|
|
31
|
+
|
|
32
|
+
## When adding components
|
|
33
|
+
|
|
34
|
+
1. Add at least one uncontrolled test and one controlled test.
|
|
35
|
+
2. Add one accessibility-semantic assertion (`role`, `aria-*`, or keyboard behavior).
|
|
36
|
+
3. Add one cleanup/dispose or close-path assertion.
|