@hypoth-ui/cli 0.0.1 → 0.1.1
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,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select context for compound component pattern.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { SelectBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
|
+
import { createCompoundContext } from "../../utils/create-context.js";
|
|
7
|
+
|
|
8
|
+
export interface SelectContextValue {
|
|
9
|
+
/** Select behavior instance */
|
|
10
|
+
behavior: SelectBehavior<string>;
|
|
11
|
+
/** Whether select is open */
|
|
12
|
+
open: boolean;
|
|
13
|
+
/** Set open state */
|
|
14
|
+
setOpen: (open: boolean) => void;
|
|
15
|
+
/** Current value */
|
|
16
|
+
value: string | null;
|
|
17
|
+
/** Set value */
|
|
18
|
+
setValue: (value: string | null) => void;
|
|
19
|
+
/** Highlighted value for keyboard navigation */
|
|
20
|
+
highlightedValue: string | null;
|
|
21
|
+
/** Set highlighted value */
|
|
22
|
+
setHighlightedValue: (value: string | null) => void;
|
|
23
|
+
/** Whether the select is in a loading state */
|
|
24
|
+
loading: boolean;
|
|
25
|
+
/** Text to display during loading */
|
|
26
|
+
loadingText: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const [SelectProvider, useSelectContext] =
|
|
30
|
+
createCompoundContext<SelectContextValue>("Select");
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select Group component - groups options with an optional label.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type HTMLAttributes, type ReactNode, forwardRef, useId } from "react";
|
|
6
|
+
|
|
7
|
+
export interface SelectGroupProps extends HTMLAttributes<HTMLFieldSetElement> {
|
|
8
|
+
/** Group content (Label and Options) */
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
/** Optional label text (alternative to using Select.Label as child) */
|
|
11
|
+
label?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Group for organizing select options.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* <Select.Content>
|
|
20
|
+
* <Select.Group>
|
|
21
|
+
* <Select.Label>Fruits</Select.Label>
|
|
22
|
+
* <Select.Option value="apple">Apple</Select.Option>
|
|
23
|
+
* <Select.Option value="banana">Banana</Select.Option>
|
|
24
|
+
* </Select.Group>
|
|
25
|
+
* <Select.Group label="Vegetables">
|
|
26
|
+
* <Select.Option value="carrot">Carrot</Select.Option>
|
|
27
|
+
* <Select.Option value="potato">Potato</Select.Option>
|
|
28
|
+
* </Select.Group>
|
|
29
|
+
* </Select.Content>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export const SelectGroup = forwardRef<HTMLFieldSetElement, SelectGroupProps>(
|
|
33
|
+
({ children, className, label, ...restProps }, ref) => {
|
|
34
|
+
const groupId = useId();
|
|
35
|
+
const labelId = `${groupId}-label`;
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<fieldset
|
|
39
|
+
ref={ref}
|
|
40
|
+
aria-label={label}
|
|
41
|
+
aria-labelledby={!label ? labelId : undefined}
|
|
42
|
+
className={className}
|
|
43
|
+
data-group-label-id={labelId}
|
|
44
|
+
{...restProps}
|
|
45
|
+
>
|
|
46
|
+
{label && <legend className="sr-only">{label}</legend>}
|
|
47
|
+
{children}
|
|
48
|
+
</fieldset>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
SelectGroup.displayName = "Select.Group";
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select Label component - label for option groups.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type HTMLAttributes, type ReactNode, forwardRef } from "react";
|
|
6
|
+
|
|
7
|
+
export interface SelectLabelProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
/** Label content */
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Label for grouping options.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <Select.Content>
|
|
18
|
+
* <Select.Label>Fruits</Select.Label>
|
|
19
|
+
* <Select.Option value="apple">Apple</Select.Option>
|
|
20
|
+
* <Select.Option value="banana">Banana</Select.Option>
|
|
21
|
+
* </Select.Content>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export const SelectLabel = forwardRef<HTMLDivElement, SelectLabelProps>(
|
|
25
|
+
({ children, className, ...restProps }, ref) => {
|
|
26
|
+
return (
|
|
27
|
+
<div ref={ref} role="presentation" aria-hidden="true" className={className} {...restProps}>
|
|
28
|
+
{children}
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
SelectLabel.displayName = "Select.Label";
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select Option component - individual option in the select.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type HTMLAttributes, type ReactNode, forwardRef, useCallback } from "react";
|
|
6
|
+
import { useSelectContext } from "./select-context.js";
|
|
7
|
+
|
|
8
|
+
export interface SelectOptionProps extends Omit<HTMLAttributes<HTMLDivElement>, "value"> {
|
|
9
|
+
/** Option value */
|
|
10
|
+
value: string;
|
|
11
|
+
/** Display label (uses children if not specified) */
|
|
12
|
+
label?: string;
|
|
13
|
+
/** Whether the option is disabled */
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
/** Option content */
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Individual option in the select.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <Select.Option value="apple">Apple</Select.Option>
|
|
25
|
+
* <Select.Option value="banana" disabled>Banana</Select.Option>
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export const SelectOption = forwardRef<HTMLDivElement, SelectOptionProps>(
|
|
29
|
+
(
|
|
30
|
+
{
|
|
31
|
+
value: optionValue,
|
|
32
|
+
label,
|
|
33
|
+
disabled = false,
|
|
34
|
+
children,
|
|
35
|
+
className,
|
|
36
|
+
onClick,
|
|
37
|
+
onMouseEnter,
|
|
38
|
+
...restProps
|
|
39
|
+
},
|
|
40
|
+
ref
|
|
41
|
+
) => {
|
|
42
|
+
const { behavior, value, highlightedValue, setHighlightedValue, setOpen } =
|
|
43
|
+
useSelectContext("Select.Option");
|
|
44
|
+
|
|
45
|
+
const isSelected = value === optionValue;
|
|
46
|
+
const isHighlighted = highlightedValue === optionValue;
|
|
47
|
+
|
|
48
|
+
// Handle click to select
|
|
49
|
+
const handleClick = useCallback(
|
|
50
|
+
(event: React.MouseEvent<HTMLDivElement>) => {
|
|
51
|
+
if (disabled) return;
|
|
52
|
+
behavior.select(optionValue);
|
|
53
|
+
setOpen(false);
|
|
54
|
+
onClick?.(event);
|
|
55
|
+
},
|
|
56
|
+
[behavior, optionValue, disabled, setOpen, onClick]
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Handle mouse enter to highlight
|
|
60
|
+
const handleMouseEnter = useCallback(
|
|
61
|
+
(event: React.MouseEvent<HTMLDivElement>) => {
|
|
62
|
+
if (disabled) return;
|
|
63
|
+
behavior.highlight(optionValue);
|
|
64
|
+
setHighlightedValue(optionValue);
|
|
65
|
+
onMouseEnter?.(event);
|
|
66
|
+
},
|
|
67
|
+
[behavior, optionValue, disabled, setHighlightedValue, onMouseEnter]
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const optionProps = behavior.getOptionProps(
|
|
71
|
+
optionValue,
|
|
72
|
+
label || String(children) || optionValue
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
ref={ref}
|
|
78
|
+
role={optionProps.role}
|
|
79
|
+
id={optionProps.id}
|
|
80
|
+
aria-selected={isSelected}
|
|
81
|
+
aria-disabled={disabled || undefined}
|
|
82
|
+
data-value={optionValue}
|
|
83
|
+
data-selected={isSelected || undefined}
|
|
84
|
+
data-highlighted={isHighlighted || undefined}
|
|
85
|
+
data-disabled={disabled || undefined}
|
|
86
|
+
className={className}
|
|
87
|
+
onClick={handleClick}
|
|
88
|
+
onMouseEnter={handleMouseEnter}
|
|
89
|
+
{...restProps}
|
|
90
|
+
>
|
|
91
|
+
{children}
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
SelectOption.displayName = "Select.Option";
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select Root component - provides context to all Select compound components.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type Placement, createSelectBehavior } 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 { SelectProvider } from "./select-context.js";
|
|
9
|
+
|
|
10
|
+
export interface SelectRootProps {
|
|
11
|
+
/** Select content */
|
|
12
|
+
children?: ReactNode;
|
|
13
|
+
/** Custom ID for the select (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
|
+
/** Controlled value */
|
|
22
|
+
value?: string | null;
|
|
23
|
+
/** Default value (uncontrolled) */
|
|
24
|
+
defaultValue?: string | null;
|
|
25
|
+
/** Called when value changes */
|
|
26
|
+
onValueChange?: (value: string | null) => void;
|
|
27
|
+
/** Placement relative to trigger */
|
|
28
|
+
placement?: Placement;
|
|
29
|
+
/** Offset from trigger in pixels */
|
|
30
|
+
offset?: number;
|
|
31
|
+
/** Whether to flip placement on viewport edge */
|
|
32
|
+
flip?: boolean;
|
|
33
|
+
/** Whether select is disabled */
|
|
34
|
+
disabled?: boolean;
|
|
35
|
+
/** Whether select is read-only */
|
|
36
|
+
readOnly?: boolean;
|
|
37
|
+
/** Enable typeahead search */
|
|
38
|
+
searchable?: boolean;
|
|
39
|
+
/** Allow clearing the selection */
|
|
40
|
+
clearable?: boolean;
|
|
41
|
+
/** Whether the select is in a loading state (e.g., fetching options) */
|
|
42
|
+
loading?: boolean;
|
|
43
|
+
/** Text to display/announce during loading */
|
|
44
|
+
loadingText?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Root component for Select compound pattern.
|
|
49
|
+
* Provides context to Trigger, Content, and Option.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```tsx
|
|
53
|
+
* <Select.Root onValueChange={(value) => console.log(value)}>
|
|
54
|
+
* <Select.Trigger>Select a fruit</Select.Trigger>
|
|
55
|
+
* <Select.Content>
|
|
56
|
+
* <Select.Option value="apple">Apple</Select.Option>
|
|
57
|
+
* <Select.Option value="banana">Banana</Select.Option>
|
|
58
|
+
* </Select.Content>
|
|
59
|
+
* </Select.Root>
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function SelectRoot({
|
|
63
|
+
children,
|
|
64
|
+
id,
|
|
65
|
+
open: controlledOpen,
|
|
66
|
+
defaultOpen = false,
|
|
67
|
+
onOpenChange,
|
|
68
|
+
value: controlledValue,
|
|
69
|
+
defaultValue = null,
|
|
70
|
+
onValueChange,
|
|
71
|
+
placement: _placement = "bottom-start",
|
|
72
|
+
offset: _offset = 4,
|
|
73
|
+
flip: _flip = true,
|
|
74
|
+
disabled = false,
|
|
75
|
+
readOnly = false,
|
|
76
|
+
searchable = true,
|
|
77
|
+
clearable = false,
|
|
78
|
+
loading = false,
|
|
79
|
+
loadingText = "Loading...",
|
|
80
|
+
}: SelectRootProps) {
|
|
81
|
+
// Generate SSR-safe stable ID using React 18's useId under the hood
|
|
82
|
+
const stableId = useStableId({ id, prefix: "select" });
|
|
83
|
+
|
|
84
|
+
// Support both controlled and uncontrolled modes for open
|
|
85
|
+
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
86
|
+
const isOpenControlled = controlledOpen !== undefined;
|
|
87
|
+
const open = isOpenControlled ? controlledOpen : internalOpen;
|
|
88
|
+
|
|
89
|
+
// Support both controlled and uncontrolled modes for value
|
|
90
|
+
const [internalValue, setInternalValue] = useState<string | null>(defaultValue);
|
|
91
|
+
const isValueControlled = controlledValue !== undefined;
|
|
92
|
+
const value = isValueControlled ? controlledValue : internalValue;
|
|
93
|
+
|
|
94
|
+
// Highlighted value for keyboard navigation
|
|
95
|
+
const [highlightedValue, setHighlightedValue] = useState<string | null>(value);
|
|
96
|
+
|
|
97
|
+
const setOpen = useCallback(
|
|
98
|
+
(nextOpen: boolean) => {
|
|
99
|
+
if (!isOpenControlled) {
|
|
100
|
+
setInternalOpen(nextOpen);
|
|
101
|
+
}
|
|
102
|
+
onOpenChange?.(nextOpen);
|
|
103
|
+
},
|
|
104
|
+
[isOpenControlled, onOpenChange]
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const setValue = useCallback(
|
|
108
|
+
(nextValue: string | null) => {
|
|
109
|
+
if (!isValueControlled) {
|
|
110
|
+
setInternalValue(nextValue);
|
|
111
|
+
}
|
|
112
|
+
onValueChange?.(nextValue);
|
|
113
|
+
},
|
|
114
|
+
[isValueControlled, onValueChange]
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
// Create behavior instance - intentionally created once with initial values
|
|
118
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: behavior is created once, state synced via callbacks
|
|
119
|
+
const behavior = useMemo(
|
|
120
|
+
() =>
|
|
121
|
+
createSelectBehavior({
|
|
122
|
+
defaultValue: value,
|
|
123
|
+
disabled,
|
|
124
|
+
readOnly,
|
|
125
|
+
searchable,
|
|
126
|
+
clearable,
|
|
127
|
+
onValueChange: (v) => setValue(v),
|
|
128
|
+
onOpenChange: (o) => setOpen(o),
|
|
129
|
+
// Use SSR-safe stable ID generator
|
|
130
|
+
generateId: () => stableId,
|
|
131
|
+
}),
|
|
132
|
+
[stableId]
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const contextValue = useMemo(
|
|
136
|
+
() => ({
|
|
137
|
+
behavior,
|
|
138
|
+
open,
|
|
139
|
+
setOpen,
|
|
140
|
+
value,
|
|
141
|
+
setValue,
|
|
142
|
+
highlightedValue,
|
|
143
|
+
setHighlightedValue,
|
|
144
|
+
loading,
|
|
145
|
+
loadingText,
|
|
146
|
+
}),
|
|
147
|
+
[behavior, open, setOpen, value, setValue, highlightedValue, loading, loadingText]
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
return <SelectProvider value={contextValue}>{children}</SelectProvider>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
SelectRoot.displayName = "Select.Root";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select Separator component - visual separator between groups.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type HTMLAttributes, forwardRef } from "react";
|
|
6
|
+
|
|
7
|
+
export interface SelectSeparatorProps extends HTMLAttributes<HTMLHRElement> {}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Visual separator for grouping options.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* <Select.Content>
|
|
15
|
+
* <Select.Option value="apple">Apple</Select.Option>
|
|
16
|
+
* <Select.Separator />
|
|
17
|
+
* <Select.Option value="banana">Banana</Select.Option>
|
|
18
|
+
* </Select.Content>
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export const SelectSeparator = forwardRef<HTMLHRElement, SelectSeparatorProps>(
|
|
22
|
+
({ className, ...restProps }, ref) => {
|
|
23
|
+
return <hr ref={ref} className={className} {...restProps} />;
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
SelectSeparator.displayName = "Select.Separator";
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select Trigger component - opens the select when activated.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type ButtonHTMLAttributes, type ReactNode, forwardRef, useCallback, useRef } from "react";
|
|
6
|
+
import { Slot } from "../../primitives/slot.js";
|
|
7
|
+
import { useSelectContext } from "./select-context.js";
|
|
8
|
+
|
|
9
|
+
export interface SelectTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
10
|
+
/** Trigger content */
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
/** Render as child element (polymorphic) */
|
|
13
|
+
asChild?: boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Trigger button that opens the select.
|
|
18
|
+
* Supports asChild for custom trigger elements.
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```tsx
|
|
22
|
+
* <Select.Trigger>Select a fruit</Select.Trigger>
|
|
23
|
+
*
|
|
24
|
+
* <Select.Trigger asChild>
|
|
25
|
+
* <button className="custom-button">Custom Trigger</button>
|
|
26
|
+
* </Select.Trigger>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export const SelectTrigger = forwardRef<HTMLButtonElement, SelectTriggerProps>(
|
|
30
|
+
({ children, asChild = false, onClick, onKeyDown, ...restProps }, ref) => {
|
|
31
|
+
const { behavior, open, highlightedValue } = useSelectContext("Select.Trigger");
|
|
32
|
+
const internalRef = useRef<HTMLButtonElement>(null);
|
|
33
|
+
|
|
34
|
+
// Handle click to toggle select
|
|
35
|
+
const handleClick = useCallback(
|
|
36
|
+
(event: React.MouseEvent<HTMLButtonElement>) => {
|
|
37
|
+
behavior.toggle();
|
|
38
|
+
onClick?.(event);
|
|
39
|
+
},
|
|
40
|
+
[behavior, onClick]
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Handle keyboard navigation
|
|
44
|
+
const handleKeyDown = useCallback(
|
|
45
|
+
(event: React.KeyboardEvent<HTMLButtonElement>) => {
|
|
46
|
+
switch (event.key) {
|
|
47
|
+
case "Enter":
|
|
48
|
+
case " ":
|
|
49
|
+
event.preventDefault();
|
|
50
|
+
behavior.toggle();
|
|
51
|
+
break;
|
|
52
|
+
case "ArrowDown":
|
|
53
|
+
event.preventDefault();
|
|
54
|
+
if (!open) {
|
|
55
|
+
behavior.open();
|
|
56
|
+
behavior.highlightFirst();
|
|
57
|
+
}
|
|
58
|
+
break;
|
|
59
|
+
case "ArrowUp":
|
|
60
|
+
event.preventDefault();
|
|
61
|
+
if (!open) {
|
|
62
|
+
behavior.open();
|
|
63
|
+
behavior.highlightLast();
|
|
64
|
+
}
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
onKeyDown?.(event);
|
|
68
|
+
},
|
|
69
|
+
[behavior, open, onKeyDown]
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Get trigger props from behavior
|
|
73
|
+
const triggerProps = behavior.getTriggerProps();
|
|
74
|
+
|
|
75
|
+
const Component = asChild ? Slot : "button";
|
|
76
|
+
|
|
77
|
+
// Merge refs
|
|
78
|
+
const mergedRef = useCallback(
|
|
79
|
+
(element: HTMLButtonElement | null) => {
|
|
80
|
+
(internalRef as React.MutableRefObject<HTMLButtonElement | null>).current = element;
|
|
81
|
+
if (typeof ref === "function") {
|
|
82
|
+
ref(element);
|
|
83
|
+
} else if (ref) {
|
|
84
|
+
(ref as React.MutableRefObject<HTMLButtonElement | null>).current = element;
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
[ref]
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Component
|
|
92
|
+
ref={mergedRef}
|
|
93
|
+
type={asChild ? undefined : "button"}
|
|
94
|
+
role={triggerProps.role}
|
|
95
|
+
aria-haspopup={triggerProps["aria-haspopup"]}
|
|
96
|
+
aria-expanded={open}
|
|
97
|
+
aria-controls={behavior.contentId}
|
|
98
|
+
aria-activedescendant={
|
|
99
|
+
open && highlightedValue ? `select-option-${highlightedValue}` : undefined
|
|
100
|
+
}
|
|
101
|
+
onClick={handleClick}
|
|
102
|
+
onKeyDown={handleKeyDown}
|
|
103
|
+
data-state={open ? "open" : "closed"}
|
|
104
|
+
{...restProps}
|
|
105
|
+
>
|
|
106
|
+
{children}
|
|
107
|
+
</Component>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
SelectTrigger.displayName = "Select.Trigger";
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select Value component - displays the selected value.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type HTMLAttributes, type ReactNode, forwardRef } from "react";
|
|
6
|
+
import { useSelectContext } from "./select-context.js";
|
|
7
|
+
|
|
8
|
+
export interface SelectValueProps extends Omit<HTMLAttributes<HTMLSpanElement>, "children"> {
|
|
9
|
+
/** Placeholder text when no value is selected */
|
|
10
|
+
placeholder?: string;
|
|
11
|
+
/** Custom render function for the value */
|
|
12
|
+
children?: ReactNode | ((value: string | null) => ReactNode);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Displays the currently selected value.
|
|
17
|
+
* Used inside Select.Trigger.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```tsx
|
|
21
|
+
* <Select.Trigger>
|
|
22
|
+
* <Select.Value placeholder="Select a fruit" />
|
|
23
|
+
* </Select.Trigger>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export const SelectValue = forwardRef<HTMLSpanElement, SelectValueProps>(
|
|
27
|
+
({ placeholder, children, className, ...restProps }, ref) => {
|
|
28
|
+
const { value } = useSelectContext("Select.Value");
|
|
29
|
+
|
|
30
|
+
let displayContent: ReactNode;
|
|
31
|
+
|
|
32
|
+
if (typeof children === "function") {
|
|
33
|
+
displayContent = children(value);
|
|
34
|
+
} else if (children) {
|
|
35
|
+
displayContent = value ? children : placeholder;
|
|
36
|
+
} else {
|
|
37
|
+
displayContent = value || placeholder;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<span ref={ref} className={className} data-placeholder={!value || undefined} {...restProps}>
|
|
42
|
+
{displayContent}
|
|
43
|
+
</span>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
SelectValue.displayName = "Select.Value";
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { DsSelect } from "./select.js";
|
|
2
|
+
export { DsSelectTrigger } from "./select-trigger.js";
|
|
3
|
+
export { DsSelectContent, type SelectContentState } from "./select-content.js";
|
|
4
|
+
export { DsSelectOption } from "./select-option.js";
|
|
5
|
+
export { DsSelectGroup } from "./select-group.js";
|
|
6
|
+
export { DsSelectLabel } from "./select-label.js";
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { html } from "lit";
|
|
2
|
+
import { property } from "lit/decorators.js";
|
|
3
|
+
import { DSElement } from "../../base/ds-element.js";
|
|
4
|
+
import { define } from "../../registry/define.js";
|
|
5
|
+
|
|
6
|
+
export type SelectContentState = "open" | "closed";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Select content container with role="listbox".
|
|
10
|
+
*
|
|
11
|
+
* @element ds-select-content
|
|
12
|
+
*
|
|
13
|
+
* @slot - Select options (ds-select-option elements)
|
|
14
|
+
*
|
|
15
|
+
* @attr {string} data-state - Animation state ("open" or "closed")
|
|
16
|
+
* @attr {string} data-placement - Current anchor position placement
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```html
|
|
20
|
+
* <ds-select>
|
|
21
|
+
* <button slot="trigger">Select fruit</button>
|
|
22
|
+
* <ds-select-content>
|
|
23
|
+
* <ds-select-option value="apple">Apple</ds-select-option>
|
|
24
|
+
* <ds-select-option value="banana">Banana</ds-select-option>
|
|
25
|
+
* </ds-select-content>
|
|
26
|
+
* </ds-select>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export class DsSelectContent extends DSElement {
|
|
30
|
+
/** Unique ID for ARIA association */
|
|
31
|
+
@property({ type: String, reflect: true })
|
|
32
|
+
override id = "";
|
|
33
|
+
|
|
34
|
+
/** Animation state (open or closed) - set by parent ds-select */
|
|
35
|
+
@property({ type: String, reflect: true, attribute: "data-state" })
|
|
36
|
+
dataState: SelectContentState = "closed";
|
|
37
|
+
|
|
38
|
+
/** Optional label for the listbox */
|
|
39
|
+
@property({ type: String })
|
|
40
|
+
label = "";
|
|
41
|
+
|
|
42
|
+
override connectedCallback(): void {
|
|
43
|
+
super.connectedCallback();
|
|
44
|
+
|
|
45
|
+
// Generate ID if not set
|
|
46
|
+
if (!this.id) {
|
|
47
|
+
this.id = `select-content-${crypto.randomUUID().slice(0, 8)}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Set ARIA role for listbox
|
|
51
|
+
this.setAttribute("role", "listbox");
|
|
52
|
+
|
|
53
|
+
// Set label if provided
|
|
54
|
+
if (this.label) {
|
|
55
|
+
this.setAttribute("aria-label", this.label);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Hidden by default (parent select controls visibility)
|
|
59
|
+
this.setAttribute("hidden", "");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
override updated(changedProperties: Map<string, unknown>): void {
|
|
63
|
+
super.updated(changedProperties);
|
|
64
|
+
|
|
65
|
+
if (changedProperties.has("label")) {
|
|
66
|
+
if (this.label) {
|
|
67
|
+
this.setAttribute("aria-label", this.label);
|
|
68
|
+
} else {
|
|
69
|
+
this.removeAttribute("aria-label");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
override render() {
|
|
75
|
+
return html`
|
|
76
|
+
<div class="ds-select-content" part="container">
|
|
77
|
+
<slot></slot>
|
|
78
|
+
</div>
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
define("ds-select-content", DsSelectContent);
|
|
84
|
+
|
|
85
|
+
declare global {
|
|
86
|
+
interface HTMLElementTagNameMap {
|
|
87
|
+
"ds-select-content": DsSelectContent;
|
|
88
|
+
}
|
|
89
|
+
}
|