@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,40 @@
|
|
|
1
|
+
import type React from "react";
|
|
2
|
+
import { type HTMLAttributes, createElement, forwardRef, useEffect, useRef } from "react";
|
|
3
|
+
|
|
4
|
+
export interface LabelProps extends HTMLAttributes<HTMLElement> {
|
|
5
|
+
/** For attribute linking to input */
|
|
6
|
+
htmlFor?: string;
|
|
7
|
+
/** Label content */
|
|
8
|
+
children?: React.ReactNode;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* React wrapper for ds-label Web Component.
|
|
13
|
+
* Label for form fields with automatic ID association.
|
|
14
|
+
*/
|
|
15
|
+
export const Label = forwardRef<HTMLElement, LabelProps>((props, forwardedRef) => {
|
|
16
|
+
const { htmlFor, children, className, ...rest } = props;
|
|
17
|
+
|
|
18
|
+
const internalRef = useRef<HTMLElement>(null);
|
|
19
|
+
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
if (typeof forwardedRef === "function") {
|
|
22
|
+
forwardedRef(internalRef.current);
|
|
23
|
+
} else if (forwardedRef) {
|
|
24
|
+
(forwardedRef as React.MutableRefObject<HTMLElement | null>).current = internalRef.current;
|
|
25
|
+
}
|
|
26
|
+
}, [forwardedRef]);
|
|
27
|
+
|
|
28
|
+
return createElement(
|
|
29
|
+
"ds-label",
|
|
30
|
+
{
|
|
31
|
+
ref: internalRef,
|
|
32
|
+
for: htmlFor,
|
|
33
|
+
class: className,
|
|
34
|
+
...rest,
|
|
35
|
+
},
|
|
36
|
+
children
|
|
37
|
+
);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
Label.displayName = "Label";
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { html } from "lit";
|
|
2
|
+
import { DSElement } from "../../base/ds-element.js";
|
|
3
|
+
import { define } from "../../registry/define.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Description/help text component for form fields.
|
|
7
|
+
*
|
|
8
|
+
* Used within ds-field to provide supplementary help text that is
|
|
9
|
+
* automatically associated with the form control via aria-describedby.
|
|
10
|
+
*
|
|
11
|
+
* @element ds-field-description
|
|
12
|
+
*
|
|
13
|
+
* @slot - Description text content
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```html
|
|
17
|
+
* <ds-field>
|
|
18
|
+
* <ds-label>Password</ds-label>
|
|
19
|
+
* <ds-input type="password"></ds-input>
|
|
20
|
+
* <ds-field-description>
|
|
21
|
+
* Must be at least 8 characters with one number
|
|
22
|
+
* </ds-field-description>
|
|
23
|
+
* </ds-field>
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export class DsFieldDescription extends DSElement {
|
|
27
|
+
override render() {
|
|
28
|
+
return html`
|
|
29
|
+
<div class="ds-field-description" part="description">
|
|
30
|
+
<slot></slot>
|
|
31
|
+
</div>
|
|
32
|
+
`;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
define("ds-field-description", DsFieldDescription);
|
|
37
|
+
|
|
38
|
+
declare global {
|
|
39
|
+
interface HTMLElementTagNameMap {
|
|
40
|
+
"ds-field-description": DsFieldDescription;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { html } from "lit";
|
|
2
|
+
import { DSElement } from "../../base/ds-element.js";
|
|
3
|
+
import { define } from "../../registry/define.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Error message component for form fields.
|
|
7
|
+
*
|
|
8
|
+
* Used within ds-field to display validation error messages that are
|
|
9
|
+
* automatically associated with the form control via aria-describedby.
|
|
10
|
+
* Error messages take precedence over descriptions in the ARIA description.
|
|
11
|
+
*
|
|
12
|
+
* @element ds-field-error
|
|
13
|
+
*
|
|
14
|
+
* @slot - Error message text content
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```html
|
|
18
|
+
* <ds-field>
|
|
19
|
+
* <ds-label>Email</ds-label>
|
|
20
|
+
* <ds-input type="email" error></ds-input>
|
|
21
|
+
* <ds-field-error>Please enter a valid email address</ds-field-error>
|
|
22
|
+
* </ds-field>
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
export class DsFieldError extends DSElement {
|
|
26
|
+
override render() {
|
|
27
|
+
return html`
|
|
28
|
+
<div
|
|
29
|
+
class="ds-field-error"
|
|
30
|
+
part="error"
|
|
31
|
+
role="alert"
|
|
32
|
+
aria-live="polite"
|
|
33
|
+
>
|
|
34
|
+
<slot></slot>
|
|
35
|
+
</div>
|
|
36
|
+
`;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
define("ds-field-error", DsFieldError);
|
|
41
|
+
|
|
42
|
+
declare global {
|
|
43
|
+
interface HTMLElementTagNameMap {
|
|
44
|
+
"ds-field-error": DsFieldError;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { html } from "lit";
|
|
2
|
+
import { property, state } from "lit/decorators.js";
|
|
3
|
+
import { DSElement } from "../../base/ds-element.js";
|
|
4
|
+
import { define } from "../../registry/define.js";
|
|
5
|
+
|
|
6
|
+
// Import child components to ensure they're registered
|
|
7
|
+
import "./label.js";
|
|
8
|
+
import "./field-description.js";
|
|
9
|
+
import "./field-error.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Field container component for form controls.
|
|
13
|
+
*
|
|
14
|
+
* Provides consistent layout and automatic ARIA attribute composition
|
|
15
|
+
* for form fields. Associates labels, descriptions, and error messages
|
|
16
|
+
* with the form control for accessibility.
|
|
17
|
+
*
|
|
18
|
+
* @element ds-field
|
|
19
|
+
*
|
|
20
|
+
* @slot - Field contents (label, form control, description, error)
|
|
21
|
+
*
|
|
22
|
+
* @csspart container - The field container element
|
|
23
|
+
*
|
|
24
|
+
* @cssprop --ds-field-gap - Gap between field children (default: 0.5rem)
|
|
25
|
+
* @cssprop --ds-field-error-color - Error message color
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```html
|
|
29
|
+
* <ds-field required>
|
|
30
|
+
* <ds-label>Email address</ds-label>
|
|
31
|
+
* <ds-field-description>We'll never share your email</ds-field-description>
|
|
32
|
+
* <ds-input type="email" name="email"></ds-input>
|
|
33
|
+
* <ds-field-error>Please enter a valid email</ds-field-error>
|
|
34
|
+
* </ds-field>
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export class DsField extends DSElement {
|
|
38
|
+
/** Marks the field as required */
|
|
39
|
+
@property({ type: Boolean, reflect: true })
|
|
40
|
+
required = false;
|
|
41
|
+
|
|
42
|
+
/** Whether the field is disabled */
|
|
43
|
+
@property({ type: Boolean, reflect: true })
|
|
44
|
+
disabled = false;
|
|
45
|
+
|
|
46
|
+
/** Generated unique ID for ARIA associations */
|
|
47
|
+
@state()
|
|
48
|
+
private fieldId = "";
|
|
49
|
+
|
|
50
|
+
/** Whether an error child is present */
|
|
51
|
+
@state()
|
|
52
|
+
private hasError = false;
|
|
53
|
+
|
|
54
|
+
/** Whether a description child is present */
|
|
55
|
+
@state()
|
|
56
|
+
private hasDescription = false;
|
|
57
|
+
|
|
58
|
+
private observer: MutationObserver | null = null;
|
|
59
|
+
|
|
60
|
+
override connectedCallback(): void {
|
|
61
|
+
super.connectedCallback();
|
|
62
|
+
|
|
63
|
+
// Generate unique ID for this field instance
|
|
64
|
+
this.fieldId = `field-${crypto.randomUUID().slice(0, 8)}`;
|
|
65
|
+
|
|
66
|
+
// Set up mutation observer to track child changes
|
|
67
|
+
this.observer = new MutationObserver(() => this.updateChildComponents());
|
|
68
|
+
this.observer.observe(this, { childList: true, subtree: true });
|
|
69
|
+
|
|
70
|
+
// Initial setup after first render
|
|
71
|
+
this.updateComplete.then(() => {
|
|
72
|
+
this.updateChildComponents();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
override disconnectedCallback(): void {
|
|
77
|
+
super.disconnectedCallback();
|
|
78
|
+
this.observer?.disconnect();
|
|
79
|
+
this.observer = null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Updates ARIA attributes on child components based on field structure.
|
|
84
|
+
*/
|
|
85
|
+
private updateChildComponents(): void {
|
|
86
|
+
// Find child components
|
|
87
|
+
const label = this.querySelector("ds-label");
|
|
88
|
+
const description = this.querySelector("ds-field-description");
|
|
89
|
+
const error = this.querySelector("ds-field-error");
|
|
90
|
+
const formControl = this.querySelector(
|
|
91
|
+
"ds-input, ds-textarea, ds-checkbox, ds-radio-group, ds-switch, ds-select, ds-combobox, ds-date-picker, ds-slider, ds-number-input, ds-file-upload, ds-time-picker, ds-pin-input, input, textarea, select"
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// Track which components are present
|
|
95
|
+
this.hasError = !!error && error.textContent?.trim() !== "";
|
|
96
|
+
this.hasDescription = !!description && description.textContent?.trim() !== "";
|
|
97
|
+
|
|
98
|
+
// Assign IDs to components
|
|
99
|
+
if (label) {
|
|
100
|
+
const labelId = `${this.fieldId}-label`;
|
|
101
|
+
label.id = labelId;
|
|
102
|
+
|
|
103
|
+
// Set data-required on label for styling
|
|
104
|
+
if (this.required) {
|
|
105
|
+
label.setAttribute("data-required", "");
|
|
106
|
+
} else {
|
|
107
|
+
label.removeAttribute("data-required");
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (description) {
|
|
112
|
+
description.id = `${this.fieldId}-desc`;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (error) {
|
|
116
|
+
error.id = `${this.fieldId}-error`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Compose ARIA attributes for form control
|
|
120
|
+
if (formControl) {
|
|
121
|
+
// Build aria-labelledby
|
|
122
|
+
if (label) {
|
|
123
|
+
formControl.setAttribute("aria-labelledby", label.id);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Build aria-describedby (error first, then description)
|
|
127
|
+
const describedByIds: string[] = [];
|
|
128
|
+
if (this.hasError && error) {
|
|
129
|
+
describedByIds.push(error.id);
|
|
130
|
+
}
|
|
131
|
+
if (this.hasDescription && description) {
|
|
132
|
+
describedByIds.push(description.id);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (describedByIds.length > 0) {
|
|
136
|
+
formControl.setAttribute("aria-describedby", describedByIds.join(" "));
|
|
137
|
+
} else {
|
|
138
|
+
formControl.removeAttribute("aria-describedby");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Set aria-invalid if error is present
|
|
142
|
+
if (this.hasError) {
|
|
143
|
+
formControl.setAttribute("aria-invalid", "true");
|
|
144
|
+
// Also set error property if it's a design system component
|
|
145
|
+
if ("error" in formControl) {
|
|
146
|
+
(formControl as { error: boolean }).error = true;
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
formControl.setAttribute("aria-invalid", "false");
|
|
150
|
+
if ("error" in formControl) {
|
|
151
|
+
(formControl as { error: boolean }).error = false;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Set aria-required if field is required
|
|
156
|
+
if (this.required) {
|
|
157
|
+
formControl.setAttribute("aria-required", "true");
|
|
158
|
+
if ("required" in formControl) {
|
|
159
|
+
(formControl as { required: boolean }).required = true;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Set disabled state
|
|
164
|
+
if (this.disabled) {
|
|
165
|
+
formControl.setAttribute("aria-disabled", "true");
|
|
166
|
+
if ("disabled" in formControl) {
|
|
167
|
+
(formControl as { disabled: boolean }).disabled = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Update data attributes on self for CSS styling
|
|
173
|
+
if (this.hasError) {
|
|
174
|
+
this.setAttribute("data-error", "");
|
|
175
|
+
} else {
|
|
176
|
+
this.removeAttribute("data-error");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (this.disabled) {
|
|
180
|
+
this.setAttribute("data-disabled", "");
|
|
181
|
+
} else {
|
|
182
|
+
this.removeAttribute("data-disabled");
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
override updated(changedProperties: Map<string, unknown>): void {
|
|
187
|
+
super.updated(changedProperties);
|
|
188
|
+
|
|
189
|
+
// Re-apply ARIA attributes when required or disabled changes
|
|
190
|
+
if (changedProperties.has("required") || changedProperties.has("disabled")) {
|
|
191
|
+
this.updateChildComponents();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
override render() {
|
|
196
|
+
return html`
|
|
197
|
+
<div class="ds-field" part="container">
|
|
198
|
+
<slot @slotchange=${this.updateChildComponents}></slot>
|
|
199
|
+
</div>
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
define("ds-field", DsField);
|
|
205
|
+
|
|
206
|
+
declare global {
|
|
207
|
+
interface HTMLElementTagNameMap {
|
|
208
|
+
"ds-field": DsField;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
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
|
+
/**
|
|
7
|
+
* Label component for form fields.
|
|
8
|
+
*
|
|
9
|
+
* Used within ds-field to provide an accessible label that is
|
|
10
|
+
* automatically associated with the form control via aria-labelledby.
|
|
11
|
+
*
|
|
12
|
+
* @element ds-label
|
|
13
|
+
*
|
|
14
|
+
* @slot - Label text content
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```html
|
|
18
|
+
* <ds-field>
|
|
19
|
+
* <ds-label>Email address</ds-label>
|
|
20
|
+
* <ds-input type="email"></ds-input>
|
|
21
|
+
* </ds-field>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export class DsLabel extends DSElement {
|
|
25
|
+
/**
|
|
26
|
+
* ID of the associated form control.
|
|
27
|
+
* Typically set automatically by the parent ds-field component.
|
|
28
|
+
*/
|
|
29
|
+
@property({ type: String, attribute: "for" })
|
|
30
|
+
for = "";
|
|
31
|
+
|
|
32
|
+
override render() {
|
|
33
|
+
return html`
|
|
34
|
+
<label
|
|
35
|
+
class="ds-label"
|
|
36
|
+
part="label"
|
|
37
|
+
for=${this.for || nothing}
|
|
38
|
+
>
|
|
39
|
+
<slot></slot>
|
|
40
|
+
</label>
|
|
41
|
+
`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Import nothing for conditional attributes
|
|
46
|
+
import { nothing } from "lit";
|
|
47
|
+
|
|
48
|
+
define("ds-label", DsLabel);
|
|
49
|
+
|
|
50
|
+
declare global {
|
|
51
|
+
interface HTMLElementTagNameMap {
|
|
52
|
+
"ds-label": DsLabel;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileUpload context for compound component pattern.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { FileInfo, FileUploadBehavior } from "@hypoth-ui/primitives-dom";
|
|
6
|
+
import { createCompoundContext } from "../../utils/create-context.js";
|
|
7
|
+
|
|
8
|
+
export interface FileUploadContextValue {
|
|
9
|
+
/** FileUpload behavior instance */
|
|
10
|
+
behavior: FileUploadBehavior;
|
|
11
|
+
/** Current files */
|
|
12
|
+
files: FileInfo[];
|
|
13
|
+
/** Is dragging over dropzone */
|
|
14
|
+
isDragging: boolean;
|
|
15
|
+
/** Set dragging state */
|
|
16
|
+
setIsDragging: (dragging: boolean) => void;
|
|
17
|
+
/** Input ref for triggering file dialog */
|
|
18
|
+
inputRef: React.RefObject<HTMLInputElement>;
|
|
19
|
+
/** Open file dialog */
|
|
20
|
+
openFileDialog: () => void;
|
|
21
|
+
/** Disabled state */
|
|
22
|
+
disabled: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const [FileUploadProvider, useFileUploadContext] =
|
|
26
|
+
createCompoundContext<FileUploadContextValue>("FileUpload");
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileUpload Dropzone component - the drop target area.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
type DragEvent,
|
|
7
|
+
type HTMLAttributes,
|
|
8
|
+
type KeyboardEvent,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
forwardRef,
|
|
11
|
+
useCallback,
|
|
12
|
+
} from "react";
|
|
13
|
+
import { useFileUploadContext } from "./file-upload-context.js";
|
|
14
|
+
|
|
15
|
+
export interface FileUploadDropzoneProps extends HTMLAttributes<HTMLDivElement> {
|
|
16
|
+
/** Dropzone content */
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The dropzone area for drag-and-drop file uploads.
|
|
22
|
+
* Also handles click-to-upload functionality.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* <FileUpload.Dropzone className="dropzone">
|
|
27
|
+
* <p>Drop files here or click to upload</p>
|
|
28
|
+
* </FileUpload.Dropzone>
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export const FileUploadDropzone = forwardRef<HTMLDivElement, FileUploadDropzoneProps>(
|
|
32
|
+
({ children, className, onClick, onKeyDown, ...restProps }, ref) => {
|
|
33
|
+
const { behavior, isDragging, setIsDragging, openFileDialog, disabled } =
|
|
34
|
+
useFileUploadContext("FileUpload.Dropzone");
|
|
35
|
+
|
|
36
|
+
const dropzoneProps = behavior.getDropzoneProps();
|
|
37
|
+
|
|
38
|
+
const handleClick = useCallback(
|
|
39
|
+
(event: React.MouseEvent<HTMLDivElement>) => {
|
|
40
|
+
openFileDialog();
|
|
41
|
+
onClick?.(event);
|
|
42
|
+
},
|
|
43
|
+
[openFileDialog, onClick]
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
const handleKeyDown = useCallback(
|
|
47
|
+
(event: KeyboardEvent<HTMLDivElement>) => {
|
|
48
|
+
if (behavior.handleKeyDown(event.nativeEvent)) {
|
|
49
|
+
openFileDialog();
|
|
50
|
+
}
|
|
51
|
+
onKeyDown?.(event);
|
|
52
|
+
},
|
|
53
|
+
[behavior, openFileDialog, onKeyDown]
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const handleDragEnter = useCallback(
|
|
57
|
+
(event: DragEvent<HTMLDivElement>) => {
|
|
58
|
+
behavior.handleDragEnter(event.nativeEvent);
|
|
59
|
+
setIsDragging(behavior.state.isDragging);
|
|
60
|
+
},
|
|
61
|
+
[behavior, setIsDragging]
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const handleDragLeave = useCallback(
|
|
65
|
+
(event: DragEvent<HTMLDivElement>) => {
|
|
66
|
+
behavior.handleDragLeave(event.nativeEvent);
|
|
67
|
+
setIsDragging(behavior.state.isDragging);
|
|
68
|
+
},
|
|
69
|
+
[behavior, setIsDragging]
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
const handleDragOver = useCallback(
|
|
73
|
+
(event: DragEvent<HTMLDivElement>) => {
|
|
74
|
+
behavior.handleDragOver(event.nativeEvent);
|
|
75
|
+
},
|
|
76
|
+
[behavior]
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const handleDrop = useCallback(
|
|
80
|
+
(event: DragEvent<HTMLDivElement>) => {
|
|
81
|
+
behavior.handleDrop(event.nativeEvent);
|
|
82
|
+
setIsDragging(false);
|
|
83
|
+
},
|
|
84
|
+
[behavior, setIsDragging]
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
ref={ref}
|
|
90
|
+
role={dropzoneProps.role}
|
|
91
|
+
tabIndex={dropzoneProps.tabIndex}
|
|
92
|
+
aria-label={dropzoneProps["aria-label"]}
|
|
93
|
+
aria-disabled={dropzoneProps["aria-disabled"]}
|
|
94
|
+
className={className}
|
|
95
|
+
data-dragging={isDragging || undefined}
|
|
96
|
+
data-disabled={disabled || undefined}
|
|
97
|
+
onClick={handleClick}
|
|
98
|
+
onKeyDown={handleKeyDown}
|
|
99
|
+
onDragEnter={handleDragEnter}
|
|
100
|
+
onDragLeave={handleDragLeave}
|
|
101
|
+
onDragOver={handleDragOver}
|
|
102
|
+
onDrop={handleDrop}
|
|
103
|
+
{...restProps}
|
|
104
|
+
>
|
|
105
|
+
{children}
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
FileUploadDropzone.displayName = "FileUpload.Dropzone";
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FileUpload Input component - hidden file input element.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { type ChangeEvent, type InputHTMLAttributes, forwardRef, useCallback } from "react";
|
|
6
|
+
import { useFileUploadContext } from "./file-upload-context.js";
|
|
7
|
+
|
|
8
|
+
export interface FileUploadInputProps
|
|
9
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "onChange"> {}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Hidden file input element that handles file selection.
|
|
13
|
+
* Must be rendered within FileUpload.Root.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* <FileUpload.Root>
|
|
18
|
+
* <FileUpload.Input />
|
|
19
|
+
* <FileUpload.Dropzone>...</FileUpload.Dropzone>
|
|
20
|
+
* </FileUpload.Root>
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export const FileUploadInput = forwardRef<HTMLInputElement, FileUploadInputProps>(
|
|
24
|
+
({ className, style, ...restProps }, ref) => {
|
|
25
|
+
const { behavior, inputRef, disabled } = useFileUploadContext("FileUpload.Input");
|
|
26
|
+
|
|
27
|
+
const inputProps = behavior.getInputProps();
|
|
28
|
+
|
|
29
|
+
const handleChange = useCallback(
|
|
30
|
+
(event: ChangeEvent<HTMLInputElement>) => {
|
|
31
|
+
const input = event.target;
|
|
32
|
+
if (input.files && input.files.length > 0) {
|
|
33
|
+
behavior.addFiles(input.files);
|
|
34
|
+
// Reset input so the same file can be selected again
|
|
35
|
+
input.value = "";
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
[behavior]
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Merge refs
|
|
42
|
+
const mergedRef = useCallback(
|
|
43
|
+
(element: HTMLInputElement | null) => {
|
|
44
|
+
(inputRef as React.MutableRefObject<HTMLInputElement | null>).current = element;
|
|
45
|
+
if (typeof ref === "function") {
|
|
46
|
+
ref(element);
|
|
47
|
+
} else if (ref) {
|
|
48
|
+
(ref as React.MutableRefObject<HTMLInputElement | null>).current = element;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
[inputRef, ref]
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Default hidden styles
|
|
55
|
+
const hiddenStyles: React.CSSProperties = {
|
|
56
|
+
position: "absolute",
|
|
57
|
+
width: 1,
|
|
58
|
+
height: 1,
|
|
59
|
+
padding: 0,
|
|
60
|
+
margin: -1,
|
|
61
|
+
overflow: "hidden",
|
|
62
|
+
clip: "rect(0, 0, 0, 0)",
|
|
63
|
+
whiteSpace: "nowrap",
|
|
64
|
+
border: 0,
|
|
65
|
+
...style,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<input
|
|
70
|
+
ref={mergedRef}
|
|
71
|
+
type={inputProps.type}
|
|
72
|
+
accept={inputProps.accept}
|
|
73
|
+
multiple={inputProps.multiple}
|
|
74
|
+
disabled={disabled || inputProps.disabled}
|
|
75
|
+
aria-hidden={inputProps["aria-hidden"]}
|
|
76
|
+
tabIndex={inputProps.tabIndex}
|
|
77
|
+
className={className}
|
|
78
|
+
style={hiddenStyles}
|
|
79
|
+
onChange={handleChange}
|
|
80
|
+
{...restProps}
|
|
81
|
+
/>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
FileUploadInput.displayName = "FileUpload.Input";
|