@flikk/ui 1.0.0-beta.23 → 1.0.0-beta.24
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/dist/components/charts/AreaChart/AreaChart.types.d.ts +2 -2
- package/dist/components/charts/BarChart/BarChart.types.d.ts +2 -2
- package/dist/components/charts/ComboChart/ComboChart.types.d.ts +2 -2
- package/dist/components/charts/LineChart/LineChart.types.d.ts +2 -2
- package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +2 -2
- package/dist/components/charts/StackedBarChart/StackedBarChart.types.d.ts +2 -2
- package/dist/components/core/Accordion/AccordionTrigger.js +28 -1
- package/dist/components/core/Alert/Alert.js +2 -1
- package/dist/components/core/AlertDialog/AlertDialog.js +1 -1
- package/dist/components/core/Badge/Badge.js +6 -17
- package/dist/components/core/Breadcrumbs/Breadcrumbs.js +17 -28
- package/dist/components/core/Calendar/Calendar.js +1 -1
- package/dist/components/core/Carousel/CarouselBody.js +6 -1
- package/dist/components/core/ContextMenu/ContextMenuBody.js +6 -3
- package/dist/components/core/ContextMenu/ContextMenuSubBody.js +6 -3
- package/dist/components/core/DragDrop/DragDrop.js +170 -17
- package/dist/components/core/DragDrop/DragDrop.types.d.ts +6 -0
- package/dist/components/core/Drawer/Drawer.d.ts +1 -1
- package/dist/components/core/Drawer/Drawer.js +2 -0
- package/dist/components/core/Drawer/Drawer.types.d.ts +2 -0
- package/dist/components/core/Drawer/DrawerTitle.d.ts +1 -1
- package/dist/components/core/Drawer/DrawerTitle.js +2 -2
- package/dist/components/core/Dropdown/DropdownMenu.js +2 -30
- package/dist/components/core/Masonry/Masonry.js +7 -2
- package/dist/components/core/Modal/Modal.d.ts +1 -1
- package/dist/components/core/Modal/Modal.js +3 -3
- package/dist/components/core/Modal/Modal.types.d.ts +2 -0
- package/dist/components/core/Modal/ModalTitle.d.ts +1 -1
- package/dist/components/core/Modal/ModalTitle.js +2 -2
- package/dist/components/core/NavItem/NavItem.js +13 -8
- package/dist/components/core/Pagination/Pagination.js +1 -1
- package/dist/components/core/Popover/PopoverBody.js +3 -27
- package/dist/components/core/Popover/PopoverTrigger.js +1 -1
- package/dist/components/core/Progress/Progress.js +4 -4
- package/dist/components/core/Rating/Rating.js +1 -1
- package/dist/components/core/ScrollArea/ScrollArea.js +3 -2
- package/dist/components/core/Segmented/Segmented.js +6 -6
- package/dist/components/core/Segmented/SegmentedItem.js +28 -2
- package/dist/components/core/Skeleton/Skeleton.js +2 -2
- package/dist/components/core/Sortable/Sortable.d.ts +1 -1
- package/dist/components/core/Sortable/Sortable.js +20 -4
- package/dist/components/core/Sortable/Sortable.types.d.ts +2 -0
- package/dist/components/core/Tabs/Tabs.js +6 -4
- package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
- package/dist/components/core/Tabs/TabsBody.js +3 -3
- package/dist/components/core/Tabs/TabsList.js +19 -21
- package/dist/components/core/Tabs/TabsTrigger.js +47 -28
- package/dist/components/core/Toast/Toast.js +1 -1
- package/dist/components/core/Toast/ToastProvider.js +3 -2
- package/dist/components/core/Tooltip/Tooltip.js +25 -34
- package/dist/components/core/Tree/Tree.js +178 -8
- package/dist/components/data-display/GanttChart/GanttSplitter.js +15 -1
- package/dist/components/data-display/Table/Table.js +2 -2
- package/dist/components/data-display/Table/Table.types.d.ts +2 -0
- package/dist/components/data-display/Table/TableFilter.js +21 -18
- package/dist/components/effects/CustomCursor/CustomCursor.js +3 -7
- package/dist/components/effects/Overlay/Overlay.js +4 -9
- package/dist/components/effects/PageTransition/PageTransition.js +17 -6
- package/dist/components/effects/SpotlightBorder/SpotlightBorder.js +3 -4
- package/dist/components/forms/Checkbox/Checkbox.js +11 -1
- package/dist/components/forms/Checkbox/Checkbox.theme.js +4 -1
- package/dist/components/forms/Checkbox/Checkbox.types.d.ts +7 -5
- package/dist/components/forms/Checkbox/CheckboxGroup.js +9 -4
- package/dist/components/forms/ColorPicker/ColorPicker.js +7 -4
- package/dist/components/forms/ColorPicker/ColorPicker.types.d.ts +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerBody.js +5 -3
- package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +3 -3
- package/dist/components/forms/ColorPicker/ColorPickerGradient.js +8 -8
- package/dist/components/forms/ColorPicker/ColorPickerInput.js +1 -1
- package/dist/components/forms/ColorPicker/index.d.ts +1 -1
- package/dist/components/forms/Combobox/Combobox.js +20 -45
- package/dist/components/forms/Combobox/Combobox.types.d.ts +4 -2
- package/dist/components/forms/CronInput/CronInput.js +10 -4
- package/dist/components/forms/CronInput/CronInput.types.d.ts +12 -1
- package/dist/components/forms/DatePicker/DatePicker.js +2 -2
- package/dist/components/forms/DatePicker/DatePicker.types.d.ts +4 -2
- package/dist/components/forms/DatePicker/DatePickerBody.js +1 -29
- package/dist/components/forms/DatePicker/DatePickerTrigger.js +2 -2
- package/dist/components/forms/DateRangePicker/DateRangePicker.js +3 -3
- package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +4 -2
- package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +1 -28
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +2 -2
- package/dist/components/forms/FileUpload/FileUpload.js +14 -10
- package/dist/components/forms/FileUpload/FileUpload.types.d.ts +15 -0
- package/dist/components/forms/FormLabel/FormLabel.js +2 -2
- package/dist/components/forms/Input/Input.js +5 -3
- package/dist/components/forms/Input/Input.types.d.ts +2 -0
- package/dist/components/forms/InputAddress/InputAddress.d.ts +1 -1
- package/dist/components/forms/InputAddress/InputAddress.js +8 -9
- package/dist/components/forms/InputCounter/InputCounter.js +25 -18
- package/dist/components/forms/InputCounter/InputCounter.types.d.ts +8 -1
- package/dist/components/forms/InputCreditCard/InputCreditCard.d.ts +1 -1
- package/dist/components/forms/InputCreditCard/InputCreditCard.js +27 -9
- package/dist/components/forms/InputCreditCard/InputCreditCard.types.d.ts +5 -0
- package/dist/components/forms/InputOTP/InputOTP.js +26 -17
- package/dist/components/forms/InputOTP/InputOTP.types.d.ts +21 -4
- package/dist/components/forms/InputTag/InputTag.js +12 -39
- package/dist/components/forms/InputTag/InputTag.types.d.ts +2 -2
- package/dist/components/forms/Mention/Mention.js +13 -6
- package/dist/components/forms/Mention/Mention.types.d.ts +9 -0
- package/dist/components/forms/Radio/Radio.js +12 -2
- package/dist/components/forms/Radio/Radio.theme.js +5 -1
- package/dist/components/forms/Radio/Radio.types.d.ts +7 -5
- package/dist/components/forms/Radio/RadioGroup.js +8 -3
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +21 -21
- package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +2 -2
- package/dist/components/forms/Select/Select.d.ts +2 -0
- package/dist/components/forms/Select/Select.js +26 -56
- package/dist/components/forms/Select/Select.types.d.ts +14 -0
- package/dist/components/forms/Signature/Signature.js +1 -1
- package/dist/components/forms/Signature/Signature.types.d.ts +3 -3
- package/dist/components/forms/Slider/Slider.js +18 -4
- package/dist/components/forms/Slider/Slider.types.d.ts +2 -2
- package/dist/components/forms/Switch/Switch.js +12 -19
- package/dist/components/forms/Switch/Switch.theme.js +12 -2
- package/dist/components/forms/Switch/Switch.types.d.ts +20 -2
- package/dist/components/forms/Textarea/Textarea.js +8 -3
- package/dist/components/forms/Textarea/Textarea.types.d.ts +2 -0
- package/dist/components/forms/TimePicker/TimePicker.js +1 -1
- package/dist/components/forms/TimePicker/TimePicker.types.d.ts +6 -2
- package/dist/components/forms/TimePicker/TimePickerContent.js +2 -29
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +2 -2
- package/dist/components/forms/index.js +1 -1
- package/dist/components/layout/index.js +4 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useClickOutside.d.ts +10 -4
- package/dist/hooks/useClickOutside.js +44 -24
- package/dist/hooks/useDarkMode.d.ts +1 -0
- package/dist/hooks/useDarkMode.js +42 -0
- package/dist/hooks/useFormStateMachine.d.ts +1 -1
- package/dist/hooks/useNetworkStatus.js +1 -1
- package/dist/hooks/useOverlay.js +4 -0
- package/dist/hooks/useScaleBackground.js +5 -2
- package/dist/hooks/useSelectPortal.js +14 -5
- package/dist/index.d.ts +0 -2
- package/dist/index.js +1 -57
- package/dist/shadcn-compat.css +80 -0
- package/dist/styles.css +1 -1
- package/dist/utils/stateMachine.d.ts +4 -4
- package/package.json +11 -10
- package/tailwind.preset.cjs +32 -3
- /package/{src/theme-plugin.css → dist/theme.css} +0 -0
|
@@ -416,6 +416,12 @@ const RichTextEditor = React__default.forwardRef(({ size = "md", state = "defaul
|
|
|
416
416
|
setShowColorPicker(false);
|
|
417
417
|
setShowFontSizePicker(false);
|
|
418
418
|
}, [execEditorCommand]);
|
|
419
|
+
const handleMouseDown = useCallback((event) => {
|
|
420
|
+
event.preventDefault();
|
|
421
|
+
focusEditor();
|
|
422
|
+
}, [focusEditor]);
|
|
423
|
+
const fontSizeAnimations = useMemo(() => createDropdownAnimations(fontSizeIsFlipped), [fontSizeIsFlipped]);
|
|
424
|
+
const colorAnimations = useMemo(() => createDropdownAnimations(colorIsFlipped), [colorIsFlipped]);
|
|
419
425
|
const handleAlign = useCallback((alignment) => {
|
|
420
426
|
const commandMap = {
|
|
421
427
|
left: "justifyLeft",
|
|
@@ -525,7 +531,7 @@ const RichTextEditor = React__default.forwardRef(({ size = "md", state = "defaul
|
|
|
525
531
|
updateFormattingState();
|
|
526
532
|
}
|
|
527
533
|
}, [updateFormattingState, value, sanitizeHtml]);
|
|
528
|
-
const renderToolbarTool = (tool,
|
|
534
|
+
const renderToolbarTool = (tool, toolKey) => {
|
|
529
535
|
var _a, _b;
|
|
530
536
|
const isActive = tool === "bold"
|
|
531
537
|
? formattingState.bold
|
|
@@ -543,21 +549,16 @@ const RichTextEditor = React__default.forwardRef(({ size = "md", state = "defaul
|
|
|
543
549
|
? formattingState.textAlign === "right"
|
|
544
550
|
: false;
|
|
545
551
|
const isFloatingToolbar = toolbarConfig.position === "floating";
|
|
546
|
-
const handleMouseDown = (event) => {
|
|
547
|
-
event.preventDefault();
|
|
548
|
-
focusEditor();
|
|
549
|
-
};
|
|
550
552
|
switch (tool) {
|
|
551
553
|
case "bold":
|
|
552
|
-
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", "data-pressed": isActive ? "true" : "false", onMouseDown: handleMouseDown, onClick: handleBold, disabled: isDisabled, "aria-label": "Bold", "aria-pressed": isActive ? "true" : "false", children: jsx(BoldIcon, { className: "w-4 h-4" }) },
|
|
554
|
+
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", "data-pressed": isActive ? "true" : "false", onMouseDown: handleMouseDown, onClick: handleBold, disabled: isDisabled, "aria-label": "Bold", "aria-pressed": isActive ? "true" : "false", children: jsx(BoldIcon, { className: "w-4 h-4" }) }, toolKey));
|
|
553
555
|
case "italic":
|
|
554
|
-
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", "data-pressed": isActive ? "true" : "false", onMouseDown: handleMouseDown, onClick: handleItalic, disabled: isDisabled, "aria-label": "Italic", "aria-pressed": isActive ? "true" : "false", children: jsx(ItalicIcon, { className: "w-4 h-4" }) },
|
|
556
|
+
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", "data-pressed": isActive ? "true" : "false", onMouseDown: handleMouseDown, onClick: handleItalic, disabled: isDisabled, "aria-label": "Italic", "aria-pressed": isActive ? "true" : "false", children: jsx(ItalicIcon, { className: "w-4 h-4" }) }, toolKey));
|
|
555
557
|
case "underline":
|
|
556
|
-
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", "data-pressed": isActive ? "true" : "false", onMouseDown: handleMouseDown, onClick: handleUnderline, disabled: isDisabled, "aria-label": "Underline", "aria-pressed": isActive ? "true" : "false", children: jsx(UnderlineIcon, { className: "w-4 h-4" }) },
|
|
558
|
+
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", "data-pressed": isActive ? "true" : "false", onMouseDown: handleMouseDown, onClick: handleUnderline, disabled: isDisabled, "aria-label": "Underline", "aria-pressed": isActive ? "true" : "false", children: jsx(UnderlineIcon, { className: "w-4 h-4" }) }, toolKey));
|
|
557
559
|
case "fontSize": {
|
|
558
560
|
const currentOption = getFontSizeOption(formattingState.fontSize);
|
|
559
561
|
const dropdownClassName = themeStyles.dropdownStyle;
|
|
560
|
-
const fontSizeAnimations = createDropdownAnimations(fontSizeIsFlipped);
|
|
561
562
|
const dropdownList = (jsx("div", { className: dropdownClassName, role: "listbox", children: FONT_SIZE_OPTIONS.map((option) => (jsx("button", { className: cn(themeStyles.dropdownItemStyle, option.key === formattingState.fontSize
|
|
562
563
|
? "bg-[var(--color-primary-50)] text-[var(--color-primary-900)]"
|
|
563
564
|
: undefined), "data-active": option.key === formattingState.fontSize ? "true" : "false", onMouseDown: (event) => {
|
|
@@ -599,12 +600,11 @@ const RichTextEditor = React__default.forwardRef(({ size = "md", state = "defaul
|
|
|
599
600
|
focusEditor();
|
|
600
601
|
setShowFontSizePicker((previous) => !previous);
|
|
601
602
|
setShowColorPicker(false);
|
|
602
|
-
}, disabled: isDisabled, "aria-label": "Font size", "aria-expanded": showFontSizePicker, "aria-haspopup": "listbox", children: jsx("span", { className: "text-sm font-medium tracking-wide uppercase", children: currentOption.label }) }), toolbarConfig.position === "floating" ? null : renderedDropdown, toolbarConfig.position === "floating" ? renderedDropdown : null] },
|
|
603
|
+
}, disabled: isDisabled, "aria-label": "Font size", "aria-expanded": showFontSizePicker, "aria-haspopup": "listbox", children: jsx("span", { className: "text-sm font-medium tracking-wide uppercase", children: currentOption.label }) }), toolbarConfig.position === "floating" ? null : renderedDropdown, toolbarConfig.position === "floating" ? renderedDropdown : null] }, toolKey));
|
|
603
604
|
}
|
|
604
605
|
case "textColor":
|
|
605
606
|
{
|
|
606
607
|
const colorDropdownClassName = cn(themeStyles.dropdownStyle, themeStyles.colorPickerStyle);
|
|
607
|
-
const colorAnimations = createDropdownAnimations(colorIsFlipped);
|
|
608
608
|
const colorDropdownList = (jsx("div", { className: cn(colorDropdownClassName, "p-2"), children: colorOptions.map((color) => (jsx("button", { className: themeStyles.colorSwatchStyle, style: { backgroundColor: color.value }, onMouseDown: (event) => {
|
|
609
609
|
event.preventDefault();
|
|
610
610
|
focusEditor();
|
|
@@ -644,32 +644,32 @@ const RichTextEditor = React__default.forwardRef(({ size = "md", state = "defaul
|
|
|
644
644
|
focusEditor();
|
|
645
645
|
setShowFontSizePicker(false);
|
|
646
646
|
setShowColorPicker((previous) => !previous);
|
|
647
|
-
}, disabled: isDisabled, "aria-label": "Text Color", "aria-expanded": showColorPicker, children: jsx(PaintBrushIcon, { className: "w-4 h-4" }) }), isFloatingToolbar ? null : renderedColorDropdown, isFloatingToolbar ? renderedColorDropdown : null] },
|
|
647
|
+
}, disabled: isDisabled, "aria-label": "Text Color", "aria-expanded": showColorPicker, children: jsx(PaintBrushIcon, { className: "w-4 h-4" }) }), isFloatingToolbar ? null : renderedColorDropdown, isFloatingToolbar ? renderedColorDropdown : null] }, toolKey));
|
|
648
648
|
}
|
|
649
649
|
case "alignLeft":
|
|
650
650
|
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", "data-pressed": isActive ? "true" : "false", onMouseDown: handleMouseDown, onClick: (event) => {
|
|
651
651
|
event.preventDefault();
|
|
652
652
|
event.stopPropagation();
|
|
653
653
|
handleAlign("left");
|
|
654
|
-
}, disabled: isDisabled, "aria-label": "Align Left", "aria-pressed": isActive ? "true" : "false", children: jsx(Bars3BottomLeftIcon, { className: "w-4 h-4" }) },
|
|
654
|
+
}, disabled: isDisabled, "aria-label": "Align Left", "aria-pressed": isActive ? "true" : "false", children: jsx(Bars3BottomLeftIcon, { className: "w-4 h-4" }) }, toolKey));
|
|
655
655
|
case "alignCenter":
|
|
656
656
|
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", "data-pressed": isActive ? "true" : "false", onMouseDown: handleMouseDown, onClick: (event) => {
|
|
657
657
|
event.preventDefault();
|
|
658
658
|
event.stopPropagation();
|
|
659
659
|
handleAlign("center");
|
|
660
|
-
}, disabled: isDisabled, "aria-label": "Align Center", "aria-pressed": isActive ? "true" : "false", children: jsx(Bars3Icon, { className: "w-4 h-4" }) },
|
|
660
|
+
}, disabled: isDisabled, "aria-label": "Align Center", "aria-pressed": isActive ? "true" : "false", children: jsx(Bars3Icon, { className: "w-4 h-4" }) }, toolKey));
|
|
661
661
|
case "alignRight":
|
|
662
662
|
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), "data-state": isActive ? "active" : "inactive", "data-pressed": isActive ? "true" : "false", onMouseDown: handleMouseDown, onClick: (event) => {
|
|
663
663
|
event.preventDefault();
|
|
664
664
|
event.stopPropagation();
|
|
665
665
|
handleAlign("right");
|
|
666
|
-
}, disabled: isDisabled, "aria-label": "Align Right", "aria-pressed": isActive ? "true" : "false", children: jsx(Bars3BottomRightIcon, { className: "w-4 h-4" }) },
|
|
666
|
+
}, disabled: isDisabled, "aria-label": "Align Right", "aria-pressed": isActive ? "true" : "false", children: jsx(Bars3BottomRightIcon, { className: "w-4 h-4" }) }, toolKey));
|
|
667
667
|
case "undo":
|
|
668
|
-
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), onMouseDown: handleMouseDown, onClick: handleUndo, disabled: isDisabled, "aria-label": "Undo", children: jsx(ArrowUturnLeftIcon, { className: "w-4 h-4" }) },
|
|
668
|
+
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), onMouseDown: handleMouseDown, onClick: handleUndo, disabled: isDisabled, "aria-label": "Undo", children: jsx(ArrowUturnLeftIcon, { className: "w-4 h-4" }) }, toolKey));
|
|
669
669
|
case "redo":
|
|
670
|
-
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), onMouseDown: handleMouseDown, onClick: handleRedo, disabled: isDisabled, "aria-label": "Redo", children: jsx(ArrowUturnRightIcon, { className: "w-4 h-4" }) },
|
|
670
|
+
return (jsx(Button, { size: "sm", color: "neutral", variant: "soft", iconOnly: true, className: cn(themeStyles.toolbarButtonStyle), onMouseDown: handleMouseDown, onClick: handleRedo, disabled: isDisabled, "aria-label": "Redo", children: jsx(ArrowUturnRightIcon, { className: "w-4 h-4" }) }, toolKey));
|
|
671
671
|
case "separator":
|
|
672
|
-
return jsx("div", { className: themeStyles.separatorStyle },
|
|
672
|
+
return jsx("div", { className: themeStyles.separatorStyle }, toolKey);
|
|
673
673
|
default:
|
|
674
674
|
return null;
|
|
675
675
|
}
|
|
@@ -678,13 +678,13 @@ const RichTextEditor = React__default.forwardRef(({ size = "md", state = "defaul
|
|
|
678
678
|
if (!toolbarConfig.show || !toolbarConfig.tools)
|
|
679
679
|
return null;
|
|
680
680
|
const toolbarClass = cn(themeStyles.toolbarStyle, toolbarConfig.className);
|
|
681
|
-
const toolbarContent = (jsx("div", { ref: toolbarRef, className: toolbarClass, children: toolbarConfig.tools.map((tool
|
|
681
|
+
const toolbarContent = (jsx("div", { ref: toolbarRef, className: toolbarClass, children: toolbarConfig.tools.map((tool) => renderToolbarTool(tool, tool)) }));
|
|
682
682
|
if (toolbarConfig.position === "floating") {
|
|
683
683
|
return (jsx(AnimatePresence, { mode: "wait", children: showToolbar && (jsx(motion.div, { variants: shouldReduceMotion ? undefined : floatingToolbarVariants, initial: "initial", animate: "animate", exit: "exit", className: "bg-white/70 backdrop-blur-sm shadow-2xl rounded-xl border border-[var(--color-border)] overflow-hidden", style: { position: "absolute", top: toolbarPosition.top, left: toolbarPosition.left, zIndex: 50 }, children: toolbarContent }, "floating-toolbar")) }));
|
|
684
684
|
}
|
|
685
685
|
return toolbarContent;
|
|
686
686
|
};
|
|
687
|
-
return (jsxs("div", { className: cn(themeStyles.wrapperStyle, className), ...props, children: [label && (jsx("div", { className: "mb-1", children: jsx(FormLabel, { htmlFor: editorId, className: labelClassName, state: state, required: required, children: label }) })), jsxs("div", { className: "relative", children: [jsxs("div", { className: cn(themeStyles.containerStyle, themeStyles.states[state], themeStyles.sizes[size]), "data-state": getDataState(), children: [toolbarConfig.position === "top" && renderToolbar(), jsx("div", { ref: (el) => {
|
|
687
|
+
return (jsxs("div", { className: cn(themeStyles.wrapperStyle, className), ...props, children: [label && (typeof label === 'string' ? (jsx("div", { className: "mb-1", children: jsx(FormLabel, { htmlFor: editorId, className: labelClassName, state: state, required: required, children: label }) })) : (label)), jsxs("div", { className: "relative", children: [jsxs("div", { className: cn(themeStyles.containerStyle, themeStyles.states[state], themeStyles.sizes[size]), "data-state": getDataState(), children: [toolbarConfig.position === "top" && renderToolbar(), jsx("div", { ref: (el) => {
|
|
688
688
|
if (el) {
|
|
689
689
|
editorRef.current = el;
|
|
690
690
|
if (typeof ref === "function") {
|
|
@@ -704,7 +704,7 @@ const RichTextEditor = React__default.forwardRef(({ size = "md", state = "defaul
|
|
|
704
704
|
}
|
|
705
705
|
setShowColorPicker(false);
|
|
706
706
|
setShowFontSizePicker(false);
|
|
707
|
-
}, "aria-invalid": isInvalid, "aria-required": required, role: "textbox", "aria-multiline": "true", "aria-label": label
|
|
707
|
+
}, "aria-invalid": isInvalid, "aria-required": required, role: "textbox", "aria-multiline": "true", "aria-label": typeof label === 'string' ? label : "Rich text editor", suppressContentEditableWarning: true })] }), toolbarConfig.position === "floating" && renderToolbar()] }), helperText && (typeof helperText === 'string' ? (jsx("div", { className: cn(themeStyles.helperText, helperTextClassName), children: helperText })) : (helperText))] }));
|
|
708
708
|
});
|
|
709
709
|
RichTextEditor.displayName = "RichTextEditor";
|
|
710
710
|
|
|
@@ -19,9 +19,9 @@ export interface RichTextEditorProps extends Omit<React.HTMLAttributes<HTMLDivEl
|
|
|
19
19
|
/** The state of the editor (default, disabled, invalid) */
|
|
20
20
|
state?: RichTextEditorState;
|
|
21
21
|
/** Optional label text for the editor */
|
|
22
|
-
label?:
|
|
22
|
+
label?: React.ReactNode;
|
|
23
23
|
/** Optional helper text displayed below the editor */
|
|
24
|
-
helperText?:
|
|
24
|
+
helperText?: React.ReactNode;
|
|
25
25
|
/** The HTML content value */
|
|
26
26
|
value?: string;
|
|
27
27
|
/** Callback when content changes */
|
|
@@ -79,6 +79,8 @@ export declare const SelectButton: React.FC<SelectButtonProps>;
|
|
|
79
79
|
export interface SelectOptionsExtendedProps extends SelectOptionsProps {
|
|
80
80
|
/** Custom trigger ref - if provided, uses this instead of buttonRef from context */
|
|
81
81
|
triggerRef?: React.RefObject<HTMLElement>;
|
|
82
|
+
/** id applied to the listbox element for aria-controls matching */
|
|
83
|
+
listboxId?: string;
|
|
82
84
|
}
|
|
83
85
|
/**
|
|
84
86
|
* Select Options Component
|
|
@@ -13,40 +13,9 @@ import { cn } from '../../../utils/cn.js';
|
|
|
13
13
|
import { isValidTypeaheadChar, findNextMatchingOption, TYPEAHEAD_TIMEOUT_MS } from '../../../utils/typeaheadUtils.js';
|
|
14
14
|
import { Tag } from '../../core/Tag/Tag.js';
|
|
15
15
|
import { useIsClient } from '../../../hooks/useIsClient.js';
|
|
16
|
+
import { useDarkMode } from '../../../hooks/useDarkMode.js';
|
|
16
17
|
import { useSelectState } from './useSelectState.js';
|
|
17
18
|
|
|
18
|
-
// ============================================================================
|
|
19
|
-
// Dark Mode Hook
|
|
20
|
-
// ============================================================================
|
|
21
|
-
/**
|
|
22
|
-
* Hook to detect dark mode state from document
|
|
23
|
-
*/
|
|
24
|
-
const useDarkMode = () => {
|
|
25
|
-
const [isDark, setIsDark] = React__default.useState(false);
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
if (typeof document === "undefined")
|
|
28
|
-
return;
|
|
29
|
-
const checkDarkMode = () => {
|
|
30
|
-
const hasDarkClass = document.documentElement.classList.contains("dark") ||
|
|
31
|
-
document.body.classList.contains("dark") ||
|
|
32
|
-
document.querySelector(".dark") !== null;
|
|
33
|
-
setIsDark(hasDarkClass);
|
|
34
|
-
};
|
|
35
|
-
checkDarkMode();
|
|
36
|
-
// Observe class changes on html and body
|
|
37
|
-
const observer = new MutationObserver(checkDarkMode);
|
|
38
|
-
observer.observe(document.documentElement, {
|
|
39
|
-
attributes: true,
|
|
40
|
-
attributeFilter: ["class"],
|
|
41
|
-
});
|
|
42
|
-
observer.observe(document.body, {
|
|
43
|
-
attributes: true,
|
|
44
|
-
attributeFilter: ["class"],
|
|
45
|
-
});
|
|
46
|
-
return () => observer.disconnect();
|
|
47
|
-
}, []);
|
|
48
|
-
return isDark;
|
|
49
|
-
};
|
|
50
19
|
/**
|
|
51
20
|
* SelectContext - exported for external consumption
|
|
52
21
|
*/
|
|
@@ -104,7 +73,7 @@ function SelectProvider({ children, selectState, size = "md", componentState = "
|
|
|
104
73
|
/**
|
|
105
74
|
* Select Button Component
|
|
106
75
|
*/
|
|
107
|
-
const SelectButton = ({ id, disabled, isOpen, state = "default", size = "md", iconStart, iconEnd, className = "", onClick, onFocus, onBlur, children, }) => {
|
|
76
|
+
const SelectButton = ({ id, disabled, isOpen, state = "default", size = "md", iconStart, iconEnd, contentStart, contentEnd, className = "", onClick, onFocus, onBlur, children, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, "aria-labelledby": ariaLabelledby, }) => {
|
|
108
77
|
var _a, _b, _c, _d, _e, _f;
|
|
109
78
|
const { theme, buttonRef } = useSelectContext();
|
|
110
79
|
const baseStyle = theme.baseStyle || "";
|
|
@@ -118,17 +87,23 @@ const SelectButton = ({ id, disabled, isOpen, state = "default", size = "md", ic
|
|
|
118
87
|
const iconEndStyle = theme.iconEndStyle || "";
|
|
119
88
|
const iconStartPadding = iconStart ? ((_e = theme.iconPadding) === null || _e === void 0 ? void 0 : _e.left) || "" : "";
|
|
120
89
|
const iconEndPadding = iconEnd ? ((_f = theme.iconPadding) === null || _f === void 0 ? void 0 : _f.right) || "" : "";
|
|
90
|
+
// Size-aware content slot style (mirrors Input.theme contentStyle)
|
|
91
|
+
const contentSlotStyle = `flex items-center px-3 ${size === "sm"
|
|
92
|
+
? "text-sm"
|
|
93
|
+
: size === "lg"
|
|
94
|
+
? "text-base"
|
|
95
|
+
: "text-sm"} text-[var(--color-text-muted)] shrink-0 dark:text-[var(--color-neutral-500)]`;
|
|
121
96
|
// Use either the themed focus state or directly apply the focus classes if the theme is not working
|
|
122
97
|
const activeFocusState = isOpen
|
|
123
98
|
? focusStateStyle ||
|
|
124
99
|
"outline-2 outline-[var(--color-primary)] -outline-offset-1 ring-4 ring-[var(--color-primary)]/10"
|
|
125
100
|
: "";
|
|
126
|
-
return (
|
|
101
|
+
return (jsxs("div", { className: cn(buttonWrapperStyle, "flex items-stretch"), children: [contentStart && (jsx("div", { "data-slot": "content-start", className: cn(contentSlotStyle, "pr-0 border-r border-[var(--color-border)]"), children: contentStart })), jsxs("button", { ref: buttonRef, id: id, type: "button", className: cn(baseStyle, sizeStyle, stateStyle, hoverStateStyle, activeFocusState, iconStartPadding, iconEndPadding, buttonStyle, "flex-1 min-w-0", className), onClick: onClick, onFocus: onFocus, onBlur: onBlur, disabled: disabled, "aria-expanded": isOpen, "aria-haspopup": "listbox", "aria-controls": isOpen && id ? `${id}-listbox` : undefined, "aria-labelledby": ariaLabelledby, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedby, children: [iconStart && jsx("span", { className: iconStartStyle, children: iconStart }), children, iconEnd && jsx("span", { className: iconEndStyle, children: iconEnd })] }), contentEnd && (jsx("div", { "data-slot": "content-end", className: cn(contentSlotStyle, "pl-0 border-l border-[var(--color-border)]"), children: contentEnd }))] }));
|
|
127
102
|
};
|
|
128
103
|
/**
|
|
129
104
|
* Select Options Component
|
|
130
105
|
*/
|
|
131
|
-
const SelectOptions = ({ isOpen, maxHeight = "15rem", className = "", onClose, children, portal = true, placement = "bottom-start", offset = 4, triggerRef: customTriggerRef, }) => {
|
|
106
|
+
const SelectOptions = ({ isOpen, maxHeight = "15rem", className = "", onClose, children, portal = true, placement = "bottom-start", offset = 4, triggerRef: customTriggerRef, listboxId, }) => {
|
|
132
107
|
var _a;
|
|
133
108
|
const context = useSelectContext();
|
|
134
109
|
const { optionsRef, buttonRef, highlightedIndex, setHighlightedIndex, filteredOptions, searchable, searchValue, setSearchValue, enableTypeahead, typeaheadValue, setTypeaheadValue, typeaheadTimeoutRef, theme, selectOption, } = context;
|
|
@@ -285,22 +260,12 @@ const SelectOptions = ({ isOpen, maxHeight = "15rem", className = "", onClose, c
|
|
|
285
260
|
searchInputRef.current.select();
|
|
286
261
|
}
|
|
287
262
|
}, [isOpen, searchable]);
|
|
288
|
-
// Add effect to measure content after render for improved positioning
|
|
289
|
-
useEffect(() => {
|
|
290
|
-
if (isOpen && contentRef.current) {
|
|
291
|
-
// Trigger recalculation after content renders
|
|
292
|
-
const timer = setTimeout(() => {
|
|
293
|
-
// Hook will remeasure automatically due to contentRef dependency
|
|
294
|
-
}, 0);
|
|
295
|
-
return () => clearTimeout(timer);
|
|
296
|
-
}
|
|
297
|
-
}, [isOpen, children, filteredOptions.length]);
|
|
298
263
|
// Handle search input change
|
|
299
264
|
const handleSearchChange = (e) => {
|
|
300
265
|
setSearchValue(e.target.value);
|
|
301
266
|
setHighlightedIndex(-1); // Reset highlighted index when search changes
|
|
302
267
|
};
|
|
303
|
-
const optionsContent = (jsx(AnimatePresence, { mode: "wait", children: isOpen && (jsxs(motion.div, { ref: combinedRef, className: cn(optionsContainerStyle, className), style: {
|
|
268
|
+
const optionsContent = (jsx(AnimatePresence, { mode: "wait", children: isOpen && (jsxs(motion.div, { ref: combinedRef, id: listboxId, className: cn(optionsContainerStyle, className), style: {
|
|
304
269
|
...cssVariables,
|
|
305
270
|
overscrollBehavior: "contain",
|
|
306
271
|
...(portal
|
|
@@ -386,7 +351,7 @@ const SelectOption = ({ value, isSelected, disabled, className = "", onSelect, c
|
|
|
386
351
|
* />
|
|
387
352
|
* ```
|
|
388
353
|
*/
|
|
389
|
-
const SelectInner = ({ id, name, size = "md", state = "default", value, displayValue, placeholder = "Select an option", disabled = false, required = false, options = [], label, helperText, onChange, onFocus, onBlur, iconStart, iconEnd, theme: themeOverrides, className = "", wrapperClassName = "", searchable = false, searchPlaceholder = "Search options...", portal = true, placement = "bottom-start", offset = 8, enableTypeahead = true, creatable = false, onCreateOption, createLabel, multiple = false, maxDisplayedChips = 3, children, }, ref) => {
|
|
354
|
+
const SelectInner = ({ id, name, size = "md", state = "default", value, displayValue, placeholder = "Select an option", disabled = false, required = false, options = [], label, helperText, onChange, onFocus, onBlur, iconStart, iconEnd, contentStart, contentEnd, theme: themeOverrides, className = "", wrapperClassName = "", searchable = false, searchPlaceholder = "Search options...", portal = true, placement = "bottom-start", offset = 8, enableTypeahead = true, creatable = false, onCreateOption, createLabel, multiple = false, maxDisplayedChips = 3, children, }, ref) => {
|
|
390
355
|
var _a;
|
|
391
356
|
// Merge theme with overrides
|
|
392
357
|
const theme = useMemo(() => ({ ...selectTheme, ...(themeOverrides || {}) }), [themeOverrides]);
|
|
@@ -438,18 +403,23 @@ const SelectInner = ({ id, name, size = "md", state = "default", value, displayV
|
|
|
438
403
|
// Generate a unique ID if not provided
|
|
439
404
|
const generatedId = React__default.useId();
|
|
440
405
|
const selectId = id || generatedId;
|
|
441
|
-
return (jsx(SelectProvider, { selectState: selectState, size: size, componentState: state, theme: themeOverrides, searchable: searchable, enableTypeahead: enableTypeahead, children: jsxs("div", { ref: ref, className: cn("relative", containerStyle, wrapperClassName), children: [label && (typeof label === 'string' ? (jsx(FormLabel, { htmlFor: selectId, required: required, children: label })) : (label)), jsx(SelectButton, { id: selectId, disabled: disabled, isOpen: isOpen, state: state, size: size, iconStart: iconStart, iconEnd: iconEnd || defaultChevronIcon, onClick: handleButtonClick, onFocus: onFocus, onBlur: onBlur, className: className, children: jsx("span", { className: cn(multiple && selectedValues.length > 0
|
|
406
|
+
return (jsx(SelectProvider, { selectState: selectState, size: size, componentState: state, theme: themeOverrides, searchable: searchable, enableTypeahead: enableTypeahead, children: jsxs("div", { ref: ref, className: cn("relative", containerStyle, wrapperClassName), children: [label && (typeof label === 'string' ? (jsx(FormLabel, { id: `${selectId}-label`, htmlFor: selectId, required: required, state: state, children: label })) : (label)), jsx(SelectButton, { id: selectId, disabled: disabled, isOpen: isOpen, state: state, size: size, iconStart: iconStart, iconEnd: iconEnd || defaultChevronIcon, contentStart: contentStart, contentEnd: contentEnd, onClick: handleButtonClick, onFocus: onFocus, onBlur: onBlur, className: className, "aria-labelledby": label && typeof label === 'string' ? `${selectId}-label` : undefined, "aria-invalid": state === "invalid" || undefined, "aria-describedby": helperText ? `${selectId}-helper` : undefined, children: jsx("span", { className: cn(multiple && selectedValues.length > 0
|
|
442
407
|
? "flex min-w-0"
|
|
443
408
|
: "block truncate", (multiple ? selectedValues.length > 0 : selectedOption)
|
|
444
409
|
? theme.valueTextStyle
|
|
445
|
-
: theme.placeholderTextStyle), children: computedDisplayValue }) }), jsx(SelectOptions, { isOpen: isOpen, onClose: () => setIsOpen(false), portal: portal, placement: placement, offset: offset, children: children ||
|
|
446
|
-
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
410
|
+
: theme.placeholderTextStyle), children: computedDisplayValue }) }), jsx(SelectOptions, { isOpen: isOpen, onClose: () => setIsOpen(false), portal: portal, placement: placement, offset: offset, listboxId: `${selectId}-listbox`, children: children ||
|
|
411
|
+
(() => {
|
|
412
|
+
const displayedOptions = filteredOptions.length > 100
|
|
413
|
+
? filteredOptions.slice(0, 100)
|
|
414
|
+
: filteredOptions;
|
|
415
|
+
return (jsxs(Fragment, { children: [displayedOptions.map((option) => {
|
|
416
|
+
const isCreateOption = option.id === "__create__";
|
|
417
|
+
const isSelected = multiple
|
|
418
|
+
? isValueSelected(option.value)
|
|
419
|
+
: option.value === selectedValue;
|
|
420
|
+
return (jsx(SelectOption, { value: option.value, isSelected: isSelected, disabled: option.disabled || false, children: isCreateOption ? (jsxs("span", { className: "flex items-center gap-2", children: [jsx(PlusIcon, { className: "size-4 shrink-0" }), jsx("span", { className: "block truncate", children: option.label })] })) : multiple ? (jsxs(Fragment, { children: [jsx("span", { className: "block truncate", children: option.label }), isSelected && (jsx("span", { className: theme.checkIconStyle, children: jsx(CheckIcon, { className: "size-4", strokeWidth: 2 }) }))] })) : (jsx("span", { className: "block truncate", children: option.label })) }, option.id));
|
|
421
|
+
}), filteredOptions.length > 100 && (jsxs("div", { className: "px-3 py-2 text-xs text-[var(--color-text-muted)] text-center border-t border-[var(--color-border)]", children: ["Showing 100 of ", filteredOptions.length, " results \u2014 refine your search"] }))] }));
|
|
422
|
+
})() }), helperText && (typeof helperText === 'string' ? (jsx("p", { id: `${selectId}-helper`, className: cn(helperTextStyle, helperTextStateStyle), children: helperText })) : (helperText)), name && (jsx("input", { type: "hidden", name: name, value: selectedValue !== undefined ? String(selectedValue) : "" }))] }) }));
|
|
453
423
|
};
|
|
454
424
|
const SelectForwarded = React__default.forwardRef(SelectInner);
|
|
455
425
|
SelectForwarded.displayName = "Select";
|
|
@@ -64,6 +64,10 @@ export interface SelectProps<T = unknown> extends SelectBaseProps {
|
|
|
64
64
|
iconStart?: React.ReactNode;
|
|
65
65
|
/** Right icon to display in the select button (default is chevron) */
|
|
66
66
|
iconEnd?: React.ReactNode;
|
|
67
|
+
/** Structural content rendered before iconStart (e.g. a prefix label or nested Select) */
|
|
68
|
+
contentStart?: React.ReactNode;
|
|
69
|
+
/** Structural content rendered after iconEnd (e.g. a suffix label or nested Select) */
|
|
70
|
+
contentEnd?: React.ReactNode;
|
|
67
71
|
/** Children to render instead of default options */
|
|
68
72
|
children?: React.ReactNode;
|
|
69
73
|
/** Component-level theme overrides */
|
|
@@ -115,6 +119,16 @@ export interface SelectButtonProps extends SelectBaseProps {
|
|
|
115
119
|
onBlur?: (event: React.FocusEvent<HTMLButtonElement>) => void;
|
|
116
120
|
/** Children to render inside the button */
|
|
117
121
|
children?: React.ReactNode;
|
|
122
|
+
/** Marks the button as invalid for assistive technologies */
|
|
123
|
+
"aria-invalid"?: boolean;
|
|
124
|
+
/** ID of the element that describes this button (e.g. helper text) */
|
|
125
|
+
"aria-describedby"?: string;
|
|
126
|
+
/** ID of the element that labels this button (e.g. form label) */
|
|
127
|
+
"aria-labelledby"?: string;
|
|
128
|
+
/** Structural content rendered before iconStart */
|
|
129
|
+
contentStart?: React.ReactNode;
|
|
130
|
+
/** Structural content rendered after iconEnd */
|
|
131
|
+
contentEnd?: React.ReactNode;
|
|
118
132
|
}
|
|
119
133
|
/**
|
|
120
134
|
* Select options container props
|
|
@@ -145,7 +145,7 @@ const Signature = React__default.forwardRef(({ id, label, labelClassName, helper
|
|
|
145
145
|
const sizeClasses = ((_a = theme.sizes) === null || _a === void 0 ? void 0 : _a[size]) || '';
|
|
146
146
|
const stateClasses = ((_b = theme.states) === null || _b === void 0 ? void 0 : _b[state]) || '';
|
|
147
147
|
const helperTextClasses = theme.helperText || '';
|
|
148
|
-
return (jsxs("div", { className: cn(wrapperClasses, stateClass, wrapperClassName), children: [label && (jsx("div", { className: "mb-1", children: jsx(FormLabel, { htmlFor: signatureId, className: labelClassName, state: state, required: required, children: label }) })), jsx("div", { className: cn(inputGroupClasses, sizeClasses, stateClasses, className), style: { width, height }, children: jsxs("div", { ref: ref, className: baseClasses, ...props, children: [jsx("canvas", { ref: canvasRef, id: signatureId, className: cn(theme.canvasStyle, customTheme.canvasStyle), tabIndex: isDisabled ? -1 : 0, onMouseDown: startDrawing, onMouseMove: draw, onMouseUp: stopDrawing, onMouseLeave: stopDrawing, onTouchStart: startDrawing, onTouchMove: draw, onTouchEnd: stopDrawing, "aria-label": label
|
|
148
|
+
return (jsxs("div", { className: cn(wrapperClasses, stateClass, wrapperClassName), children: [label && (typeof label === 'string' ? (jsx("div", { className: "mb-1", children: jsx(FormLabel, { htmlFor: signatureId, className: labelClassName, state: state, required: required, children: label }) })) : (label)), jsx("div", { className: cn(inputGroupClasses, sizeClasses, stateClasses, className), style: { width, height }, children: jsxs("div", { ref: ref, className: baseClasses, ...props, children: [jsx("canvas", { ref: canvasRef, id: signatureId, className: cn(theme.canvasStyle, customTheme.canvasStyle), tabIndex: isDisabled ? -1 : 0, onMouseDown: startDrawing, onMouseMove: draw, onMouseUp: stopDrawing, onMouseLeave: stopDrawing, onTouchStart: startDrawing, onTouchMove: draw, onTouchEnd: stopDrawing, "aria-label": typeof label === 'string' ? label : 'Signature canvas', "aria-required": required, "aria-invalid": isInvalid, "aria-disabled": isDisabled }), isEmpty && placeholder && !isDisabled && (jsx("div", { className: cn(theme.placeholderStyle, customTheme.placeholderStyle), children: placeholder })), showControls && !isDisabled && (jsxs("div", { className: cn(theme.controlsStyle, customTheme.controlsStyle), children: [jsx(Button, { size: "sm", variant: "outline", color: 'neutral', onClick: clear, type: "button", children: "Clear" }), jsx(Button, { size: "sm", variant: "filled", onClick: save, disabled: isEmpty, type: "button", children: "Save" })] }))] }) }), helperText && (typeof helperText === 'string' ? (jsx("div", { className: cn(helperTextClasses, helperTextClassName), children: helperText })) : (helperText))] }));
|
|
149
149
|
});
|
|
150
150
|
Signature.displayName = 'Signature';
|
|
151
151
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { HTMLAttributes } from 'react';
|
|
1
|
+
import { HTMLAttributes, ReactNode } from 'react';
|
|
2
2
|
/**
|
|
3
3
|
* Signature component size variants
|
|
4
4
|
*/
|
|
@@ -51,11 +51,11 @@ export interface SignatureProps extends Omit<HTMLAttributes<HTMLDivElement>, 'on
|
|
|
51
51
|
/** Unique identifier for the signature field */
|
|
52
52
|
id?: string;
|
|
53
53
|
/** Label text for the signature */
|
|
54
|
-
label?:
|
|
54
|
+
label?: ReactNode;
|
|
55
55
|
/** Additional class name for the label */
|
|
56
56
|
labelClassName?: string;
|
|
57
57
|
/** Helper text displayed below the signature */
|
|
58
|
-
helperText?:
|
|
58
|
+
helperText?: ReactNode;
|
|
59
59
|
/** Additional class name for the helper text */
|
|
60
60
|
helperTextClassName?: string;
|
|
61
61
|
/** Additional class name for the wrapper */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
-
import React__default, { useRef, useState, useCallback, useMemo } from 'react';
|
|
2
|
+
import React__default, { useRef, useEffect, useState, useCallback, useMemo } from 'react';
|
|
3
3
|
import { FormLabel } from '../FormLabel/FormLabel.js';
|
|
4
4
|
import { sliderTheme } from './Slider.theme.js';
|
|
5
5
|
import { cn } from '../../../utils/cn.js';
|
|
@@ -7,9 +7,21 @@ import { cn } from '../../../utils/cn.js';
|
|
|
7
7
|
const Slider = React__default.forwardRef(({ state = "default", className = "", label, labelClassName = "", helperText, helperTextClassName = "", wrapperClassName = "", min = 0, max = 100, step = 1, value, defaultValue, onChange, onChangeCommitted, showValue = false, unit = "", showMinMax = false, showTicks = false, tickStep, range = false, iconStart, iconEnd, theme = {}, required = false, name, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, "aria-describedby": ariaDescribedby, ...props }, ref) => {
|
|
8
8
|
var _a;
|
|
9
9
|
const trackRef = useRef(null);
|
|
10
|
+
// Track active pointer listeners so they can be cleaned up on unmount during a drag
|
|
11
|
+
const activeListenersRef = useRef({ move: null, up: null });
|
|
12
|
+
// Cleanup on unmount — removes any lingering listeners if component unmounts mid-drag
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
return () => {
|
|
15
|
+
const { move, up } = activeListenersRef.current;
|
|
16
|
+
if (move)
|
|
17
|
+
document.removeEventListener("pointermove", move);
|
|
18
|
+
if (up)
|
|
19
|
+
document.removeEventListener("pointerup", up);
|
|
20
|
+
};
|
|
21
|
+
}, []);
|
|
10
22
|
const generatedId = React__default.useId();
|
|
11
23
|
const sliderId = id || generatedId;
|
|
12
|
-
const helperTextId = helperText ? `${sliderId}-helper` : undefined;
|
|
24
|
+
const helperTextId = typeof helperText === 'string' && helperText ? `${sliderId}-helper` : undefined;
|
|
13
25
|
// Handle controlled/uncontrolled value
|
|
14
26
|
const [internalValue, setInternalValue] = useState((_a = value !== null && value !== void 0 ? value : defaultValue) !== null && _a !== void 0 ? _a : (range ? [min, min] : min));
|
|
15
27
|
const currentValue = value !== null && value !== void 0 ? value : internalValue;
|
|
@@ -68,11 +80,13 @@ const Slider = React__default.forwardRef(({ state = "default", className = "", l
|
|
|
68
80
|
}
|
|
69
81
|
};
|
|
70
82
|
const handleEnd = () => {
|
|
83
|
+
activeListenersRef.current = { move: null, up: null };
|
|
71
84
|
setDragIndex(null);
|
|
72
85
|
onChangeCommitted === null || onChangeCommitted === void 0 ? void 0 : onChangeCommitted(currentValue);
|
|
73
86
|
document.removeEventListener("pointermove", handleMove);
|
|
74
87
|
document.removeEventListener("pointerup", handleEnd);
|
|
75
88
|
};
|
|
89
|
+
activeListenersRef.current = { move: handleMove, up: handleEnd };
|
|
76
90
|
document.addEventListener("pointermove", handleMove);
|
|
77
91
|
document.addEventListener("pointerup", handleEnd);
|
|
78
92
|
}, [
|
|
@@ -217,7 +231,7 @@ const Slider = React__default.forwardRef(({ state = "default", className = "", l
|
|
|
217
231
|
return `${currentValue}${unit}`;
|
|
218
232
|
};
|
|
219
233
|
const filterId = `slider-liquid-lens-${sliderId}`;
|
|
220
|
-
return (jsxs("div", { className: cn(mergedTheme.wrapperStyle, wrapperClassName), ref: ref, ...props, children: [jsx("svg", { width: "0", height: "0", className: "absolute", "aria-hidden": "true", children: jsx("defs", { children: jsxs("filter", { id: filterId, children: [jsx("feGaussianBlur", { in: "SourceGraphic", stdDeviation: "1", result: "blur" }), jsx("feColorMatrix", { in: "blur", type: "matrix", values: "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7", result: "goo" }), jsx("feComposite", { in: "SourceGraphic", in2: "goo", operator: "atop" })] }) }) }), label && (jsx(FormLabel, { htmlFor: sliderId, required: required, className: cn(mergedTheme.labelStyle, labelClassName), children: label })), jsxs("div", { className: "flex items-center gap-4", children: [iconStart && (jsx("div", { className: "flex-shrink-0 text-[var(--color-text-muted)]", children: iconStart })), showMinMax && (jsx("span", { className: cn(mergedTheme.minMaxLabelStyle), children: min })), jsxs("div", { className: cn(mergedTheme.rootStyle, className), "data-disabled": isDisabled || undefined, "data-invalid": isInvalid || undefined, "data-orientation": "horizontal", children: [jsxs("div", { ref: trackRef, className: mergedTheme.trackStyle, onPointerDown: handleTrackClick, role: "presentation", children: [jsx("div", { className: mergedTheme.rangeStyle, style: getFilledTrackStyle() }), showTicks &&
|
|
234
|
+
return (jsxs("div", { className: cn(mergedTheme.wrapperStyle, wrapperClassName), ref: ref, ...props, children: [jsx("svg", { width: "0", height: "0", className: "absolute", "aria-hidden": "true", children: jsx("defs", { children: jsxs("filter", { id: filterId, children: [jsx("feGaussianBlur", { in: "SourceGraphic", stdDeviation: "1", result: "blur" }), jsx("feColorMatrix", { in: "blur", type: "matrix", values: "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7", result: "goo" }), jsx("feComposite", { in: "SourceGraphic", in2: "goo", operator: "atop" })] }) }) }), label && (typeof label === 'string' ? (jsx(FormLabel, { htmlFor: sliderId, state: state, required: required, className: cn(mergedTheme.labelStyle, labelClassName), children: label })) : (label)), jsxs("div", { className: "flex items-center gap-4", children: [iconStart && (jsx("div", { className: "flex-shrink-0 text-[var(--color-text-muted)]", children: iconStart })), showMinMax && (jsx("span", { className: cn(mergedTheme.minMaxLabelStyle), children: min })), jsxs("div", { className: cn(mergedTheme.rootStyle, className), "data-disabled": isDisabled || undefined, "data-invalid": isInvalid || undefined, "data-orientation": "horizontal", children: [jsxs("div", { ref: trackRef, className: mergedTheme.trackStyle, onPointerDown: handleTrackClick, role: "presentation", children: [jsx("div", { className: mergedTheme.rangeStyle, style: getFilledTrackStyle() }), showTicks &&
|
|
221
235
|
ticks.map((tick) => {
|
|
222
236
|
const percentage = getPercentage(tick);
|
|
223
237
|
return (jsx("div", { className: mergedTheme.tickStyle, style: { left: `${percentage}%` } }, tick));
|
|
@@ -237,7 +251,7 @@ const Slider = React__default.forwardRef(({ state = "default", className = "", l
|
|
|
237
251
|
}, "aria-hidden": "true" })] }, index));
|
|
238
252
|
}), name && (jsx("input", { type: "hidden", name: name, value: Array.isArray(currentValue)
|
|
239
253
|
? currentValue.join(",")
|
|
240
|
-
: currentValue }))] }), showMinMax && (jsx("span", { className: cn(mergedTheme.minMaxLabelStyle), children: max })), showValue && (jsx("div", { className: cn(mergedTheme.valueDisplayStyle), children: formatValueDisplay() })), iconEnd && (jsx("div", { className: "flex-shrink-0 text-[var(--color-text-muted)]", children: iconEnd }))] }), helperText && (jsx("div", { id: helperTextId, className: cn(mergedTheme.helperTextStyle, helperTextClassName), children: helperText }))] }));
|
|
254
|
+
: currentValue }))] }), showMinMax && (jsx("span", { className: cn(mergedTheme.minMaxLabelStyle), children: max })), showValue && (jsx("div", { className: cn(mergedTheme.valueDisplayStyle), children: formatValueDisplay() })), iconEnd && (jsx("div", { className: "flex-shrink-0 text-[var(--color-text-muted)]", children: iconEnd }))] }), helperText && (typeof helperText === 'string' ? (jsx("div", { id: helperTextId, className: cn(mergedTheme.helperTextStyle, helperTextClassName), children: helperText })) : (helperText))] }));
|
|
241
255
|
});
|
|
242
256
|
Slider.displayName = "Slider";
|
|
243
257
|
|
|
@@ -26,9 +26,9 @@ export interface SliderProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
|
|
|
26
26
|
/** The state of the slider (default, disabled, invalid) */
|
|
27
27
|
state?: SliderState;
|
|
28
28
|
/** Optional label text for the slider */
|
|
29
|
-
label?:
|
|
29
|
+
label?: React.ReactNode;
|
|
30
30
|
/** Optional helper text displayed below the slider */
|
|
31
|
-
helperText?:
|
|
31
|
+
helperText?: React.ReactNode;
|
|
32
32
|
/** Minimum value for the slider */
|
|
33
33
|
min?: number;
|
|
34
34
|
/** Maximum value for the slider */
|
|
@@ -8,8 +8,10 @@ import { cn } from '../../../utils/cn.js';
|
|
|
8
8
|
/**
|
|
9
9
|
* Switch component for toggling between two states
|
|
10
10
|
*/
|
|
11
|
-
const Switch = React__default.forwardRef(({ id, checked: controlledChecked, onChange, label, description, state = "default", labelPosition = "end", theme, className, ...rest }, ref) => {
|
|
12
|
-
const
|
|
11
|
+
const Switch = React__default.forwardRef(({ id, checked: controlledChecked, defaultChecked, onChange, label, description, state = "default", labelPosition = "end", size = "md", theme, className, name, value, required, form, ...rest }, ref) => {
|
|
12
|
+
const generatedId = React__default.useId();
|
|
13
|
+
const switchId = id !== null && id !== void 0 ? id : generatedId;
|
|
14
|
+
const [internalChecked, setInternalChecked] = useState(defaultChecked !== null && defaultChecked !== void 0 ? defaultChecked : false);
|
|
13
15
|
// Determine if component is controlled or uncontrolled
|
|
14
16
|
const isControlled = controlledChecked !== undefined;
|
|
15
17
|
const isChecked = isControlled ? controlledChecked : internalChecked;
|
|
@@ -24,18 +26,9 @@ const Switch = React__default.forwardRef(({ id, checked: controlledChecked, onCh
|
|
|
24
26
|
}
|
|
25
27
|
onChange === null || onChange === void 0 ? void 0 : onChange(e.target.checked);
|
|
26
28
|
};
|
|
27
|
-
const handleTrackClick = () => {
|
|
28
|
-
if (!isDisabled) {
|
|
29
|
-
const newValue = !isChecked;
|
|
30
|
-
if (!isControlled) {
|
|
31
|
-
setInternalChecked(newValue);
|
|
32
|
-
}
|
|
33
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(newValue);
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
29
|
// Label and description content
|
|
37
30
|
const labelContent = label || description ? (jsxs("div", { className: "flex grow flex-col", children: [label &&
|
|
38
|
-
(typeof label === "string" ? (jsx(FormLabel, { htmlFor:
|
|
31
|
+
(typeof label === "string" ? (jsx(FormLabel, { htmlFor: switchId, state: state, children: label })) : (label)), description &&
|
|
39
32
|
(typeof description === "string" ? (jsx("div", { className: cn(switchTheme.description, theme === null || theme === void 0 ? void 0 : theme.description), children: description })) : (description))] })) : null;
|
|
40
33
|
// Build data attributes for state-based styling
|
|
41
34
|
const trackDataState = isDisabled
|
|
@@ -45,14 +38,14 @@ const Switch = React__default.forwardRef(({ id, checked: controlledChecked, onCh
|
|
|
45
38
|
: isChecked
|
|
46
39
|
? "checked"
|
|
47
40
|
: "unchecked";
|
|
48
|
-
const filterId = `switch-liquid-lens-${
|
|
41
|
+
const filterId = `switch-liquid-lens-${switchId}`;
|
|
49
42
|
// Switch toggle component
|
|
50
|
-
const switchToggle = (jsxs("div", { className: "relative flex items-center h-6", children: [jsx("
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
43
|
+
const switchToggle = (jsxs("div", { className: "relative flex items-center h-6", children: [jsx("svg", { width: "0", height: "0", className: "absolute", "aria-hidden": "true", children: jsx("defs", { children: jsxs("filter", { id: filterId, children: [jsx("feGaussianBlur", { in: "SourceGraphic", stdDeviation: "1", result: "blur" }), jsx("feColorMatrix", { in: "blur", type: "matrix", values: "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7", result: "goo" }), jsx("feComposite", { in: "SourceGraphic", in2: "goo", operator: "atop" })] }) }) }), jsxs("label", { htmlFor: switchId, className: "contents", children: [jsx("input", { ref: ref, type: "checkbox", id: switchId, checked: isChecked, onChange: handleChange, disabled: isDisabled, className: "sr-only", name: name, value: value, required: required, form: form }), jsx("div", { className: cn(switchTheme.track, switchTheme.trackSizes[size], theme === null || theme === void 0 ? void 0 : theme.track), "data-state": trackDataState, "aria-hidden": "true", children: jsxs(motion.div, { className: cn(switchTheme.thumb, switchTheme.thumbSizes[size], theme === null || theme === void 0 ? void 0 : theme.thumb), initial: false, animate: isChecked ? thumbPositions.checked : thumbPositions.unchecked, transition: { type: "spring", stiffness: 500, damping: 30 }, whileTap: isDisabled ? undefined : { scale: 1.15 }, style: { filter: `url(#${filterId})` }, children: [jsx("div", { className: "absolute inset-0 z-0 rounded-full", style: {
|
|
44
|
+
backdropFilter: "blur(0.6px)",
|
|
45
|
+
WebkitBackdropFilter: "blur(0.6px)",
|
|
46
|
+
}, "aria-hidden": "true" }), jsx("div", { className: "absolute inset-0 z-[1] rounded-full bg-[var(--color-surface)]/80", "aria-hidden": "true" }), jsx("div", { className: "absolute inset-0 z-[2] rounded-full", style: {
|
|
47
|
+
boxShadow: "inset 1px 1px 0 rgba(255,255,255,0.35), inset 1px 3px 0 rgba(0,0,0,0.03), inset 0 0 14px rgba(255,255,255,0.25), inset -1px -1px 0 rgba(0,0,0,0.06)",
|
|
48
|
+
}, "aria-hidden": "true" })] }) })] })] }));
|
|
56
49
|
return (jsxs("div", { className: cn(switchTheme.container, labelPosition === "start" ? "flex-row-reverse" : "flex-row", theme === null || theme === void 0 ? void 0 : theme.container, className), ...rest, children: [labelContent, switchToggle] }));
|
|
57
50
|
});
|
|
58
51
|
Switch.displayName = "Switch";
|
|
@@ -3,9 +3,19 @@
|
|
|
3
3
|
*/
|
|
4
4
|
const switchTheme = {
|
|
5
5
|
container: "flex w-full justify-between items-center gap-4",
|
|
6
|
-
track: "
|
|
7
|
-
thumb: "
|
|
6
|
+
track: "rounded-full cursor-pointer shadow-inner bg-[var(--color-background-tertiary)] data-[state=checked]:bg-[var(--color-primary)] data-[state=disabled]:bg-[var(--color-background-disabled)] data-[state=disabled]:cursor-not-allowed data-[state=invalid]:border-2 data-[state=invalid]:border-[var(--color-danger)] outline outline-[var(--color-border)] transition-colors duration-300",
|
|
7
|
+
thumb: "rounded-full bg-transparent overflow-hidden border border-[var(--color-border)]/40 shadow-lg",
|
|
8
8
|
description: "text-sm text-[var(--color-text-muted)] -mt-1.5",
|
|
9
|
+
trackSizes: {
|
|
10
|
+
sm: "w-9 h-5",
|
|
11
|
+
md: "w-12 h-6",
|
|
12
|
+
lg: "w-14 h-7",
|
|
13
|
+
},
|
|
14
|
+
thumbSizes: {
|
|
15
|
+
sm: "w-5 h-4",
|
|
16
|
+
md: "w-7 h-5",
|
|
17
|
+
lg: "w-8 h-6",
|
|
18
|
+
},
|
|
9
19
|
};
|
|
10
20
|
/**
|
|
11
21
|
* Thumb position values for animation
|
|
@@ -6,14 +6,20 @@ export type SwitchLabelPosition = "start" | "end";
|
|
|
6
6
|
* State variants for the switch component
|
|
7
7
|
*/
|
|
8
8
|
export type SwitchState = 'default' | 'disabled' | 'invalid';
|
|
9
|
+
/**
|
|
10
|
+
* Size variants for the switch component
|
|
11
|
+
*/
|
|
12
|
+
export type SwitchSize = 'sm' | 'md' | 'lg';
|
|
9
13
|
/**
|
|
10
14
|
* Switch component props
|
|
11
15
|
*/
|
|
12
16
|
export interface SwitchProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> {
|
|
13
|
-
/** Unique identifier for the switch input */
|
|
14
|
-
id
|
|
17
|
+
/** Unique identifier for the switch input — if omitted, one is generated via React.useId() */
|
|
18
|
+
id?: string;
|
|
15
19
|
/** Whether the switch is checked (controlled mode) */
|
|
16
20
|
checked?: boolean;
|
|
21
|
+
/** Initial checked state for uncontrolled mode */
|
|
22
|
+
defaultChecked?: boolean;
|
|
17
23
|
/** Function called when the switch state changes */
|
|
18
24
|
onChange?: (checked: boolean) => void;
|
|
19
25
|
/** Label for the switch — pass a string for default styling, or a ReactNode for full control */
|
|
@@ -24,8 +30,18 @@ export interface SwitchProps extends Omit<React.HTMLAttributes<HTMLDivElement>,
|
|
|
24
30
|
state?: SwitchState;
|
|
25
31
|
/** Position of the label relative to the switch */
|
|
26
32
|
labelPosition?: SwitchLabelPosition;
|
|
33
|
+
/** Size of the switch track and thumb */
|
|
34
|
+
size?: SwitchSize;
|
|
27
35
|
/** Optional theme overrides */
|
|
28
36
|
theme?: SwitchThemeOverrides;
|
|
37
|
+
/** HTML name attribute for the hidden input */
|
|
38
|
+
name?: string;
|
|
39
|
+
/** HTML value attribute for the hidden input */
|
|
40
|
+
value?: string;
|
|
41
|
+
/** Whether the hidden input is required */
|
|
42
|
+
required?: boolean;
|
|
43
|
+
/** The id of the form this input belongs to */
|
|
44
|
+
form?: string;
|
|
29
45
|
}
|
|
30
46
|
/**
|
|
31
47
|
* Switch theme configuration
|
|
@@ -35,6 +51,8 @@ export interface SwitchTheme {
|
|
|
35
51
|
track: string;
|
|
36
52
|
thumb: string;
|
|
37
53
|
description: string;
|
|
54
|
+
trackSizes: Record<SwitchSize, string>;
|
|
55
|
+
thumbSizes: Record<SwitchSize, string>;
|
|
38
56
|
}
|
|
39
57
|
/**
|
|
40
58
|
* Switch theme overrides
|