flikkui 0.2.0-beta.2 → 0.2.0-beta.4
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/ai/PromptInput/PromptInput.js +23 -15
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.d.ts +27 -0
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.js +62 -0
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.theme.d.ts +10 -0
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.theme.js +12 -0
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.types.d.ts +53 -0
- package/dist/components/ai/PromptSuggestions/index.d.ts +4 -2
- package/dist/components/ai/index.d.ts +2 -12
- package/dist/components/charts/ActivityRings/ActivityRings.js +70 -58
- package/dist/components/charts/ActivityRings/ActivityRings.theme.js +0 -1
- package/dist/components/charts/ActivityRings/ActivityRings.types.d.ts +17 -0
- package/dist/components/charts/BarChart/BarChart.js +8 -4
- package/dist/components/charts/BarChart/BarChart.types.d.ts +14 -0
- package/dist/components/charts/DonutChart/DonutChart.js +11 -8
- package/dist/components/charts/DonutChart/DonutChart.theme.d.ts +3 -0
- package/dist/components/charts/DonutChart/DonutChart.theme.js +5 -4
- package/dist/components/charts/DonutChart/donut-utils.d.ts +5 -0
- package/dist/components/charts/DonutChart/donut-utils.js +26 -1
- package/dist/components/charts/Heatmap/Heatmap.theme.js +2 -2
- package/dist/components/charts/shared/ChartAxis/XAxis.d.ts +2 -2
- package/dist/components/charts/shared/ChartAxis/XAxis.js +4 -4
- package/dist/components/charts/shared/ChartAxis/YAxis.d.ts +2 -2
- package/dist/components/charts/shared/ChartAxis/YAxis.js +8 -7
- package/dist/components/charts/shared/ChartGrid/HorizontalGrid.d.ts +1 -1
- package/dist/components/charts/shared/ChartGrid/HorizontalGrid.js +2 -2
- package/dist/components/charts/theme/chart.theme.d.ts +1 -1
- package/dist/components/charts/theme/chart.theme.js +39 -39
- package/dist/components/core/Accordion/Accordion.d.ts +1 -1
- package/dist/components/core/Accordion/Accordion.js +2 -2
- package/dist/components/core/Accordion/Accordion.types.d.ts +8 -0
- package/dist/components/core/Badge/Badge.js +11 -15
- package/dist/components/core/Badge/Badge.theme.js +7 -21
- package/dist/components/core/Badge/Badge.types.d.ts +9 -1
- package/dist/components/core/Button/Button.js +2 -2
- package/dist/components/core/Button/Button.theme.js +1 -1
- package/dist/components/core/Button/Button.types.d.ts +8 -0
- package/dist/components/core/Card/Card.js +8 -2
- package/dist/components/core/Card/Card.theme.js +1 -1
- package/dist/components/core/Card/Card.types.d.ts +24 -1
- package/dist/components/core/Drawer/Drawer.d.ts +1 -1
- package/dist/components/core/Drawer/Drawer.js +10 -40
- package/dist/components/core/Drawer/Drawer.theme.js +2 -1
- package/dist/components/core/Drawer/Drawer.types.d.ts +8 -0
- package/dist/components/core/Dropdown/Dropdown.d.ts +1 -1
- package/dist/components/core/Dropdown/Dropdown.js +2 -2
- package/dist/components/core/Dropdown/Dropdown.types.d.ts +8 -0
- package/dist/components/core/Metric/Metric.d.ts +1 -1
- package/dist/components/core/Metric/Metric.js +9 -5
- package/dist/components/core/Metric/Metric.theme.d.ts +1 -1
- package/dist/components/core/Metric/Metric.theme.js +38 -28
- package/dist/components/core/Metric/Metric.types.d.ts +27 -8
- package/dist/components/core/Modal/Modal.d.ts +1 -1
- package/dist/components/core/Modal/Modal.js +17 -40
- package/dist/components/core/Modal/Modal.theme.js +8 -3
- package/dist/components/core/Modal/Modal.types.d.ts +18 -0
- package/dist/components/core/Modal/index.d.ts +1 -1
- package/dist/components/core/Notification/Notification.js +2 -0
- package/dist/components/core/Pill/Pill.d.ts +6 -11
- package/dist/components/core/Pill/Pill.theme.d.ts +2 -2
- package/dist/components/core/Pill/Pill.types.d.ts +9 -22
- package/dist/components/core/Pill/index.d.ts +1 -1
- package/dist/components/core/Popover/Popover.d.ts +1 -1
- package/dist/components/core/Popover/Popover.js +2 -2
- package/dist/components/core/Popover/Popover.types.d.ts +8 -0
- package/dist/components/core/Progress/Progress.d.ts +28 -0
- package/dist/components/core/Progress/Progress.js +114 -0
- package/dist/components/core/Progress/Progress.theme.d.ts +5 -0
- package/dist/components/core/Progress/Progress.theme.js +33 -0
- package/dist/components/core/Progress/Progress.types.d.ts +92 -0
- package/dist/components/core/Progress/index.d.ts +2 -0
- package/dist/components/core/Tabs/Tabs.js +2 -2
- package/dist/components/core/Tabs/Tabs.types.d.ts +8 -0
- package/dist/components/core/Tag/Tag.animations.d.ts +3 -0
- package/dist/components/core/Tag/Tag.animations.js +31 -0
- package/dist/components/core/Tag/Tag.d.ts +14 -0
- package/dist/components/core/Tag/Tag.js +45 -0
- package/dist/components/core/Tag/Tag.theme.d.ts +2 -0
- package/dist/components/core/Tag/Tag.theme.js +21 -0
- package/dist/components/core/Tag/Tag.types.d.ts +40 -0
- package/dist/components/core/Tag/index.d.ts +3 -0
- package/dist/components/core/Tooltip/Tooltip.d.ts +1 -1
- package/dist/components/core/Tooltip/Tooltip.js +3 -3
- package/dist/components/core/Tooltip/Tooltip.theme.js +1 -1
- package/dist/components/core/Tooltip/Tooltip.types.d.ts +17 -0
- package/dist/components/core/index.d.ts +2 -1
- package/dist/components/core/index.js +3 -2
- package/dist/components/effects/CustomCursor/CustomCursor.d.ts +0 -13
- package/dist/components/effects/CustomCursor/CustomCursor.js +26 -2
- package/dist/components/effects/CustomCursor/CustomCursor.theme.js +12 -1
- package/dist/components/effects/CustomCursor/CustomCursor.types.d.ts +14 -1
- package/dist/components/forms/Combobox/Combobox.d.ts +25 -0
- package/dist/components/forms/Combobox/Combobox.js +412 -0
- package/dist/components/forms/Combobox/Combobox.theme.d.ts +6 -0
- package/dist/components/forms/Combobox/Combobox.theme.js +60 -0
- package/dist/components/forms/Combobox/Combobox.types.d.ts +111 -0
- package/dist/components/forms/Combobox/index.d.ts +3 -0
- package/dist/components/forms/FileUpload/FileUpload.js +2 -0
- package/dist/components/forms/Input/Input.js +25 -28
- package/dist/components/forms/Input/inputMasks.d.ts +15 -0
- package/dist/components/forms/Input/inputMasks.js +72 -1
- package/dist/components/forms/InputTag/InputTag.d.ts +40 -0
- package/dist/components/forms/InputTag/InputTag.js +491 -0
- package/dist/components/forms/InputTag/InputTag.theme.d.ts +2 -0
- package/dist/components/forms/InputTag/InputTag.theme.js +16 -0
- package/dist/components/forms/InputTag/InputTag.types.d.ts +107 -0
- package/dist/components/forms/InputTag/index.d.ts +3 -0
- package/dist/components/forms/Select/Select.d.ts +101 -2
- package/dist/components/forms/Select/Select.js +128 -132
- package/dist/components/forms/Select/Select.theme.js +10 -14
- package/dist/components/forms/Select/Select.types.d.ts +6 -2
- package/dist/components/forms/Select/index.d.ts +7 -4
- package/dist/components/forms/Select/useSelectState.d.ts +66 -0
- package/dist/components/forms/Select/useSelectState.js +134 -0
- package/dist/components/forms/SelectExpand/SelectExpand.animations.d.ts +20 -0
- package/dist/components/forms/SelectExpand/SelectExpand.animations.js +74 -0
- package/dist/components/forms/SelectExpand/SelectExpand.d.ts +9 -0
- package/dist/components/forms/SelectExpand/SelectExpand.js +223 -0
- package/dist/components/forms/SelectExpand/SelectExpand.theme.d.ts +5 -0
- package/dist/components/forms/SelectExpand/SelectExpand.theme.js +74 -0
- package/dist/components/forms/SelectExpand/SelectExpand.types.d.ts +126 -0
- package/dist/components/forms/SelectExpand/index.d.ts +4 -0
- package/dist/components/forms/Switch/Switch.js +3 -3
- package/dist/components/forms/Switch/Switch.theme.d.ts +1 -1
- package/dist/components/forms/Switch/Switch.theme.js +2 -2
- package/dist/components/forms/TimePicker/TimePicker.animations.d.ts +0 -46
- package/dist/components/forms/TimePicker/TimePicker.d.ts +15 -6
- package/dist/components/forms/TimePicker/TimePicker.js +285 -124
- package/dist/components/forms/TimePicker/TimePicker.theme.d.ts +1 -1
- package/dist/components/forms/TimePicker/TimePicker.theme.js +39 -22
- package/dist/components/forms/TimePicker/TimePicker.types.d.ts +88 -34
- package/dist/components/forms/TimePicker/TimePickerContent.d.ts +7 -10
- package/dist/components/forms/TimePicker/TimePickerContent.js +149 -16
- package/dist/components/forms/TimePicker/TimePickerTrigger.d.ts +3 -3
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +22 -19
- package/dist/components/forms/TimePicker/WheelColumn.d.ts +14 -0
- package/dist/components/forms/TimePicker/WheelColumn.js +90 -0
- package/dist/components/forms/TimePicker/index.d.ts +4 -1
- package/dist/components/forms/TimePicker/useWheelPicker.d.ts +37 -0
- package/dist/components/forms/TimePicker/useWheelPicker.js +138 -0
- package/dist/components/forms/forms.theme.d.ts +14 -0
- package/dist/components/forms/forms.theme.js +31 -0
- package/dist/components/forms/index.d.ts +9 -3
- package/dist/components/forms/index.js +73 -2
- package/dist/hooks/index.d.ts +0 -4
- package/dist/icons/Icon.d.ts +7 -0
- package/dist/icons/Icon.js +6 -2
- package/dist/index.js +12 -16
- package/dist/styles.css +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/optimisticErrors.js +1 -70
- package/package.json +1 -1
- package/dist/components/ai/EditingIndicator/EditingIndicator.animations.d.ts +0 -31
- package/dist/components/ai/EditingIndicator/EditingIndicator.animations.js +0 -115
- package/dist/components/ai/EditingIndicator/EditingIndicator.d.ts +0 -35
- package/dist/components/ai/EditingIndicator/EditingIndicator.js +0 -94
- package/dist/components/ai/EditingIndicator/EditingIndicator.theme.d.ts +0 -2
- package/dist/components/ai/EditingIndicator/EditingIndicator.theme.js +0 -13
- package/dist/components/ai/EditingIndicator/EditingIndicator.types.d.ts +0 -54
- package/dist/components/ai/EditingIndicator/index.d.ts +0 -9
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.d.ts +0 -3
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.js +0 -126
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.theme.d.ts +0 -2
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.theme.js +0 -8
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.types.d.ts +0 -45
- package/dist/components/ai/GenerativeRenderer/index.d.ts +0 -3
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.animations.d.ts +0 -17
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.animations.js +0 -56
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.d.ts +0 -38
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.js +0 -110
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.theme.d.ts +0 -2
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.theme.js +0 -13
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.types.d.ts +0 -53
- package/dist/components/ai/PresenceIndicator/index.d.ts +0 -8
- package/dist/components/ai/PresenceProvider/PresenceContext.d.ts +0 -24
- package/dist/components/ai/PresenceProvider/PresenceContext.js +0 -34
- package/dist/components/ai/PresenceProvider/PresenceProvider.d.ts +0 -32
- package/dist/components/ai/PresenceProvider/PresenceProvider.js +0 -321
- package/dist/components/ai/PresenceProvider/PresenceProvider.types.d.ts +0 -140
- package/dist/components/ai/PresenceProvider/adapters/MockAdapter.d.ts +0 -102
- package/dist/components/ai/PresenceProvider/adapters/MockAdapter.js +0 -331
- package/dist/components/ai/PresenceProvider/adapters/PresenceAdapter.d.ts +0 -93
- package/dist/components/ai/PresenceProvider/adapters/SupabaseAdapter.d.ts +0 -134
- package/dist/components/ai/PresenceProvider/adapters/WebSocketAdapter.d.ts +0 -149
- package/dist/components/ai/PresenceProvider/adapters/index.d.ts +0 -11
- package/dist/components/ai/PresenceProvider/index.d.ts +0 -10
- package/dist/components/ai/PromptSuggestions/PromptSuggestions.d.ts +0 -27
- package/dist/components/ai/PromptSuggestions/PromptSuggestions.js +0 -61
- package/dist/components/ai/PromptSuggestions/PromptSuggestions.types.d.ts +0 -65
- package/dist/components/ai/VersionSlider/VersionSlider.d.ts +0 -3
- package/dist/components/ai/VersionSlider/VersionSlider.js +0 -97
- package/dist/components/ai/VersionSlider/VersionSlider.theme.d.ts +0 -2
- package/dist/components/ai/VersionSlider/VersionSlider.theme.js +0 -18
- package/dist/components/ai/VersionSlider/VersionSlider.types.d.ts +0 -77
- package/dist/components/ai/VersionSlider/index.d.ts +0 -3
- package/dist/components/core/Pill/Pill.animations.js +0 -25
- package/dist/components/core/Pill/Pill.js +0 -145
- package/dist/components/core/Pill/Pill.theme.js +0 -65
- package/dist/components/core/RetryBoundary/RetryBoundary.d.ts +0 -35
- package/dist/components/core/RetryBoundary/RetryBoundary.js +0 -154
- package/dist/components/core/RetryBoundary/RetryBoundary.theme.d.ts +0 -2
- package/dist/components/core/RetryBoundary/RetryBoundary.theme.js +0 -7
- package/dist/components/core/RetryBoundary/RetryBoundary.types.d.ts +0 -51
- package/dist/components/core/RetryBoundary/index.d.ts +0 -3
- package/dist/components/forms/OptimisticForm/OptimisticForm.d.ts +0 -33
- package/dist/components/forms/OptimisticForm/OptimisticForm.js +0 -87
- package/dist/components/forms/OptimisticForm/OptimisticForm.theme.d.ts +0 -2
- package/dist/components/forms/OptimisticForm/OptimisticForm.theme.js +0 -8
- package/dist/components/forms/OptimisticForm/OptimisticForm.types.d.ts +0 -74
- package/dist/components/forms/OptimisticForm/index.d.ts +0 -3
- package/dist/hooks/useOptimisticMutation.d.ts +0 -109
- package/dist/hooks/useOptimisticMutation.js +0 -171
- package/dist/hooks/usePresence.d.ts +0 -88
- package/dist/utils/presenceUtils.d.ts +0 -66
- package/dist/utils/presenceUtils.js +0 -107
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
import React__default, { useState, useRef, useEffect, useMemo, useCallback } from 'react';
|
|
2
|
+
import { AnimatePresence, motion } from 'motion/react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { TagIcon, PlusIcon, CheckIcon } from '@heroicons/react/24/outline';
|
|
5
|
+
import { cn } from '../../../utils/cn.js';
|
|
6
|
+
import { Input } from '../Input/Input.js';
|
|
7
|
+
import '../Input/Input.theme.js';
|
|
8
|
+
import { Tag } from '../../core/Tag/Tag.js';
|
|
9
|
+
import { inputTagTheme } from './InputTag.theme.js';
|
|
10
|
+
import { useSelectPortal } from '../../../hooks/useSelectPortal.js';
|
|
11
|
+
import { createDropdownAnimations } from '../Select/Select.animations.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Hook to detect dark mode state from document
|
|
15
|
+
*/
|
|
16
|
+
const useDarkMode = () => {
|
|
17
|
+
const [isDark, setIsDark] = useState(false);
|
|
18
|
+
useEffect(() => {
|
|
19
|
+
if (typeof document === "undefined")
|
|
20
|
+
return;
|
|
21
|
+
const checkDarkMode = () => {
|
|
22
|
+
const hasDarkClass = document.documentElement.classList.contains("dark") ||
|
|
23
|
+
document.body.classList.contains("dark") ||
|
|
24
|
+
document.querySelector(".dark") !== null;
|
|
25
|
+
setIsDark(hasDarkClass);
|
|
26
|
+
};
|
|
27
|
+
checkDarkMode();
|
|
28
|
+
const observer = new MutationObserver(checkDarkMode);
|
|
29
|
+
observer.observe(document.documentElement, {
|
|
30
|
+
attributes: true,
|
|
31
|
+
attributeFilter: ["class"],
|
|
32
|
+
});
|
|
33
|
+
observer.observe(document.body, {
|
|
34
|
+
attributes: true,
|
|
35
|
+
attributeFilter: ["class"],
|
|
36
|
+
});
|
|
37
|
+
return () => observer.disconnect();
|
|
38
|
+
}, []);
|
|
39
|
+
return isDark;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Default filter function for suggestions
|
|
43
|
+
*/
|
|
44
|
+
const defaultFilterFn = (option, inputValue) => {
|
|
45
|
+
return option.label.toLowerCase().includes(inputValue.toLowerCase());
|
|
46
|
+
};
|
|
47
|
+
const InputTagOptions = ({ isOpen, onClose, triggerRef, filteredOptions, selectedTags, highlightedIndex, setHighlightedIndex, onSelectOption, showSelectedIndicator, portal, placement, offset, emptyMessage, className, theme, }) => {
|
|
48
|
+
var _a;
|
|
49
|
+
const optionsRef = useRef(null);
|
|
50
|
+
const isDarkMode = useDarkMode();
|
|
51
|
+
// Use enhanced dropdown positioning hook
|
|
52
|
+
const triggerWidth = ((_a = triggerRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) || 0;
|
|
53
|
+
const { position, cssVariables, contentRef, isFlipped } = useSelectPortal({
|
|
54
|
+
triggerRef,
|
|
55
|
+
isOpen,
|
|
56
|
+
placement,
|
|
57
|
+
offset,
|
|
58
|
+
estimatedWidth: triggerWidth,
|
|
59
|
+
});
|
|
60
|
+
const animations = createDropdownAnimations(isFlipped);
|
|
61
|
+
// Combine refs
|
|
62
|
+
const combinedRef = useCallback((el) => {
|
|
63
|
+
optionsRef.current =
|
|
64
|
+
el;
|
|
65
|
+
if (contentRef &&
|
|
66
|
+
typeof contentRef === "object" &&
|
|
67
|
+
"current" in contentRef) {
|
|
68
|
+
contentRef.current = el;
|
|
69
|
+
}
|
|
70
|
+
}, [contentRef]);
|
|
71
|
+
// Handle click outside
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (!isOpen)
|
|
74
|
+
return;
|
|
75
|
+
const handleClickOutside = (event) => {
|
|
76
|
+
const currentContentRef = portal ? contentRef.current : optionsRef.current;
|
|
77
|
+
if (currentContentRef &&
|
|
78
|
+
!currentContentRef.contains(event.target) &&
|
|
79
|
+
triggerRef.current &&
|
|
80
|
+
!triggerRef.current.contains(event.target)) {
|
|
81
|
+
onClose();
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
85
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
86
|
+
}, [isOpen, onClose, portal, contentRef, optionsRef, triggerRef]);
|
|
87
|
+
const optionsContent = (React__default.createElement(AnimatePresence, { mode: "wait" }, isOpen && (React__default.createElement(motion.div, { ref: combinedRef, role: "listbox", className: cn(theme.dropdownStyle, className), style: {
|
|
88
|
+
...cssVariables,
|
|
89
|
+
overscrollBehavior: "contain",
|
|
90
|
+
...(portal
|
|
91
|
+
? {
|
|
92
|
+
top: `${position.top}px`,
|
|
93
|
+
left: `${position.left}px`,
|
|
94
|
+
width: `${position.width}px`,
|
|
95
|
+
}
|
|
96
|
+
: {}),
|
|
97
|
+
}, tabIndex: -1, variants: animations, initial: "initial", animate: "visible", exit: "exit" }, filteredOptions.length === 0 ? (React__default.createElement("div", { className: theme.emptyMessageStyle }, emptyMessage)) : (filteredOptions.map((option, index) => {
|
|
98
|
+
const isSelected = selectedTags.includes(option.value);
|
|
99
|
+
const isHighlighted = index === highlightedIndex;
|
|
100
|
+
const isCreateOption = option.id === "__create__";
|
|
101
|
+
return (React__default.createElement("div", { key: option.id, role: "option", "aria-selected": isSelected, "aria-disabled": option.disabled, "data-option-index": index, className: cn(theme.optionStyle, isHighlighted && theme.highlightedOptionStyle, option.disabled && theme.disabledOptionStyle, isCreateOption && theme.createOptionStyle), onMouseEnter: () => !option.disabled && setHighlightedIndex(index), onClick: () => {
|
|
102
|
+
if (!option.disabled) {
|
|
103
|
+
onSelectOption(option, isCreateOption);
|
|
104
|
+
}
|
|
105
|
+
} }, isCreateOption ? (React__default.createElement("span", { className: "flex items-center gap-1" },
|
|
106
|
+
React__default.createElement(PlusIcon, { className: "size-4 shrink-0", strokeWidth: 2 }),
|
|
107
|
+
React__default.createElement("span", { className: "block truncate" }, option.label))) : (React__default.createElement("span", { className: "flex items-center justify-between gap-2" },
|
|
108
|
+
React__default.createElement("span", { className: "block truncate" }, option.label),
|
|
109
|
+
showSelectedIndicator && isSelected && (React__default.createElement(CheckIcon, { className: "size-4 shrink-0 text-[var(--color-primary)]", strokeWidth: 2 }))))));
|
|
110
|
+
}))))));
|
|
111
|
+
if (portal && typeof document !== "undefined") {
|
|
112
|
+
return createPortal(React__default.createElement("div", { className: isDarkMode ? "dark" : undefined }, optionsContent), document.body);
|
|
113
|
+
}
|
|
114
|
+
return optionsContent;
|
|
115
|
+
};
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// Main InputTag Component
|
|
118
|
+
// ============================================================================
|
|
119
|
+
/**
|
|
120
|
+
* InputTag component for entering multiple tags/keywords.
|
|
121
|
+
*
|
|
122
|
+
* Supports:
|
|
123
|
+
* - Enter key to add tag
|
|
124
|
+
* - Comma to add tag
|
|
125
|
+
* - Paste multiple comma-separated values
|
|
126
|
+
* - Backspace to remove last tag (when input is empty)
|
|
127
|
+
* - Click X to remove individual tags
|
|
128
|
+
* - Optional dropdown suggestions with filtering
|
|
129
|
+
*
|
|
130
|
+
* @component
|
|
131
|
+
* @example
|
|
132
|
+
* ```tsx
|
|
133
|
+
* // Basic free-form tags (uncontrolled)
|
|
134
|
+
* <InputTag
|
|
135
|
+
* defaultValue={['JavaScript', 'React']}
|
|
136
|
+
* onChange={(tags) => console.log(tags)}
|
|
137
|
+
* />
|
|
138
|
+
*
|
|
139
|
+
* // Controlled with max tags
|
|
140
|
+
* <InputTag
|
|
141
|
+
* value={tags}
|
|
142
|
+
* onChange={setTags}
|
|
143
|
+
* maxTags={5}
|
|
144
|
+
* />
|
|
145
|
+
*
|
|
146
|
+
* // With suggestions dropdown
|
|
147
|
+
* <InputTag
|
|
148
|
+
* suggestions={[
|
|
149
|
+
* { id: '1', label: 'React', value: 'react' },
|
|
150
|
+
* { id: '2', label: 'Vue', value: 'vue' },
|
|
151
|
+
* ]}
|
|
152
|
+
* creatable={true}
|
|
153
|
+
* />
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
const InputTag = React__default.forwardRef(({ value, defaultValue = [], onChange, maxTags, allowDuplicates = false, size = "md", state = "default", label, helperText, placeholder = "Add a tag...", disabled = false, className, inputClassName, tagsContainerClassName, tagClassName, theme: customTheme,
|
|
157
|
+
// Dropdown props
|
|
158
|
+
suggestions, creatable = true, openOnFocus = true, filterFn = defaultFilterFn, emptyMessage = "No suggestions", createLabel, hideSelected = true, showSelectedIndicator = false, portal = true, placement = "bottom-start", offset = 8, dropdownClassName, ...props }, ref) => {
|
|
159
|
+
// Merge default theme with custom theme overrides
|
|
160
|
+
const theme = {
|
|
161
|
+
...inputTagTheme,
|
|
162
|
+
...(customTheme || {}),
|
|
163
|
+
};
|
|
164
|
+
// Determine if dropdown mode is enabled
|
|
165
|
+
const hasDropdown = suggestions !== undefined && suggestions.length > 0;
|
|
166
|
+
// Internal state for tags (controlled/uncontrolled pattern)
|
|
167
|
+
const [internalTags, setInternalTags] = useState(defaultValue || []);
|
|
168
|
+
const [inputValue, setInputValue] = useState("");
|
|
169
|
+
const [duplicateWarning, setDuplicateWarning] = useState(null);
|
|
170
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
171
|
+
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
|
172
|
+
const inputRef = useRef(null);
|
|
173
|
+
const wrapperRef = useRef(null);
|
|
174
|
+
const warningTimeoutRef = useRef(null);
|
|
175
|
+
// Determine if component is controlled
|
|
176
|
+
const isControlled = value !== undefined;
|
|
177
|
+
const tags = isControlled ? value : internalTags;
|
|
178
|
+
// Sync internal state when controlled value changes
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
if (isControlled) {
|
|
181
|
+
setInternalTags(value);
|
|
182
|
+
}
|
|
183
|
+
}, [isControlled, value]);
|
|
184
|
+
// Cleanup warning timeout on unmount
|
|
185
|
+
useEffect(() => {
|
|
186
|
+
return () => {
|
|
187
|
+
if (warningTimeoutRef.current) {
|
|
188
|
+
clearTimeout(warningTimeoutRef.current);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
}, []);
|
|
192
|
+
// Filter suggestions based on input and already-selected tags
|
|
193
|
+
const filteredOptions = useMemo(() => {
|
|
194
|
+
if (!hasDropdown || !suggestions)
|
|
195
|
+
return [];
|
|
196
|
+
let options = suggestions;
|
|
197
|
+
// Filter by input value
|
|
198
|
+
if (inputValue.trim()) {
|
|
199
|
+
options = options.filter((opt) => filterFn(opt, inputValue));
|
|
200
|
+
}
|
|
201
|
+
// Hide already selected tags
|
|
202
|
+
if (hideSelected) {
|
|
203
|
+
options = options.filter((opt) => !tags.includes(opt.value));
|
|
204
|
+
}
|
|
205
|
+
// Check if we should show create option
|
|
206
|
+
const shouldShowCreate = creatable &&
|
|
207
|
+
inputValue.trim() !== "" &&
|
|
208
|
+
!options.some((opt) => opt.label.toLowerCase() === inputValue.toLowerCase()) &&
|
|
209
|
+
!tags.includes(inputValue.trim());
|
|
210
|
+
if (shouldShowCreate) {
|
|
211
|
+
const createOptionLabel = typeof createLabel === "function"
|
|
212
|
+
? createLabel(inputValue)
|
|
213
|
+
: createLabel || `Create "${inputValue}"`;
|
|
214
|
+
return [
|
|
215
|
+
{
|
|
216
|
+
id: "__create__",
|
|
217
|
+
label: createOptionLabel,
|
|
218
|
+
value: inputValue.trim(),
|
|
219
|
+
},
|
|
220
|
+
...options,
|
|
221
|
+
];
|
|
222
|
+
}
|
|
223
|
+
return options;
|
|
224
|
+
}, [
|
|
225
|
+
hasDropdown,
|
|
226
|
+
suggestions,
|
|
227
|
+
inputValue,
|
|
228
|
+
tags,
|
|
229
|
+
hideSelected,
|
|
230
|
+
creatable,
|
|
231
|
+
createLabel,
|
|
232
|
+
filterFn,
|
|
233
|
+
]);
|
|
234
|
+
// Reset highlighted index when options change
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
setHighlightedIndex(-1);
|
|
237
|
+
}, [filteredOptions.length, inputValue]);
|
|
238
|
+
// Show duplicate warning temporarily
|
|
239
|
+
const showDuplicateWarning = useCallback((tagName) => {
|
|
240
|
+
if (warningTimeoutRef.current) {
|
|
241
|
+
clearTimeout(warningTimeoutRef.current);
|
|
242
|
+
}
|
|
243
|
+
setDuplicateWarning(`"${tagName}" already exists`);
|
|
244
|
+
warningTimeoutRef.current = setTimeout(() => {
|
|
245
|
+
setDuplicateWarning(null);
|
|
246
|
+
}, 2000);
|
|
247
|
+
}, []);
|
|
248
|
+
// Update tags (handles both controlled and uncontrolled modes)
|
|
249
|
+
const updateTags = useCallback((newTags) => {
|
|
250
|
+
if (!isControlled) {
|
|
251
|
+
setInternalTags(newTags);
|
|
252
|
+
}
|
|
253
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(newTags);
|
|
254
|
+
}, [isControlled, onChange]);
|
|
255
|
+
// Add a single tag
|
|
256
|
+
const addTag = useCallback((tagValue) => {
|
|
257
|
+
const trimmedTag = tagValue.trim();
|
|
258
|
+
if (!trimmedTag)
|
|
259
|
+
return false;
|
|
260
|
+
// Check for max tags
|
|
261
|
+
if (maxTags && tags.length >= maxTags)
|
|
262
|
+
return false;
|
|
263
|
+
// Check for duplicates
|
|
264
|
+
if (!allowDuplicates && tags.includes(trimmedTag)) {
|
|
265
|
+
showDuplicateWarning(trimmedTag);
|
|
266
|
+
return false;
|
|
267
|
+
}
|
|
268
|
+
updateTags([...tags, trimmedTag]);
|
|
269
|
+
return true;
|
|
270
|
+
}, [tags, maxTags, allowDuplicates, updateTags, showDuplicateWarning]);
|
|
271
|
+
// Remove a tag by index
|
|
272
|
+
const removeTag = useCallback((index) => {
|
|
273
|
+
const newTags = tags.filter((_, i) => i !== index);
|
|
274
|
+
updateTags(newTags);
|
|
275
|
+
}, [tags, updateTags]);
|
|
276
|
+
// Handle input change
|
|
277
|
+
const handleInputChange = useCallback((e) => {
|
|
278
|
+
const newValue = e.target.value;
|
|
279
|
+
setInputValue(newValue);
|
|
280
|
+
// Clear duplicate warning when user starts typing
|
|
281
|
+
if (duplicateWarning) {
|
|
282
|
+
setDuplicateWarning(null);
|
|
283
|
+
if (warningTimeoutRef.current) {
|
|
284
|
+
clearTimeout(warningTimeoutRef.current);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
// Open dropdown when typing (if suggestions mode)
|
|
288
|
+
if (hasDropdown && newValue && !isOpen) {
|
|
289
|
+
setIsOpen(true);
|
|
290
|
+
}
|
|
291
|
+
}, [duplicateWarning, hasDropdown, isOpen]);
|
|
292
|
+
// Add all tags from input (handles comma-separated values)
|
|
293
|
+
const addTagsFromInput = useCallback(() => {
|
|
294
|
+
if (!inputValue.trim())
|
|
295
|
+
return false;
|
|
296
|
+
// Split by comma and collect valid tags
|
|
297
|
+
const newTagsToAdd = inputValue
|
|
298
|
+
.split(",")
|
|
299
|
+
.map((t) => t.trim())
|
|
300
|
+
.filter(Boolean);
|
|
301
|
+
// Filter out duplicates and respect max tags
|
|
302
|
+
const validTags = [];
|
|
303
|
+
const duplicateTags = [];
|
|
304
|
+
for (const tag of newTagsToAdd) {
|
|
305
|
+
// Check for max tags
|
|
306
|
+
if (maxTags && tags.length + validTags.length >= maxTags)
|
|
307
|
+
break;
|
|
308
|
+
// Check for duplicates (against both existing and new tags being added)
|
|
309
|
+
if (!allowDuplicates &&
|
|
310
|
+
(tags.includes(tag) || validTags.includes(tag))) {
|
|
311
|
+
if (!duplicateTags.includes(tag)) {
|
|
312
|
+
duplicateTags.push(tag);
|
|
313
|
+
}
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
validTags.push(tag);
|
|
317
|
+
}
|
|
318
|
+
// Show warning for duplicates
|
|
319
|
+
if (duplicateTags.length > 0) {
|
|
320
|
+
showDuplicateWarning(duplicateTags[0]);
|
|
321
|
+
}
|
|
322
|
+
if (validTags.length > 0) {
|
|
323
|
+
updateTags([...tags, ...validTags]);
|
|
324
|
+
setInputValue("");
|
|
325
|
+
return true;
|
|
326
|
+
}
|
|
327
|
+
// Clear input even if only duplicates were entered
|
|
328
|
+
if (duplicateTags.length > 0) {
|
|
329
|
+
setInputValue("");
|
|
330
|
+
}
|
|
331
|
+
return false;
|
|
332
|
+
}, [
|
|
333
|
+
inputValue,
|
|
334
|
+
tags,
|
|
335
|
+
maxTags,
|
|
336
|
+
allowDuplicates,
|
|
337
|
+
updateTags,
|
|
338
|
+
showDuplicateWarning,
|
|
339
|
+
]);
|
|
340
|
+
// Handle selecting an option from dropdown
|
|
341
|
+
const handleSelectOption = useCallback((option, isCreate) => {
|
|
342
|
+
var _a;
|
|
343
|
+
const added = addTag(option.value);
|
|
344
|
+
if (added) {
|
|
345
|
+
setInputValue("");
|
|
346
|
+
setIsOpen(false);
|
|
347
|
+
setHighlightedIndex(-1);
|
|
348
|
+
(_a = inputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
349
|
+
}
|
|
350
|
+
}, [addTag]);
|
|
351
|
+
// Handle key down
|
|
352
|
+
const handleKeyDown = useCallback((e) => {
|
|
353
|
+
// Handle dropdown navigation when open
|
|
354
|
+
if (hasDropdown && isOpen && filteredOptions.length > 0) {
|
|
355
|
+
switch (e.key) {
|
|
356
|
+
case "ArrowDown":
|
|
357
|
+
e.preventDefault();
|
|
358
|
+
setHighlightedIndex((prev) => prev >= filteredOptions.length - 1 ? 0 : prev + 1);
|
|
359
|
+
return;
|
|
360
|
+
case "ArrowUp":
|
|
361
|
+
e.preventDefault();
|
|
362
|
+
setHighlightedIndex((prev) => prev <= 0 ? filteredOptions.length - 1 : prev - 1);
|
|
363
|
+
return;
|
|
364
|
+
case "Enter":
|
|
365
|
+
e.preventDefault();
|
|
366
|
+
if (highlightedIndex >= 0 &&
|
|
367
|
+
highlightedIndex < filteredOptions.length) {
|
|
368
|
+
const option = filteredOptions[highlightedIndex];
|
|
369
|
+
const isCreate = option.id === "__create__";
|
|
370
|
+
handleSelectOption(option, isCreate);
|
|
371
|
+
}
|
|
372
|
+
else if (creatable || !hasDropdown) {
|
|
373
|
+
// Add typed value if creatable
|
|
374
|
+
addTagsFromInput();
|
|
375
|
+
}
|
|
376
|
+
return;
|
|
377
|
+
case "Escape":
|
|
378
|
+
e.preventDefault();
|
|
379
|
+
setIsOpen(false);
|
|
380
|
+
setHighlightedIndex(-1);
|
|
381
|
+
return;
|
|
382
|
+
case "Tab":
|
|
383
|
+
setIsOpen(false);
|
|
384
|
+
setHighlightedIndex(-1);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Standard tag input behavior
|
|
389
|
+
if (e.key === "Enter") {
|
|
390
|
+
e.preventDefault();
|
|
391
|
+
if (hasDropdown && isOpen && highlightedIndex === -1 && creatable) {
|
|
392
|
+
addTagsFromInput();
|
|
393
|
+
}
|
|
394
|
+
else if (!hasDropdown || creatable) {
|
|
395
|
+
addTagsFromInput();
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
else if (e.key === "Backspace" &&
|
|
399
|
+
inputValue === "" &&
|
|
400
|
+
tags.length > 0) {
|
|
401
|
+
// Remove last tag when backspace is pressed on empty input
|
|
402
|
+
removeTag(tags.length - 1);
|
|
403
|
+
}
|
|
404
|
+
else if (e.key === "ArrowDown" && hasDropdown && !isOpen) {
|
|
405
|
+
// Open dropdown on arrow down
|
|
406
|
+
e.preventDefault();
|
|
407
|
+
setIsOpen(true);
|
|
408
|
+
}
|
|
409
|
+
}, [
|
|
410
|
+
hasDropdown,
|
|
411
|
+
isOpen,
|
|
412
|
+
filteredOptions,
|
|
413
|
+
highlightedIndex,
|
|
414
|
+
creatable,
|
|
415
|
+
handleSelectOption,
|
|
416
|
+
addTagsFromInput,
|
|
417
|
+
inputValue,
|
|
418
|
+
tags.length,
|
|
419
|
+
removeTag,
|
|
420
|
+
]);
|
|
421
|
+
// Handle paste
|
|
422
|
+
const handlePaste = useCallback((e) => {
|
|
423
|
+
e.preventDefault();
|
|
424
|
+
const pastedText = e.clipboardData.getData("text");
|
|
425
|
+
// Split by comma and add each tag
|
|
426
|
+
const pastedTags = pastedText
|
|
427
|
+
.split(",")
|
|
428
|
+
.map((t) => t.trim())
|
|
429
|
+
.filter(Boolean);
|
|
430
|
+
if (pastedTags.length > 0) {
|
|
431
|
+
pastedTags.forEach((tag) => addTag(tag));
|
|
432
|
+
setInputValue("");
|
|
433
|
+
}
|
|
434
|
+
}, [addTag]);
|
|
435
|
+
// Handle focus
|
|
436
|
+
const handleFocus = useCallback(() => {
|
|
437
|
+
if (hasDropdown && openOnFocus && !disabled) {
|
|
438
|
+
setIsOpen(true);
|
|
439
|
+
}
|
|
440
|
+
}, [hasDropdown, openOnFocus, disabled]);
|
|
441
|
+
// Handle blur - add current input as tags (only if no dropdown or closed)
|
|
442
|
+
const handleBlur = useCallback(() => {
|
|
443
|
+
// Delay to allow click events on dropdown options
|
|
444
|
+
setTimeout(() => {
|
|
445
|
+
if (!hasDropdown || !isOpen) {
|
|
446
|
+
if (creatable || !hasDropdown) {
|
|
447
|
+
addTagsFromInput();
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}, 150);
|
|
451
|
+
}, [hasDropdown, isOpen, creatable, addTagsFromInput]);
|
|
452
|
+
// Close dropdown handler
|
|
453
|
+
const handleCloseDropdown = useCallback(() => {
|
|
454
|
+
setIsOpen(false);
|
|
455
|
+
setHighlightedIndex(-1);
|
|
456
|
+
}, []);
|
|
457
|
+
// Check if input should be disabled
|
|
458
|
+
const isInputDisabled = disabled || (maxTags !== undefined && tags.length >= maxTags);
|
|
459
|
+
// Scroll highlighted option into view
|
|
460
|
+
useEffect(() => {
|
|
461
|
+
if (highlightedIndex >= 0 && isOpen) {
|
|
462
|
+
const optionElement = document.querySelector(`[data-option-index="${highlightedIndex}"]`);
|
|
463
|
+
if (optionElement) {
|
|
464
|
+
optionElement.scrollIntoView({ block: "nearest" });
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}, [highlightedIndex, isOpen]);
|
|
468
|
+
// Helper text to display (for dropdown mode, we render it separately)
|
|
469
|
+
const displayHelperText = duplicateWarning || helperText;
|
|
470
|
+
return (React__default.createElement("div", { ref: ref, className: cn("w-full", className), ...props },
|
|
471
|
+
React__default.createElement("div", { ref: wrapperRef, className: "relative" },
|
|
472
|
+
React__default.createElement(Input, { ref: inputRef, value: inputValue, onChange: handleInputChange, onKeyDown: handleKeyDown, onPaste: handlePaste, onFocus: handleFocus, onBlur: handleBlur, iconStart: React__default.createElement(TagIcon, { className: "size-4" }), placeholder: maxTags && tags.length >= maxTags
|
|
473
|
+
? `Maximum ${maxTags} tags reached`
|
|
474
|
+
: placeholder, size: size, state: state, label: label,
|
|
475
|
+
// Only pass helperText to Input when NOT in dropdown mode
|
|
476
|
+
// Otherwise the dropdown positions below the helper text
|
|
477
|
+
helperText: hasDropdown ? undefined : displayHelperText, disabled: isInputDisabled, className: inputClassName, "aria-expanded": hasDropdown ? isOpen : undefined, "aria-haspopup": hasDropdown ? "listbox" : undefined, "aria-autocomplete": hasDropdown ? "list" : undefined }),
|
|
478
|
+
hasDropdown && (React__default.createElement(InputTagOptions, { isOpen: isOpen, onClose: handleCloseDropdown, triggerRef: wrapperRef, filteredOptions: filteredOptions, selectedTags: tags, highlightedIndex: highlightedIndex, setHighlightedIndex: setHighlightedIndex, onSelectOption: handleSelectOption, showSelectedIndicator: showSelectedIndicator, portal: portal, placement: placement, offset: offset, emptyMessage: emptyMessage, className: dropdownClassName, theme: theme }))),
|
|
479
|
+
hasDropdown && displayHelperText && (React__default.createElement("p", { className: "text-xs text-[var(--color-text-muted)] mt-1.5" }, displayHelperText)),
|
|
480
|
+
React__default.createElement("div", { className: cn(theme.tagsContainerStyle, "empty:hidden", tagsContainerClassName) },
|
|
481
|
+
React__default.createElement(AnimatePresence, { mode: "popLayout" }, tags.map((tag, index) => {
|
|
482
|
+
// Generate stable key: count occurrences of this tag before current index
|
|
483
|
+
const occurrenceIndex = tags
|
|
484
|
+
.slice(0, index)
|
|
485
|
+
.filter((t) => t === tag).length;
|
|
486
|
+
return (React__default.createElement(Tag, { key: `${tag}-${occurrenceIndex}`, size: size, onRemove: () => removeTag(index), disabled: disabled, className: tagClassName }, tag));
|
|
487
|
+
})))));
|
|
488
|
+
});
|
|
489
|
+
InputTag.displayName = "InputTag";
|
|
490
|
+
|
|
491
|
+
export { InputTag };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { formsBaseTheme } from '../forms.theme.js';
|
|
2
|
+
|
|
3
|
+
const inputTagTheme = {
|
|
4
|
+
// Tags container style
|
|
5
|
+
tagsContainerStyle: "flex flex-wrap gap-2 mt-2",
|
|
6
|
+
// Dropdown styles - reuse from formsBaseTheme for consistency
|
|
7
|
+
dropdownStyle: formsBaseTheme.dropdownStyles.container + " py-1",
|
|
8
|
+
optionStyle: formsBaseTheme.dropdownStyles.option,
|
|
9
|
+
selectedOptionStyle: formsBaseTheme.dropdownStyles.optionSelected,
|
|
10
|
+
highlightedOptionStyle: formsBaseTheme.dropdownStyles.optionHighlighted,
|
|
11
|
+
disabledOptionStyle: formsBaseTheme.dropdownStyles.optionDisabled,
|
|
12
|
+
emptyMessageStyle: formsBaseTheme.dropdownStyles.emptyMessage,
|
|
13
|
+
createOptionStyle: formsBaseTheme.dropdownStyles.optionCreate,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export { inputTagTheme };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { InputState } from "../Input";
|
|
3
|
+
import { TagSize } from "../../core/Tag";
|
|
4
|
+
/**
|
|
5
|
+
* Option type for InputTag suggestions dropdown
|
|
6
|
+
*/
|
|
7
|
+
export interface InputTagOption {
|
|
8
|
+
/** Unique identifier for the option */
|
|
9
|
+
id: string;
|
|
10
|
+
/** Display label shown in dropdown */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Value stored when option is selected (defaults to label if not provided) */
|
|
13
|
+
value: string;
|
|
14
|
+
/** Whether option is disabled */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* InputTag component props
|
|
19
|
+
*/
|
|
20
|
+
export interface InputTagProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange" | "defaultValue"> {
|
|
21
|
+
/** Controlled value - array of tag strings */
|
|
22
|
+
value?: string[];
|
|
23
|
+
/** Default value for uncontrolled mode */
|
|
24
|
+
defaultValue?: string[];
|
|
25
|
+
/** Callback when tags change */
|
|
26
|
+
onChange?: (tags: string[]) => void;
|
|
27
|
+
/** Maximum number of tags allowed */
|
|
28
|
+
maxTags?: number;
|
|
29
|
+
/** Whether to allow duplicate tags (default: false) */
|
|
30
|
+
allowDuplicates?: boolean;
|
|
31
|
+
/** The size of the input and tags (sm or md only) */
|
|
32
|
+
size?: InputTagSize;
|
|
33
|
+
/** The state of the input */
|
|
34
|
+
state?: InputTagState;
|
|
35
|
+
/** Label for the input */
|
|
36
|
+
label?: string;
|
|
37
|
+
/** Helper text displayed below the tags */
|
|
38
|
+
helperText?: string;
|
|
39
|
+
/** Placeholder text for the input */
|
|
40
|
+
placeholder?: string;
|
|
41
|
+
/** Whether the input is disabled */
|
|
42
|
+
disabled?: boolean;
|
|
43
|
+
/** Additional class name for the wrapper */
|
|
44
|
+
className?: string;
|
|
45
|
+
/** Additional class name for the input */
|
|
46
|
+
inputClassName?: string;
|
|
47
|
+
/** Additional class name for the tags container */
|
|
48
|
+
tagsContainerClassName?: string;
|
|
49
|
+
/** Additional class name for individual tags */
|
|
50
|
+
tagClassName?: string;
|
|
51
|
+
/** Theme overrides */
|
|
52
|
+
theme?: InputTagThemeOverrides;
|
|
53
|
+
/** Array of suggestions to show in dropdown. When provided, enables dropdown mode */
|
|
54
|
+
suggestions?: InputTagOption[];
|
|
55
|
+
/** Allow free-form tags not in suggestions (default: true) */
|
|
56
|
+
creatable?: boolean;
|
|
57
|
+
/** Open dropdown on focus when suggestions are provided (default: true) */
|
|
58
|
+
openOnFocus?: boolean;
|
|
59
|
+
/** Custom filter function for suggestions */
|
|
60
|
+
filterFn?: (option: InputTagOption, inputValue: string) => boolean;
|
|
61
|
+
/** Message shown when no suggestions match (default: "No suggestions") */
|
|
62
|
+
emptyMessage?: string;
|
|
63
|
+
/** Label for create option - can be string or function (default: 'Create "{input}"') */
|
|
64
|
+
createLabel?: string | ((inputValue: string) => string);
|
|
65
|
+
/** Hide already-selected tags from suggestions (default: true) */
|
|
66
|
+
hideSelected?: boolean;
|
|
67
|
+
/** Show checkmark indicator for selected items in dropdown (default: false) */
|
|
68
|
+
showSelectedIndicator?: boolean;
|
|
69
|
+
/** Use portal for dropdown positioning (default: true) */
|
|
70
|
+
portal?: boolean;
|
|
71
|
+
/** Dropdown placement relative to trigger */
|
|
72
|
+
placement?: "bottom-start" | "bottom-end" | "top-start" | "top-end";
|
|
73
|
+
/** Offset from trigger in pixels (default: 8) */
|
|
74
|
+
offset?: number;
|
|
75
|
+
/** Additional class name for the dropdown */
|
|
76
|
+
dropdownClassName?: string;
|
|
77
|
+
}
|
|
78
|
+
export type InputTagSize = TagSize;
|
|
79
|
+
export type InputTagState = InputState;
|
|
80
|
+
export interface InputTagTheme {
|
|
81
|
+
/** Styles for the tags container */
|
|
82
|
+
tagsContainerStyle: string;
|
|
83
|
+
/** Dropdown container style */
|
|
84
|
+
dropdownStyle: string;
|
|
85
|
+
/** Individual option style */
|
|
86
|
+
optionStyle: string;
|
|
87
|
+
/** Selected option style modifier */
|
|
88
|
+
selectedOptionStyle: string;
|
|
89
|
+
/** Highlighted option style (keyboard navigation) */
|
|
90
|
+
highlightedOptionStyle: string;
|
|
91
|
+
/** Disabled option style */
|
|
92
|
+
disabledOptionStyle: string;
|
|
93
|
+
/** Empty state message style */
|
|
94
|
+
emptyMessageStyle: string;
|
|
95
|
+
/** Create option style */
|
|
96
|
+
createOptionStyle: string;
|
|
97
|
+
}
|
|
98
|
+
export interface InputTagThemeOverrides {
|
|
99
|
+
tagsContainerStyle?: string;
|
|
100
|
+
dropdownStyle?: string;
|
|
101
|
+
optionStyle?: string;
|
|
102
|
+
selectedOptionStyle?: string;
|
|
103
|
+
highlightedOptionStyle?: string;
|
|
104
|
+
disabledOptionStyle?: string;
|
|
105
|
+
emptyMessageStyle?: string;
|
|
106
|
+
createOptionStyle?: string;
|
|
107
|
+
}
|