@hypoth-ui/cli 0.0.1 → 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 +19 -115
- package/dist/{add-PDBC4JTE.js → add-V5PW73GC.js} +29 -17
- package/dist/{chunk-5LTQ2XVL.js → chunk-27CLUUVC.js} +0 -2
- package/dist/{chunk-YPKFYE45.js → chunk-NWIRSZUQ.js} +6 -13
- package/dist/{chunk-GJ6JOQ3Q.js → chunk-PBK72SJJ.js} +1 -1
- package/dist/{diff-BQEXG7HU.js → diff-776UATCA.js} +2 -2
- package/dist/index.js +5 -5
- package/dist/{init-7AZXYAPJ.js → init-GDU2PW7K.js} +10 -13
- package/dist/{list-X6ZLM2NQ.js → list-XDP5I537.js} +3 -3
- package/package.json +16 -12
- package/registry/components.json +1820 -206
- package/templates/accordion/index.tsx +266 -0
- package/templates/accordion/wc/accordion-content.ts +113 -0
- package/templates/accordion/wc/accordion-item.ts +111 -0
- package/templates/accordion/wc/accordion-trigger.ts +105 -0
- package/templates/accordion/wc/accordion.ts +213 -0
- package/templates/accordion/wc/index.ts +12 -0
- package/templates/alert/index.tsx +177 -0
- package/templates/alert/wc/alert.ts +167 -0
- package/templates/alert/wc/index.ts +1 -0
- package/templates/alert-dialog/index.tsx +360 -0
- package/templates/alert-dialog/wc/alert-dialog-action.ts +43 -0
- package/templates/alert-dialog/wc/alert-dialog-cancel.ts +43 -0
- package/templates/alert-dialog/wc/alert-dialog-content.ts +42 -0
- package/templates/alert-dialog/wc/alert-dialog-description.ts +34 -0
- package/templates/alert-dialog/wc/alert-dialog-footer.ts +25 -0
- package/templates/alert-dialog/wc/alert-dialog-header.ts +25 -0
- package/templates/alert-dialog/wc/alert-dialog-title.ts +34 -0
- package/templates/alert-dialog/wc/alert-dialog-trigger.ts +46 -0
- package/templates/alert-dialog/wc/alert-dialog.ts +302 -0
- package/templates/alert-dialog/wc/index.ts +13 -0
- package/templates/aspect-ratio/index.tsx +50 -0
- package/templates/aspect-ratio/wc/aspect-ratio.ts +78 -0
- package/templates/aspect-ratio/wc/index.ts +5 -0
- package/templates/avatar/avatar-group.tsx +88 -0
- package/templates/avatar/avatar.tsx +124 -0
- package/templates/avatar/index.tsx +33 -0
- package/templates/avatar/wc/avatar-group.ts +112 -0
- package/templates/avatar/wc/avatar.ts +184 -0
- package/templates/avatar/wc/index.ts +5 -0
- package/templates/badge/index.tsx +140 -0
- package/templates/badge/wc/badge.ts +119 -0
- package/templates/badge/wc/index.ts +9 -0
- package/templates/breadcrumb/index.tsx +157 -0
- package/templates/breadcrumb/wc/breadcrumb-item.ts +30 -0
- package/templates/breadcrumb/wc/breadcrumb-link.ts +70 -0
- package/templates/breadcrumb/wc/breadcrumb-list.ts +30 -0
- package/templates/breadcrumb/wc/breadcrumb-page.ts +32 -0
- package/templates/breadcrumb/wc/breadcrumb-separator.ts +31 -0
- package/templates/breadcrumb/wc/breadcrumb.ts +55 -0
- package/templates/breadcrumb/wc/index.ts +10 -0
- package/templates/button/button.tsx +119 -0
- package/templates/button/index.ts +1 -0
- package/templates/button/wc/button.ts +169 -0
- package/templates/calendar/index.tsx +149 -0
- package/templates/calendar/wc/calendar.ts +316 -0
- package/templates/calendar/wc/index.ts +4 -0
- package/templates/card/index.tsx +108 -0
- package/templates/card/wc/card-content.ts +25 -0
- package/templates/card/wc/card-footer.ts +25 -0
- package/templates/card/wc/card-header.ts +25 -0
- package/templates/card/wc/card.ts +43 -0
- package/templates/card/wc/index.ts +8 -0
- package/templates/checkbox/checkbox.tsx +85 -0
- package/templates/checkbox/wc/checkbox.ts +247 -0
- package/templates/collapsible/index.tsx +172 -0
- package/templates/collapsible/wc/collapsible-content.ts +97 -0
- package/templates/collapsible/wc/collapsible-trigger.ts +39 -0
- package/templates/collapsible/wc/collapsible.ts +143 -0
- package/templates/collapsible/wc/index.ts +7 -0
- package/templates/combobox/combobox-content.tsx +141 -0
- package/templates/combobox/combobox-context.ts +36 -0
- package/templates/combobox/combobox-empty.tsx +38 -0
- package/templates/combobox/combobox-input.tsx +159 -0
- package/templates/combobox/combobox-loading.tsx +38 -0
- package/templates/combobox/combobox-option.tsx +99 -0
- package/templates/combobox/combobox-root.tsx +207 -0
- package/templates/combobox/combobox-tag.tsx +62 -0
- package/templates/combobox/index.ts +62 -0
- package/templates/combobox/wc/combobox-content.ts +97 -0
- package/templates/combobox/wc/combobox-input.ts +134 -0
- package/templates/combobox/wc/combobox-option.ts +111 -0
- package/templates/combobox/wc/combobox-tag.ts +103 -0
- package/templates/combobox/wc/combobox.ts +981 -0
- package/templates/combobox/wc/index.ts +5 -0
- package/templates/command/index.tsx +279 -0
- package/templates/command/wc/command-empty.ts +24 -0
- package/templates/command/wc/command-group.ts +60 -0
- package/templates/command/wc/command-input.ts +136 -0
- package/templates/command/wc/command-item.ts +78 -0
- package/templates/command/wc/command-list.ts +103 -0
- package/templates/command/wc/command-loading.ts +24 -0
- package/templates/command/wc/command-separator.ts +23 -0
- package/templates/command/wc/command.ts +176 -0
- package/templates/context-menu/index.tsx +262 -0
- package/templates/context-menu/wc/context-menu-content.ts +41 -0
- package/templates/context-menu/wc/context-menu-item.ts +83 -0
- package/templates/context-menu/wc/context-menu-label.ts +30 -0
- package/templates/context-menu/wc/context-menu-separator.ts +28 -0
- package/templates/context-menu/wc/context-menu.ts +324 -0
- package/templates/context-menu/wc/index.ts +9 -0
- package/templates/data-table/index.tsx +263 -0
- package/templates/data-table/wc/data-table.ts +405 -0
- package/templates/data-table/wc/index.ts +10 -0
- package/templates/date-picker/date-picker-calendar.tsx +352 -0
- package/templates/date-picker/date-picker-content.tsx +121 -0
- package/templates/date-picker/date-picker-context.ts +46 -0
- package/templates/date-picker/date-picker-root.tsx +201 -0
- package/templates/date-picker/date-picker-trigger.tsx +95 -0
- package/templates/date-picker/index.ts +44 -0
- package/templates/date-picker/wc/date-picker-calendar.ts +457 -0
- package/templates/date-picker/wc/date-picker.ts +592 -0
- package/templates/date-picker/wc/date-utils.ts +467 -0
- package/templates/date-picker/wc/index.ts +3 -0
- package/templates/dialog/dialog-close.tsx +57 -0
- package/templates/dialog/dialog-content.tsx +106 -0
- package/templates/dialog/dialog-context.ts +24 -0
- package/templates/dialog/dialog-description.tsx +51 -0
- package/templates/dialog/dialog-root.tsx +104 -0
- package/templates/dialog/dialog-title.tsx +38 -0
- package/templates/dialog/dialog-trigger.tsx +94 -0
- package/templates/dialog/index.ts +52 -0
- package/templates/dialog/wc/dialog-content.ts +59 -0
- package/templates/dialog/wc/dialog-description.ts +58 -0
- package/templates/dialog/wc/dialog-title.ts +56 -0
- package/templates/dialog/wc/dialog.ts +411 -0
- package/templates/drawer/index.tsx +263 -0
- package/templates/drawer/wc/drawer-content.ts +150 -0
- package/templates/drawer/wc/drawer-description.ts +34 -0
- package/templates/drawer/wc/drawer-footer.ts +25 -0
- package/templates/drawer/wc/drawer-header.ts +25 -0
- package/templates/drawer/wc/drawer-title.ts +34 -0
- package/templates/drawer/wc/drawer.ts +348 -0
- package/templates/drawer/wc/index.ts +10 -0
- package/templates/dropdown-menu/index.tsx +454 -0
- package/templates/dropdown-menu/wc/dropdown-menu-checkbox-item.ts +93 -0
- package/templates/dropdown-menu/wc/dropdown-menu-content.ts +43 -0
- package/templates/dropdown-menu/wc/dropdown-menu-item.ts +85 -0
- package/templates/dropdown-menu/wc/dropdown-menu-label.ts +31 -0
- package/templates/dropdown-menu/wc/dropdown-menu-radio-group.ts +80 -0
- package/templates/dropdown-menu/wc/dropdown-menu-radio-item.ts +101 -0
- package/templates/dropdown-menu/wc/dropdown-menu-separator.ts +28 -0
- package/templates/dropdown-menu/wc/dropdown-menu.ts +358 -0
- package/templates/dropdown-menu/wc/index.ts +12 -0
- package/templates/field/field-description.tsx +39 -0
- package/templates/field/field-error.tsx +37 -0
- package/templates/field/field.tsx +46 -0
- package/templates/field/index.ts +4 -0
- package/templates/field/label.tsx +40 -0
- package/templates/field/wc/field-description.ts +42 -0
- package/templates/field/wc/field-error.ts +46 -0
- package/templates/field/wc/field.ts +210 -0
- package/templates/field/wc/label.ts +54 -0
- package/templates/file-upload/file-upload-context.ts +26 -0
- package/templates/file-upload/file-upload-dropzone.tsx +111 -0
- package/templates/file-upload/file-upload-input.tsx +86 -0
- package/templates/file-upload/file-upload-item.tsx +105 -0
- package/templates/file-upload/file-upload-root.tsx +115 -0
- package/templates/file-upload/index.ts +50 -0
- package/templates/file-upload/wc/file-upload.ts +380 -0
- package/templates/file-upload/wc/index.ts +1 -0
- package/templates/hover-card/index.tsx +203 -0
- package/templates/hover-card/wc/hover-card-content.ts +50 -0
- package/templates/hover-card/wc/hover-card.ts +382 -0
- package/templates/hover-card/wc/index.ts +6 -0
- package/templates/icon/icon.tsx +76 -0
- package/templates/icon/wc/icon-adapter.ts +108 -0
- package/templates/icon/wc/icon.ts +161 -0
- package/templates/input/input.tsx +130 -0
- package/templates/input/wc/input.ts +216 -0
- package/templates/layout/app-shell.tsx +177 -0
- package/templates/layout/box.tsx +53 -0
- package/templates/layout/center.tsx +42 -0
- package/templates/layout/container.tsx +43 -0
- package/templates/layout/flow.tsx +83 -0
- package/templates/layout/grid.tsx +79 -0
- package/templates/layout/index.ts +33 -0
- package/templates/layout/inline.tsx +16 -0
- package/templates/layout/page.tsx +43 -0
- package/templates/layout/section.tsx +39 -0
- package/templates/layout/spacer.tsx +30 -0
- package/templates/layout/split.tsx +47 -0
- package/templates/layout/stack.tsx +16 -0
- package/templates/layout/wc/app-shell.ts +58 -0
- package/templates/layout/wc/box.ts +117 -0
- package/templates/layout/wc/center.ts +78 -0
- package/templates/layout/wc/container.ts +77 -0
- package/templates/layout/wc/flow.ts +149 -0
- package/templates/layout/wc/footer.ts +57 -0
- package/templates/layout/wc/grid.ts +142 -0
- package/templates/layout/wc/header.ts +57 -0
- package/templates/layout/wc/index.ts +41 -0
- package/templates/layout/wc/main.ts +46 -0
- package/templates/layout/wc/page.ts +81 -0
- package/templates/layout/wc/section.ts +65 -0
- package/templates/layout/wc/spacer.ts +77 -0
- package/templates/layout/wc/split.ts +94 -0
- package/templates/layout/wc/wrap.ts +93 -0
- package/templates/layout/wrap.tsx +46 -0
- package/templates/link/link.tsx +109 -0
- package/templates/link/wc/link.ts +124 -0
- package/templates/list/index.tsx +55 -0
- package/templates/list/list-item.tsx +117 -0
- package/templates/list/list.tsx +115 -0
- package/templates/list/wc/index.ts +5 -0
- package/templates/list/wc/list-item.ts +127 -0
- package/templates/list/wc/list.ts +114 -0
- package/templates/menu/index.ts +49 -0
- package/templates/menu/menu-content.tsx +109 -0
- package/templates/menu/menu-context.ts +17 -0
- package/templates/menu/menu-item.tsx +108 -0
- package/templates/menu/menu-label.tsx +32 -0
- package/templates/menu/menu-root.tsx +108 -0
- package/templates/menu/menu-separator.tsx +24 -0
- package/templates/menu/menu-trigger.tsx +104 -0
- package/templates/menu/wc/menu-content.ts +67 -0
- package/templates/menu/wc/menu-item.ts +109 -0
- package/templates/menu/wc/menu.ts +449 -0
- package/templates/navigation-menu/index.tsx +328 -0
- package/templates/navigation-menu/wc/index.ts +12 -0
- package/templates/navigation-menu/wc/navigation-menu-content.ts +30 -0
- package/templates/navigation-menu/wc/navigation-menu-indicator.ts +30 -0
- package/templates/navigation-menu/wc/navigation-menu-item.ts +60 -0
- package/templates/navigation-menu/wc/navigation-menu-link.ts +97 -0
- package/templates/navigation-menu/wc/navigation-menu-list.ts +30 -0
- package/templates/navigation-menu/wc/navigation-menu-trigger.ts +110 -0
- package/templates/navigation-menu/wc/navigation-menu-viewport.ts +85 -0
- package/templates/navigation-menu/wc/navigation-menu.ts +272 -0
- package/templates/number-input/index.ts +46 -0
- package/templates/number-input/number-input-context.ts +38 -0
- package/templates/number-input/number-input-decrement.tsx +53 -0
- package/templates/number-input/number-input-field.tsx +93 -0
- package/templates/number-input/number-input-increment.tsx +53 -0
- package/templates/number-input/number-input-root.tsx +137 -0
- package/templates/number-input/wc/index.ts +1 -0
- package/templates/number-input/wc/number-input.ts +283 -0
- package/templates/pagination/index.tsx +198 -0
- package/templates/pagination/wc/index.ts +11 -0
- package/templates/pagination/wc/pagination-content.ts +30 -0
- package/templates/pagination/wc/pagination-ellipsis.ts +28 -0
- package/templates/pagination/wc/pagination-item.ts +30 -0
- package/templates/pagination/wc/pagination-link.ts +76 -0
- package/templates/pagination/wc/pagination-next.ts +69 -0
- package/templates/pagination/wc/pagination-previous.ts +69 -0
- package/templates/pagination/wc/pagination.ts +156 -0
- package/templates/pin-input/index.ts +39 -0
- package/templates/pin-input/pin-input-context.ts +30 -0
- package/templates/pin-input/pin-input-field.tsx +186 -0
- package/templates/pin-input/pin-input-root.tsx +120 -0
- package/templates/pin-input/wc/index.ts +1 -0
- package/templates/pin-input/wc/pin-input.ts +259 -0
- package/templates/popover/popover.tsx +121 -0
- package/templates/popover/wc/popover-content.ts +66 -0
- package/templates/popover/wc/popover.ts +343 -0
- package/templates/progress/index.tsx +117 -0
- package/templates/progress/wc/index.ts +4 -0
- package/templates/progress/wc/progress.ts +174 -0
- package/templates/radio/radio.tsx +43 -0
- package/templates/radio/wc/radio-group.ts +261 -0
- package/templates/radio/wc/radio.ts +145 -0
- package/templates/scroll-area/index.tsx +144 -0
- package/templates/scroll-area/wc/index.ts +8 -0
- package/templates/scroll-area/wc/scroll-area-scrollbar.ts +143 -0
- package/templates/scroll-area/wc/scroll-area-thumb.ts +225 -0
- package/templates/scroll-area/wc/scroll-area-viewport.ts +120 -0
- package/templates/scroll-area/wc/scroll-area.ts +63 -0
- package/templates/select/index.ts +57 -0
- package/templates/select/select-content.tsx +243 -0
- package/templates/select/select-context.ts +30 -0
- package/templates/select/select-group.tsx +53 -0
- package/templates/select/select-label.tsx +34 -0
- package/templates/select/select-option.tsx +97 -0
- package/templates/select/select-root.tsx +153 -0
- package/templates/select/select-separator.tsx +27 -0
- package/templates/select/select-trigger.tsx +112 -0
- package/templates/select/select-value.tsx +48 -0
- package/templates/select/wc/index.ts +6 -0
- package/templates/select/wc/select-content.ts +89 -0
- package/templates/select/wc/select-group.ts +82 -0
- package/templates/select/wc/select-label.ts +49 -0
- package/templates/select/wc/select-option.ts +111 -0
- package/templates/select/wc/select-trigger.ts +101 -0
- package/templates/select/wc/select.ts +840 -0
- package/templates/separator/index.tsx +49 -0
- package/templates/separator/wc/index.ts +5 -0
- package/templates/separator/wc/separator.ts +60 -0
- package/templates/sheet/index.tsx +291 -0
- package/templates/sheet/wc/index.ts +12 -0
- package/templates/sheet/wc/sheet-close.ts +43 -0
- package/templates/sheet/wc/sheet-content.ts +47 -0
- package/templates/sheet/wc/sheet-description.ts +34 -0
- package/templates/sheet/wc/sheet-footer.ts +25 -0
- package/templates/sheet/wc/sheet-header.ts +25 -0
- package/templates/sheet/wc/sheet-overlay.ts +23 -0
- package/templates/sheet/wc/sheet-title.ts +34 -0
- package/templates/sheet/wc/sheet.ts +336 -0
- package/templates/skeleton/index.tsx +131 -0
- package/templates/skeleton/wc/index.ts +10 -0
- package/templates/skeleton/wc/skeleton.ts +107 -0
- package/templates/slider/index.ts +41 -0
- package/templates/slider/slider-context.ts +36 -0
- package/templates/slider/slider-range.tsx +59 -0
- package/templates/slider/slider-root.tsx +166 -0
- package/templates/slider/slider-thumb.tsx +213 -0
- package/templates/slider/slider-track.tsx +113 -0
- package/templates/slider/wc/index.ts +1 -0
- package/templates/slider/wc/slider.ts +465 -0
- package/templates/spinner/spinner.tsx +64 -0
- package/templates/spinner/wc/spinner.ts +70 -0
- package/templates/stepper/index.tsx +230 -0
- package/templates/stepper/wc/index.ts +12 -0
- package/templates/stepper/wc/stepper-content.ts +30 -0
- package/templates/stepper/wc/stepper-description.ts +25 -0
- package/templates/stepper/wc/stepper-indicator.ts +30 -0
- package/templates/stepper/wc/stepper-item.ts +55 -0
- package/templates/stepper/wc/stepper-separator.ts +29 -0
- package/templates/stepper/wc/stepper-title.ts +25 -0
- package/templates/stepper/wc/stepper-trigger.ts +67 -0
- package/templates/stepper/wc/stepper.ts +164 -0
- package/templates/switch/switch.tsx +90 -0
- package/templates/switch/wc/switch.ts +228 -0
- package/templates/table/body.tsx +21 -0
- package/templates/table/cell.tsx +44 -0
- package/templates/table/head.tsx +112 -0
- package/templates/table/header.tsx +21 -0
- package/templates/table/index.tsx +93 -0
- package/templates/table/root.tsx +82 -0
- package/templates/table/row.tsx +36 -0
- package/templates/table/wc/index.ts +9 -0
- package/templates/table/wc/table-body.ts +32 -0
- package/templates/table/wc/table-cell.ts +58 -0
- package/templates/table/wc/table-head.ts +129 -0
- package/templates/table/wc/table-header.ts +32 -0
- package/templates/table/wc/table-row.ts +50 -0
- package/templates/table/wc/table.ts +93 -0
- package/templates/tabs/index.tsx +222 -0
- package/templates/tabs/wc/index.ts +8 -0
- package/templates/tabs/wc/tabs-content.ts +82 -0
- package/templates/tabs/wc/tabs-list.ts +56 -0
- package/templates/tabs/wc/tabs-trigger.ts +136 -0
- package/templates/tabs/wc/tabs.ts +202 -0
- package/templates/tag/index.tsx +186 -0
- package/templates/tag/wc/index.ts +4 -0
- package/templates/tag/wc/tag.ts +166 -0
- package/templates/text/text.tsx +100 -0
- package/templates/text/wc/text.ts +94 -0
- package/templates/textarea/textarea.tsx +134 -0
- package/templates/textarea/wc/textarea.ts +280 -0
- package/templates/time-picker/index.ts +42 -0
- package/templates/time-picker/time-picker-context.ts +28 -0
- package/templates/time-picker/time-picker-root.tsx +113 -0
- package/templates/time-picker/time-picker-segment.tsx +91 -0
- package/templates/time-picker/wc/index.ts +1 -0
- package/templates/time-picker/wc/time-picker.ts +221 -0
- package/templates/toast/index.tsx +71 -0
- package/templates/toast/provider.tsx +228 -0
- package/templates/toast/toast.tsx +142 -0
- package/templates/toast/use-toast.ts +89 -0
- package/templates/toast/wc/index.ts +15 -0
- package/templates/toast/wc/toast-controller.ts +282 -0
- package/templates/toast/wc/toast-provider.ts +161 -0
- package/templates/toast/wc/toast.ts +165 -0
- package/templates/tooltip/tooltip.tsx +62 -0
- package/templates/tooltip/wc/tooltip-content.ts +64 -0
- package/templates/tooltip/wc/tooltip.ts +289 -0
- package/templates/tree/index.tsx +60 -0
- package/templates/tree/tree-item.tsx +131 -0
- package/templates/tree/tree.tsx +138 -0
- package/templates/tree/wc/index.ts +11 -0
- package/templates/tree/wc/tree-item.ts +273 -0
- package/templates/tree/wc/tree-utils.ts +143 -0
- package/templates/tree/wc/tree.ts +139 -0
- package/templates/visually-hidden/visually-hidden.tsx +45 -0
- package/templates/visually-hidden/wc/visually-hidden.ts +64 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { type TemplateResult, html, nothing } from "lit";
|
|
2
|
+
import { property, state } from "lit/decorators.js";
|
|
3
|
+
import { classMap } from "lit/directives/class-map.js";
|
|
4
|
+
import { DSElement } from "../../base/ds-element.js";
|
|
5
|
+
import { emitEvent } from "../../events/emit.js";
|
|
6
|
+
import { define } from "../../registry/define.js";
|
|
7
|
+
|
|
8
|
+
export type ListSelectionMode = "single" | "multiple" | "none";
|
|
9
|
+
export type ListOrientation = "vertical" | "horizontal";
|
|
10
|
+
export type ListSize = "default" | "compact" | "spacious";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* List root component for collection display.
|
|
14
|
+
*
|
|
15
|
+
* @element ds-list
|
|
16
|
+
*
|
|
17
|
+
* @slot - ListItem elements
|
|
18
|
+
*
|
|
19
|
+
* @fires ds-selection-change - When selection changes
|
|
20
|
+
*
|
|
21
|
+
* @cssprop --ds-list-gap - Gap between items
|
|
22
|
+
*/
|
|
23
|
+
export class DsList extends DSElement {
|
|
24
|
+
static override styles = [];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Selection mode.
|
|
28
|
+
*/
|
|
29
|
+
@property({ type: String, attribute: "selection-mode", reflect: true })
|
|
30
|
+
selectionMode: ListSelectionMode = "single";
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* List orientation.
|
|
34
|
+
*/
|
|
35
|
+
@property({ type: String, reflect: true })
|
|
36
|
+
orientation: ListOrientation = "vertical";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Size variant.
|
|
40
|
+
*/
|
|
41
|
+
@property({ type: String, reflect: true })
|
|
42
|
+
size: ListSize = "default";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Show border around list.
|
|
46
|
+
*/
|
|
47
|
+
@property({ type: Boolean, reflect: true })
|
|
48
|
+
bordered = false;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Accessible label.
|
|
52
|
+
*/
|
|
53
|
+
@property({ type: String })
|
|
54
|
+
label = "List";
|
|
55
|
+
|
|
56
|
+
@state()
|
|
57
|
+
private selectedItems: Set<string> = new Set();
|
|
58
|
+
|
|
59
|
+
handleItemSelect(itemId: string): void {
|
|
60
|
+
if (this.selectionMode === "none") return;
|
|
61
|
+
|
|
62
|
+
if (this.selectionMode === "single") {
|
|
63
|
+
this.selectedItems.clear();
|
|
64
|
+
this.selectedItems.add(itemId);
|
|
65
|
+
} else {
|
|
66
|
+
if (this.selectedItems.has(itemId)) {
|
|
67
|
+
this.selectedItems.delete(itemId);
|
|
68
|
+
} else {
|
|
69
|
+
this.selectedItems.add(itemId);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
emitEvent(this, "selection-change", {
|
|
74
|
+
detail: { selectedItems: Array.from(this.selectedItems) },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
this.requestUpdate();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
isItemSelected(itemId: string): boolean {
|
|
81
|
+
return this.selectedItems.has(itemId);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
override render(): TemplateResult {
|
|
85
|
+
const classes = {
|
|
86
|
+
"ds-list": true,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
return html`
|
|
90
|
+
<ul
|
|
91
|
+
class=${classMap(classes)}
|
|
92
|
+
role="listbox"
|
|
93
|
+
aria-label=${this.label}
|
|
94
|
+
aria-multiselectable=${this.selectionMode === "multiple" ? "true" : nothing}
|
|
95
|
+
aria-orientation=${this.orientation}
|
|
96
|
+
data-size=${this.size !== "default" ? this.size : nothing}
|
|
97
|
+
data-orientation=${this.orientation !== "vertical" ? this.orientation : nothing}
|
|
98
|
+
?data-bordered=${this.bordered}
|
|
99
|
+
>
|
|
100
|
+
<slot></slot>
|
|
101
|
+
</ul>
|
|
102
|
+
`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Register the component
|
|
107
|
+
define("ds-list", DsList);
|
|
108
|
+
|
|
109
|
+
// TypeScript declaration for HTML
|
|
110
|
+
declare global {
|
|
111
|
+
interface HTMLElementTagNameMap {
|
|
112
|
+
"ds-list": DsList;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menu compound component exports.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```tsx
|
|
6
|
+
* import { Menu } from "@/components/ui";
|
|
7
|
+
*
|
|
8
|
+
* <Menu.Root onSelect={(value) => console.log(value)}>
|
|
9
|
+
* <Menu.Trigger>Actions</Menu.Trigger>
|
|
10
|
+
* <Menu.Content>
|
|
11
|
+
* <Menu.Label>Options</Menu.Label>
|
|
12
|
+
* <Menu.Item value="edit">Edit</Menu.Item>
|
|
13
|
+
* <Menu.Item value="duplicate">Duplicate</Menu.Item>
|
|
14
|
+
* <Menu.Separator />
|
|
15
|
+
* <Menu.Item value="delete">Delete</Menu.Item>
|
|
16
|
+
* </Menu.Content>
|
|
17
|
+
* </Menu.Root>
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { MenuContent, type MenuContentProps } from "./menu-content.js";
|
|
22
|
+
import { MenuItem, type MenuItemProps } from "./menu-item.js";
|
|
23
|
+
import { MenuLabel, type MenuLabelProps } from "./menu-label.js";
|
|
24
|
+
import { MenuRoot, type MenuRootProps } from "./menu-root.js";
|
|
25
|
+
import { MenuSeparator, type MenuSeparatorProps } from "./menu-separator.js";
|
|
26
|
+
import { MenuTrigger, type MenuTriggerProps } from "./menu-trigger.js";
|
|
27
|
+
|
|
28
|
+
// Compound component
|
|
29
|
+
export const Menu = {
|
|
30
|
+
Root: MenuRoot,
|
|
31
|
+
Trigger: MenuTrigger,
|
|
32
|
+
Content: MenuContent,
|
|
33
|
+
Item: MenuItem,
|
|
34
|
+
Separator: MenuSeparator,
|
|
35
|
+
Label: MenuLabel,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Type exports
|
|
39
|
+
export type {
|
|
40
|
+
MenuContentProps,
|
|
41
|
+
MenuItemProps,
|
|
42
|
+
MenuLabelProps,
|
|
43
|
+
MenuRootProps,
|
|
44
|
+
MenuSeparatorProps,
|
|
45
|
+
MenuTriggerProps,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Re-export Placement from primitives-dom
|
|
49
|
+
export type { Placement } from "@hypoth-ui/primitives-dom";
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menu Content component - the menu panel with items.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type HTMLAttributes,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
forwardRef,
|
|
9
|
+
useCallback,
|
|
10
|
+
useEffect,
|
|
11
|
+
useRef,
|
|
12
|
+
} from "react";
|
|
13
|
+
import { createPortal } from "react-dom";
|
|
14
|
+
import { useMenuContext } from "./menu-context.js";
|
|
15
|
+
|
|
16
|
+
export interface MenuContentProps extends HTMLAttributes<HTMLDivElement> {
|
|
17
|
+
/** Menu content */
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
/** Container element for portal (defaults to document.body) */
|
|
20
|
+
container?: HTMLElement | null;
|
|
21
|
+
/** Force mount even when closed (for animations) */
|
|
22
|
+
forceMount?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Menu content panel with roving focus and type-ahead.
|
|
27
|
+
* Renders in a portal by default.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <Menu.Content>
|
|
32
|
+
* <Menu.Item value="edit">Edit</Menu.Item>
|
|
33
|
+
* <Menu.Item value="delete">Delete</Menu.Item>
|
|
34
|
+
* </Menu.Content>
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export const MenuContent = forwardRef<HTMLDivElement, MenuContentProps>(
|
|
38
|
+
({ children, container, forceMount = false, onKeyDown, ...restProps }, ref) => {
|
|
39
|
+
const { behavior, open } = useMenuContext("Menu.Content");
|
|
40
|
+
const internalRef = useRef<HTMLDivElement>(null);
|
|
41
|
+
|
|
42
|
+
// Register content element with behavior
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (open) {
|
|
45
|
+
const element = internalRef.current;
|
|
46
|
+
behavior.setContentElement(element);
|
|
47
|
+
return () => {
|
|
48
|
+
behavior.setContentElement(null);
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}, [behavior, open]);
|
|
52
|
+
|
|
53
|
+
// Handle keyboard events
|
|
54
|
+
const handleKeyDown = useCallback(
|
|
55
|
+
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
56
|
+
behavior.handleContentKeyDown(event.nativeEvent);
|
|
57
|
+
onKeyDown?.(event);
|
|
58
|
+
},
|
|
59
|
+
[behavior, onKeyDown]
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
// Get content props from behavior
|
|
63
|
+
const contentProps = behavior.getContentProps();
|
|
64
|
+
|
|
65
|
+
// Merge refs
|
|
66
|
+
const mergedRef = useCallback(
|
|
67
|
+
(element: HTMLDivElement | null) => {
|
|
68
|
+
(internalRef as React.MutableRefObject<HTMLDivElement | null>).current = element;
|
|
69
|
+
if (typeof ref === "function") {
|
|
70
|
+
ref(element);
|
|
71
|
+
} else if (ref) {
|
|
72
|
+
(ref as React.MutableRefObject<HTMLDivElement | null>).current = element;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
[ref]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Don't render if closed and not force mounted
|
|
79
|
+
if (!open && !forceMount) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const content = (
|
|
84
|
+
<div
|
|
85
|
+
ref={mergedRef}
|
|
86
|
+
id={contentProps.id}
|
|
87
|
+
role={contentProps.role}
|
|
88
|
+
aria-labelledby={contentProps["aria-labelledby"]}
|
|
89
|
+
aria-orientation={contentProps["aria-orientation"]}
|
|
90
|
+
tabIndex={contentProps.tabIndex}
|
|
91
|
+
data-state={open ? "open" : "closed"}
|
|
92
|
+
onKeyDown={handleKeyDown}
|
|
93
|
+
{...restProps}
|
|
94
|
+
>
|
|
95
|
+
{children}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Render in portal
|
|
100
|
+
const portalContainer = container ?? (typeof document !== "undefined" ? document.body : null);
|
|
101
|
+
if (portalContainer) {
|
|
102
|
+
return createPortal(content, portalContainer);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return content;
|
|
106
|
+
}
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
MenuContent.displayName = "Menu.Content";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menu context for compound component pattern.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { MenuBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
|
+
import { createCompoundContext } from "../../utils/create-context.js";
|
|
7
|
+
|
|
8
|
+
export interface MenuContextValue {
|
|
9
|
+
/** Menu behavior instance */
|
|
10
|
+
behavior: MenuBehavior;
|
|
11
|
+
/** Whether menu is open */
|
|
12
|
+
open: boolean;
|
|
13
|
+
/** Set open state */
|
|
14
|
+
setOpen: (open: boolean) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const [MenuProvider, useMenuContext] = createCompoundContext<MenuContextValue>("Menu");
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menu Item component - a selectable item in the menu.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type HTMLAttributes,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
forwardRef,
|
|
9
|
+
useCallback,
|
|
10
|
+
useEffect,
|
|
11
|
+
useRef,
|
|
12
|
+
} from "react";
|
|
13
|
+
import { useMenuContext } from "./menu-context.js";
|
|
14
|
+
|
|
15
|
+
export interface MenuItemProps extends HTMLAttributes<HTMLDivElement> {
|
|
16
|
+
/** Item content */
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
/** Value passed to onSelect */
|
|
19
|
+
value?: string;
|
|
20
|
+
/** Whether item is disabled */
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* A selectable item in the menu.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* <Menu.Item value="edit">Edit</Menu.Item>
|
|
30
|
+
* <Menu.Item value="delete" disabled>Delete</Menu.Item>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export const MenuItem = forwardRef<HTMLDivElement, MenuItemProps>(
|
|
34
|
+
({ children, value = "", disabled = false, onClick, onPointerEnter, ...restProps }, ref) => {
|
|
35
|
+
const { behavior } = useMenuContext("Menu.Item");
|
|
36
|
+
const internalRef = useRef<HTMLDivElement>(null);
|
|
37
|
+
const indexRef = useRef<number>(-1);
|
|
38
|
+
|
|
39
|
+
// Register item with behavior
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const element = internalRef.current;
|
|
42
|
+
if (element) {
|
|
43
|
+
behavior.registerItem(element);
|
|
44
|
+
// Get index after registration
|
|
45
|
+
indexRef.current = behavior.context.items.indexOf(element);
|
|
46
|
+
return () => {
|
|
47
|
+
behavior.unregisterItem(element);
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}, [behavior]);
|
|
51
|
+
|
|
52
|
+
// Handle click
|
|
53
|
+
const handleClick = useCallback(
|
|
54
|
+
(event: React.MouseEvent<HTMLDivElement>) => {
|
|
55
|
+
if (!disabled) {
|
|
56
|
+
behavior.handleItemClick(indexRef.current, value);
|
|
57
|
+
}
|
|
58
|
+
onClick?.(event);
|
|
59
|
+
},
|
|
60
|
+
[behavior, disabled, value, onClick]
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Handle pointer enter (hover)
|
|
64
|
+
const handlePointerEnter = useCallback(
|
|
65
|
+
(event: React.PointerEvent<HTMLDivElement>) => {
|
|
66
|
+
if (!disabled) {
|
|
67
|
+
behavior.handleItemPointerEnter(indexRef.current);
|
|
68
|
+
}
|
|
69
|
+
onPointerEnter?.(event);
|
|
70
|
+
},
|
|
71
|
+
[behavior, disabled, onPointerEnter]
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Get item props from behavior (need index)
|
|
75
|
+
const itemProps = behavior.getItemProps(indexRef.current, { disabled });
|
|
76
|
+
|
|
77
|
+
// Merge refs
|
|
78
|
+
const mergedRef = useCallback(
|
|
79
|
+
(element: HTMLDivElement | null) => {
|
|
80
|
+
(internalRef as React.MutableRefObject<HTMLDivElement | null>).current = element;
|
|
81
|
+
if (typeof ref === "function") {
|
|
82
|
+
ref(element);
|
|
83
|
+
} else if (ref) {
|
|
84
|
+
(ref as React.MutableRefObject<HTMLDivElement | null>).current = element;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
[ref]
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
ref={mergedRef}
|
|
93
|
+
role={itemProps.role}
|
|
94
|
+
tabIndex={itemProps.tabIndex}
|
|
95
|
+
aria-disabled={itemProps["aria-disabled"]}
|
|
96
|
+
data-highlighted={itemProps["data-highlighted"]}
|
|
97
|
+
data-value={value}
|
|
98
|
+
onClick={handleClick}
|
|
99
|
+
onPointerEnter={handlePointerEnter}
|
|
100
|
+
{...restProps}
|
|
101
|
+
>
|
|
102
|
+
{children}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
MenuItem.displayName = "Menu.Item";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menu Label component - non-interactive label for a group of items.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type HTMLAttributes, type ReactNode, forwardRef } from "react";
|
|
6
|
+
|
|
7
|
+
export interface MenuLabelProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
/** Label content */
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Non-interactive label for a group of menu items.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <Menu.Label>Actions</Menu.Label>
|
|
18
|
+
* <Menu.Item value="edit">Edit</Menu.Item>
|
|
19
|
+
* <Menu.Item value="delete">Delete</Menu.Item>
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export const MenuLabel = forwardRef<HTMLDivElement, MenuLabelProps>(
|
|
23
|
+
({ children, ...restProps }, ref) => {
|
|
24
|
+
return (
|
|
25
|
+
<div ref={ref} role="presentation" {...restProps}>
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
MenuLabel.displayName = "Menu.Label";
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menu Root component - provides context to all Menu compound components.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type Placement, createMenuBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
|
+
import { type ReactNode, useCallback, useMemo, useState } from "react";
|
|
7
|
+
import { useStableId } from "../../hooks/use-stable-id.js";
|
|
8
|
+
import { MenuProvider } from "./menu-context.js";
|
|
9
|
+
|
|
10
|
+
export interface MenuRootProps {
|
|
11
|
+
/** Menu content */
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
/** Custom ID for the menu (SSR-safe auto-generated if not provided) */
|
|
14
|
+
id?: string;
|
|
15
|
+
/** Controlled open state */
|
|
16
|
+
open?: boolean;
|
|
17
|
+
/** Default open state (uncontrolled) */
|
|
18
|
+
defaultOpen?: boolean;
|
|
19
|
+
/** Called when open state changes */
|
|
20
|
+
onOpenChange?: (open: boolean) => void;
|
|
21
|
+
/** Called when an item is selected */
|
|
22
|
+
onSelect?: (value: string) => void;
|
|
23
|
+
/** Placement relative to trigger */
|
|
24
|
+
placement?: Placement;
|
|
25
|
+
/** Offset from trigger in pixels */
|
|
26
|
+
offset?: number;
|
|
27
|
+
/** Whether to flip placement on viewport edge */
|
|
28
|
+
flip?: boolean;
|
|
29
|
+
/** Whether to loop navigation at ends */
|
|
30
|
+
loop?: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Root component for Menu compound pattern.
|
|
35
|
+
* Provides context to Trigger, Content, and Item.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* <Menu.Root onSelect={(value) => console.log(value)}>
|
|
40
|
+
* <Menu.Trigger>Open Menu</Menu.Trigger>
|
|
41
|
+
* <Menu.Content>
|
|
42
|
+
* <Menu.Item value="edit">Edit</Menu.Item>
|
|
43
|
+
* <Menu.Item value="delete">Delete</Menu.Item>
|
|
44
|
+
* </Menu.Content>
|
|
45
|
+
* </Menu.Root>
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export function MenuRoot({
|
|
49
|
+
children,
|
|
50
|
+
id,
|
|
51
|
+
open: controlledOpen,
|
|
52
|
+
defaultOpen = false,
|
|
53
|
+
onOpenChange,
|
|
54
|
+
onSelect,
|
|
55
|
+
placement = "bottom-start",
|
|
56
|
+
offset = 4,
|
|
57
|
+
flip = true,
|
|
58
|
+
loop = true,
|
|
59
|
+
}: MenuRootProps) {
|
|
60
|
+
// Generate SSR-safe stable ID using React 18's useId under the hood
|
|
61
|
+
const stableId = useStableId({ id, prefix: "menu" });
|
|
62
|
+
|
|
63
|
+
// Support both controlled and uncontrolled modes
|
|
64
|
+
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
65
|
+
const isControlled = controlledOpen !== undefined;
|
|
66
|
+
const open = isControlled ? controlledOpen : internalOpen;
|
|
67
|
+
|
|
68
|
+
const setOpen = useCallback(
|
|
69
|
+
(nextOpen: boolean) => {
|
|
70
|
+
if (!isControlled) {
|
|
71
|
+
setInternalOpen(nextOpen);
|
|
72
|
+
}
|
|
73
|
+
onOpenChange?.(nextOpen);
|
|
74
|
+
},
|
|
75
|
+
[isControlled, onOpenChange]
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Create behavior instance - intentionally created once with initial values
|
|
79
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: behavior is created once, open state synced via setOpen callback
|
|
80
|
+
const behavior = useMemo(
|
|
81
|
+
() =>
|
|
82
|
+
createMenuBehavior({
|
|
83
|
+
defaultOpen: open,
|
|
84
|
+
placement,
|
|
85
|
+
offset,
|
|
86
|
+
flip,
|
|
87
|
+
loop,
|
|
88
|
+
onOpenChange: setOpen,
|
|
89
|
+
onSelect,
|
|
90
|
+
// Use SSR-safe stable ID generator
|
|
91
|
+
generateId: () => stableId,
|
|
92
|
+
}),
|
|
93
|
+
[stableId]
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const contextValue = useMemo(
|
|
97
|
+
() => ({
|
|
98
|
+
behavior,
|
|
99
|
+
open,
|
|
100
|
+
setOpen,
|
|
101
|
+
}),
|
|
102
|
+
[behavior, open, setOpen]
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
return <MenuProvider value={contextValue}>{children}</MenuProvider>;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
MenuRoot.displayName = "Menu.Root";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menu Separator component - visual separator between items.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type HTMLAttributes, forwardRef } from "react";
|
|
6
|
+
|
|
7
|
+
export interface MenuSeparatorProps extends HTMLAttributes<HTMLDivElement> {}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Visual separator between menu items.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <Menu.Item value="edit">Edit</Menu.Item>
|
|
15
|
+
* <Menu.Separator />
|
|
16
|
+
* <Menu.Item value="delete">Delete</Menu.Item>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export const MenuSeparator = forwardRef<HTMLDivElement, MenuSeparatorProps>((props, ref) => {
|
|
20
|
+
// biome-ignore lint/a11y/useFocusableInteractive: separator is decorative, not interactive
|
|
21
|
+
return <div ref={ref} role="separator" aria-orientation="horizontal" {...props} />;
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
MenuSeparator.displayName = "Menu.Separator";
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Menu Trigger component - opens the menu when activated.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type ButtonHTMLAttributes,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
forwardRef,
|
|
9
|
+
useCallback,
|
|
10
|
+
useEffect,
|
|
11
|
+
useRef,
|
|
12
|
+
} from "react";
|
|
13
|
+
import { Slot } from "../../primitives/slot.js";
|
|
14
|
+
import { useMenuContext } from "./menu-context.js";
|
|
15
|
+
|
|
16
|
+
export interface MenuTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
17
|
+
/** Trigger content */
|
|
18
|
+
children?: ReactNode;
|
|
19
|
+
/** Render as child element (polymorphic) */
|
|
20
|
+
asChild?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Trigger button that opens the menu.
|
|
25
|
+
* Supports asChild for custom trigger elements.
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* <Menu.Trigger>Open Menu</Menu.Trigger>
|
|
30
|
+
*
|
|
31
|
+
* <Menu.Trigger asChild>
|
|
32
|
+
* <button className="custom-button">Custom Trigger</button>
|
|
33
|
+
* </Menu.Trigger>
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export const MenuTrigger = forwardRef<HTMLButtonElement, MenuTriggerProps>(
|
|
37
|
+
({ children, asChild = false, onClick, onKeyDown, ...restProps }, ref) => {
|
|
38
|
+
const { behavior } = useMenuContext("Menu.Trigger");
|
|
39
|
+
const internalRef = useRef<HTMLButtonElement>(null);
|
|
40
|
+
|
|
41
|
+
// Register trigger element with behavior
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const element = internalRef.current;
|
|
44
|
+
behavior.setTriggerElement(element);
|
|
45
|
+
return () => {
|
|
46
|
+
behavior.setTriggerElement(null);
|
|
47
|
+
};
|
|
48
|
+
}, [behavior]);
|
|
49
|
+
|
|
50
|
+
// Handle click to toggle menu
|
|
51
|
+
const handleClick = useCallback(
|
|
52
|
+
(event: React.MouseEvent<HTMLButtonElement>) => {
|
|
53
|
+
behavior.toggle();
|
|
54
|
+
onClick?.(event);
|
|
55
|
+
},
|
|
56
|
+
[behavior, onClick]
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Handle keyboard navigation
|
|
60
|
+
const handleKeyDown = useCallback(
|
|
61
|
+
(event: React.KeyboardEvent<HTMLButtonElement>) => {
|
|
62
|
+
behavior.handleTriggerKeyDown(event.nativeEvent);
|
|
63
|
+
onKeyDown?.(event);
|
|
64
|
+
},
|
|
65
|
+
[behavior, onKeyDown]
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Get trigger props from behavior
|
|
69
|
+
const triggerProps = behavior.getTriggerProps();
|
|
70
|
+
|
|
71
|
+
const Component = asChild ? Slot : "button";
|
|
72
|
+
|
|
73
|
+
// Merge refs
|
|
74
|
+
const mergedRef = useCallback(
|
|
75
|
+
(element: HTMLButtonElement | null) => {
|
|
76
|
+
(internalRef as React.MutableRefObject<HTMLButtonElement | null>).current = element;
|
|
77
|
+
if (typeof ref === "function") {
|
|
78
|
+
ref(element);
|
|
79
|
+
} else if (ref) {
|
|
80
|
+
(ref as React.MutableRefObject<HTMLButtonElement | null>).current = element;
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
[ref]
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<Component
|
|
88
|
+
ref={mergedRef}
|
|
89
|
+
type={asChild ? undefined : "button"}
|
|
90
|
+
id={triggerProps.id}
|
|
91
|
+
aria-haspopup={triggerProps["aria-haspopup"]}
|
|
92
|
+
aria-expanded={triggerProps["aria-expanded"]}
|
|
93
|
+
aria-controls={triggerProps["aria-controls"]}
|
|
94
|
+
onClick={handleClick}
|
|
95
|
+
onKeyDown={handleKeyDown}
|
|
96
|
+
{...restProps}
|
|
97
|
+
>
|
|
98
|
+
{children}
|
|
99
|
+
</Component>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
MenuTrigger.displayName = "Menu.Trigger";
|