@flikk/ui 1.0.0-beta.30 → 1.0.0-beta.31
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/CHANGELOG.md +97 -0
- package/dist/components/ai/AgentStatus/AgentStatus.d.ts +3 -0
- package/dist/components/ai/AgentStatus/AgentStatus.js +144 -0
- package/dist/components/ai/AgentStatus/AgentStatus.theme.d.ts +2 -0
- package/dist/components/ai/AgentStatus/AgentStatus.theme.js +37 -0
- package/dist/components/ai/AgentStatus/AgentStatus.types.d.ts +116 -0
- package/dist/components/ai/AgentStatus/index.d.ts +3 -0
- package/dist/components/ai/CodeBlock/CodeBlock.js +3 -3
- package/dist/components/ai/CodeBlock/CodeBlock.theme.js +1 -1
- package/dist/components/ai/PromptInput/PromptInput.js +13 -14
- package/dist/components/ai/PromptInput/PromptInput.theme.js +1 -1
- package/dist/components/ai/PromptInput/VoiceRecorder.js +3 -3
- package/dist/components/ai/Reasoning/Reasoning.d.ts +5 -0
- package/dist/components/ai/Reasoning/Reasoning.js +16 -0
- package/dist/components/ai/Reasoning/Reasoning.types.d.ts +20 -0
- package/dist/components/ai/Reasoning/index.d.ts +2 -0
- package/dist/components/ai/SourceCitation/SourceCitation.d.ts +4 -0
- package/dist/components/ai/SourceCitation/SourceCitation.js +29 -0
- package/dist/components/ai/SourceCitation/SourceCitation.theme.d.ts +2 -0
- package/dist/components/ai/SourceCitation/SourceCitation.theme.js +23 -0
- package/dist/components/ai/SourceCitation/SourceCitation.types.d.ts +55 -0
- package/dist/components/ai/SourceCitation/index.d.ts +3 -0
- package/dist/components/ai/StreamingResponse/ErrorDisplay.js +2 -2
- package/dist/components/ai/StreamingResponse/StreamingResponse.js +1 -3
- package/dist/components/ai/StreamingResponse/StreamingResponse.theme.js +3 -1
- package/dist/components/ai/TokenCounter/TokenCounter.js +2 -2
- package/dist/components/ai/index.d.ts +3 -0
- package/dist/components/ai/index.js +6 -3
- package/dist/components/ai/shared/StatusIndicator.d.ts +8 -0
- package/dist/components/ai/shared/StatusIndicator.js +34 -0
- package/dist/components/canvas/CanvasToolbar.js +2 -2
- package/dist/components/canvas/NodeControls.js +2 -2
- package/dist/components/canvas/NodeRenderer.js +2 -2
- package/dist/components/charts/ActivityRings/ActivityRings.js +2 -2
- package/dist/components/charts/AreaChart/AreaChart.js +21 -32
- package/dist/components/charts/AreaChart/AreaChart.theme.d.ts +1 -6
- package/dist/components/charts/AreaChart/AreaChart.theme.js +7 -19
- package/dist/components/charts/AreaChart/AreaChart.types.d.ts +18 -11
- package/dist/components/charts/AreaChart/AreaChart.types.js +10 -3
- package/dist/components/charts/AreaChart/index.d.ts +1 -1
- package/dist/components/charts/BarChart/BarChart.js +9 -28
- package/dist/components/charts/BarChart/BarChart.types.d.ts +20 -16
- package/dist/components/charts/BarChart/BarChart.types.js +11 -4
- package/dist/components/charts/ComboChart/BarRenderer.js +8 -5
- package/dist/components/charts/ComboChart/ComboChart.js +11 -24
- package/dist/components/charts/ComboChart/ComboChart.types.d.ts +15 -13
- package/dist/components/charts/ComboChart/ComboChart.types.js +14 -2
- package/dist/components/charts/ComboChart/LineRenderer.js +15 -10
- package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.d.ts +16 -0
- package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.js +237 -0
- package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.types.d.ts +62 -0
- package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.types.js +21 -0
- package/dist/components/charts/HorizontalBarChart/index.d.ts +3 -0
- package/dist/components/charts/LineChart/LineChart.js +9 -26
- package/dist/components/charts/LineChart/LineChart.types.d.ts +11 -14
- package/dist/components/charts/LineChart/LineChart.types.js +11 -3
- package/dist/components/charts/LollipopChart/LollipopChart.d.ts +3 -0
- package/dist/components/charts/LollipopChart/LollipopChart.js +361 -0
- package/dist/components/charts/LollipopChart/LollipopChart.types.d.ts +42 -0
- package/dist/components/charts/LollipopChart/LollipopChart.types.js +12 -0
- package/dist/components/charts/LollipopChart/index.d.ts +3 -0
- package/dist/components/charts/ScatterPlot/ScatterPlot.js +5 -4
- package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +14 -19
- package/dist/components/charts/ScatterPlot/ScatterPlot.types.js +9 -2
- package/dist/components/charts/StackedBarChart/StackedBarChart.js +150 -132
- package/dist/components/charts/StackedBarChart/StackedBarChart.types.d.ts +20 -18
- package/dist/components/charts/StackedBarChart/StackedBarChart.types.js +10 -4
- package/dist/components/charts/hooks/useChartAccessibility.d.ts +1 -1
- package/dist/components/charts/hooks/useChartAccessibility.js +3 -0
- package/dist/components/charts/hooks/useTooltipPosition.js +18 -6
- package/dist/components/charts/index.d.ts +6 -0
- package/dist/components/charts/index.js +6 -0
- package/dist/components/charts/shared/ChartAxis/CategoryYAxis.d.ts +14 -0
- package/dist/components/charts/shared/ChartAxis/CategoryYAxis.js +35 -0
- package/dist/components/charts/shared/ChartAxis/ChartAxis.types.d.ts +69 -8
- package/dist/components/charts/shared/ChartAxis/ValueXAxis.d.ts +14 -0
- package/dist/components/charts/shared/ChartAxis/ValueXAxis.js +64 -0
- package/dist/components/charts/shared/ChartAxis/XAxis.js +1 -1
- package/dist/components/charts/shared/ChartAxis/YAxis.js +2 -2
- package/dist/components/charts/shared/ChartAxis/index.d.ts +3 -1
- package/dist/components/charts/shared/ChartGrid/HorizontalGrid.d.ts +0 -2
- package/dist/components/charts/shared/ChartGrid/HorizontalGrid.js +1 -1
- package/dist/components/charts/shared/ChartGrid/VerticalGrid.d.ts +29 -0
- package/dist/components/charts/shared/ChartGrid/VerticalGrid.js +26 -0
- package/dist/components/charts/shared/ChartGrid/index.d.ts +2 -0
- package/dist/components/charts/shared/ChartMarker/ChartMarker.js +7 -1
- package/dist/components/charts/theme/chart.theme.js +18 -23
- package/dist/components/charts/types/chart.types.d.ts +9 -5
- package/dist/components/charts/types/chart.types.js +1 -7
- package/dist/components/charts/utils/animation-utils.js +1 -3
- package/dist/components/charts/utils/bar-emphasis.d.ts +32 -3
- package/dist/components/charts/utils/bar-emphasis.js +24 -8
- package/dist/components/charts/utils/index.d.ts +1 -0
- package/dist/components/charts/utils/series-color.d.ts +7 -0
- package/dist/components/charts/utils/series-color.js +20 -0
- package/dist/components/core/Accordion/Accordion.theme.js +2 -2
- package/dist/components/core/Accordion/AccordionTrigger.js +2 -2
- package/dist/components/core/Alert/Alert.animations.d.ts +2 -1
- package/dist/components/core/Alert/Alert.animations.js +21 -6
- package/dist/components/core/Alert/Alert.js +7 -7
- package/dist/components/core/Avatar/Avatar.js +50 -42
- package/dist/components/core/Avatar/Avatar.theme.d.ts +8 -4
- package/dist/components/core/Avatar/Avatar.theme.js +64 -8
- package/dist/components/core/Avatar/Avatar.types.d.ts +13 -2
- package/dist/components/core/Avatar/index.d.ts +1 -1
- package/dist/components/core/AvatarGroup/AvatarGroup.js +6 -8
- package/dist/components/core/AvatarGroup/AvatarGroup.theme.d.ts +1 -2
- package/dist/components/core/AvatarGroup/AvatarGroup.theme.js +2 -6
- package/dist/components/core/AvatarGroup/AvatarGroup.types.d.ts +0 -6
- package/dist/components/core/AvatarGroup/index.d.ts +1 -1
- package/dist/components/core/Badge/Badge.animations.js +4 -2
- package/dist/components/core/Badge/Badge.js +9 -4
- package/dist/components/core/Badge/Badge.theme.js +18 -12
- package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.d.ts +1 -4
- package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +2 -2
- package/dist/components/core/Button/Button.js +9 -17
- package/dist/components/core/Button/Button.ripple.js +2 -2
- package/dist/components/core/Button/Button.theme.d.ts +2 -0
- package/dist/components/core/Button/Button.theme.js +34 -21
- package/dist/components/core/Button/Button.types.d.ts +18 -1
- package/dist/components/core/Calendar/Calendar.js +20 -5
- package/dist/components/core/Calendar/Calendar.theme.js +14 -22
- package/dist/components/core/Calendar/Calendar.types.d.ts +0 -1
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +3 -3
- package/dist/components/core/Card/Card.theme.js +12 -4
- package/dist/components/core/Card/CardBody.js +1 -1
- package/dist/components/core/Card/CardFooter.js +1 -1
- package/dist/components/core/Card/CardHeader.js +1 -1
- package/dist/components/core/Carousel/CarouselNext.js +2 -2
- package/dist/components/core/Carousel/CarouselPrevious.js +2 -2
- package/dist/components/core/CommandPalette/CommandPalette.js +5 -4
- package/dist/components/core/CommandPalette/CommandPalette.theme.js +3 -7
- package/dist/components/core/CommandPalette/CommandPalette.types.d.ts +2 -0
- package/dist/components/core/ContextMenu/ContextMenuItem.js +2 -2
- package/dist/components/core/ContextMenu/ContextMenuSubTrigger.js +2 -2
- package/dist/components/core/Drawer/Drawer.theme.js +7 -4
- package/dist/components/core/Drawer/DrawerHeader.js +2 -2
- package/dist/components/core/Dropdown/Dropdown.theme.js +3 -1
- package/dist/components/core/Dropdown/DropdownItem.js +2 -2
- package/dist/components/core/Dropdown/DropdownTrigger.js +16 -5
- package/dist/components/core/HeroCard/HeroCard.theme.js +5 -2
- package/dist/components/core/Kbd/Kbd.theme.js +4 -2
- package/dist/components/core/Link/Link.js +6 -4
- package/dist/components/core/Message/Message.js +2 -2
- package/dist/components/core/Message/Message.theme.js +5 -7
- package/dist/components/core/Message/MessageAudio.js +2 -2
- package/dist/components/core/Message/MessageFile.js +2 -2
- package/dist/components/core/Message/MessageLink.js +2 -2
- package/dist/components/core/Message/MessageStatusIndicator.js +5 -5
- package/dist/components/core/Modal/Modal.d.ts +1 -1
- package/dist/components/core/Modal/Modal.js +2 -2
- package/dist/components/core/Modal/Modal.theme.js +9 -4
- package/dist/components/core/Modal/ModalHeader.js +2 -2
- package/dist/components/core/ModalStack/ModalStackModal.js +2 -2
- package/dist/components/core/NavItem/NavItem.js +6 -6
- package/dist/components/core/OfflineIndicator/OfflineIndicator.animations.d.ts +22 -0
- package/dist/components/core/OfflineIndicator/OfflineIndicator.animations.js +54 -0
- package/dist/components/core/OfflineIndicator/OfflineIndicator.js +23 -24
- package/dist/components/core/OfflineIndicator/OfflineIndicator.theme.js +25 -6
- package/dist/components/core/OfflineIndicator/OfflineIndicator.types.d.ts +17 -0
- package/dist/components/core/PageHeading/PageHeading.theme.js +1 -1
- package/dist/components/core/PageHeading/PageHeadingBackButton.js +2 -2
- package/dist/components/core/Pagination/Pagination.js +3 -3
- package/dist/components/core/Pagination/Pagination.theme.js +3 -3
- package/dist/components/core/Pill/Pill.js +4 -2
- package/dist/components/core/Pill/Pill.theme.js +6 -4
- package/dist/components/core/Rating/Rating.js +4 -5
- package/dist/components/core/Rating/Rating.theme.js +1 -1
- package/dist/components/core/Segmented/Segmented.theme.js +9 -6
- package/dist/components/core/Sidebar/Sidebar.types.d.ts +4 -2
- package/dist/components/core/Sidebar/SidebarHeader.js +2 -2
- package/dist/components/core/Sidebar/SidebarMobileTrigger.js +2 -2
- package/dist/components/core/Sidebar/SidebarSearch.js +3 -3
- package/dist/components/core/Sidebar/SidebarToggle.js +3 -3
- package/dist/components/core/Sidebar/SidebarUserProfile.js +2 -2
- package/dist/components/core/Sortable/Sortable.js +2 -2
- package/dist/components/core/Tag/Tag.js +2 -2
- package/dist/components/core/Tag/Tag.theme.js +4 -2
- package/dist/components/core/Toast/Toast.js +3 -3
- package/dist/components/core/Toast/Toast.types.d.ts +1 -1
- package/dist/components/core/Toast/useToast.js +5 -5
- package/dist/components/core/Tooltip/Tooltip.d.ts +1 -1
- package/dist/components/core/Tooltip/Tooltip.js +42 -10
- package/dist/components/core/Tooltip/Tooltip.types.d.ts +25 -0
- package/dist/components/core/Tree/Tree.js +2 -2
- package/dist/components/core/shared/interaction.animations.d.ts +51 -0
- package/dist/components/core/shared/interaction.animations.js +59 -0
- package/dist/components/data-display/Feed/Feed.js +2 -2
- package/dist/components/data-display/GanttChart/GanttChart.theme.js +1 -1
- package/dist/components/data-display/GanttChart/GanttChartPanel.js +2 -2
- package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +3 -3
- package/dist/components/data-display/GanttChart/GanttTaskContextMenu.js +2 -2
- package/dist/components/data-display/GanttChart/GanttToolbar.js +2 -2
- package/dist/components/data-display/KPI/KPI.js +2 -2
- package/dist/components/data-display/KPI/KPI.theme.js +4 -4
- package/dist/components/data-display/Metric/Metric.js +2 -2
- package/dist/components/data-display/Metric/Metric.theme.js +25 -8
- package/dist/components/data-display/Metric/Metric.types.d.ts +1 -1
- package/dist/components/data-display/Table/Table.theme.js +9 -9
- package/dist/components/data-display/Table/TableActions.js +2 -2
- package/dist/components/data-display/Table/TableActionsMenu.js +1 -1
- package/dist/components/data-display/Table/TableColumnManager.js +2 -2
- package/dist/components/data-display/Table/TableDeclarative.js +3 -3
- package/dist/components/data-display/Table/TableFilter.js +4 -4
- package/dist/components/data-display/Table/TableHeader.js +3 -3
- package/dist/components/data-display/Table/TableRow.js +2 -2
- package/dist/components/data-display/Table/TableSelectionHeader.js +3 -3
- package/dist/components/data-display/Timeline/TimelineMarker.js +2 -2
- package/dist/components/effects/CustomCursor/CustomCursor.js +2 -2
- package/dist/components/effects/CustomCursor/CustomCursor.theme.js +2 -2
- package/dist/components/effects/DotPattern/DotPattern.js +6 -0
- package/dist/components/effects/DotPattern/DotPattern.types.d.ts +4 -2
- package/dist/components/effects/MeshGradient/MeshGradient.utils.d.ts +1 -1
- package/dist/components/effects/MeshGradient/MeshGradient.utils.js +1 -1
- package/dist/components/forms/Checkbox/Checkbox.js +2 -1
- package/dist/components/forms/Checkbox/Checkbox.theme.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerGradient.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerHeader.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerImage.js +3 -3
- package/dist/components/forms/ColorPicker/ColorPickerInput.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerPresets.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerSavedColors.js +2 -2
- package/dist/components/forms/Combobox/Combobox.js +3 -3
- package/dist/components/forms/Combobox/Combobox.theme.js +2 -2
- package/dist/components/forms/DatePicker/DatePicker.animations.js +16 -25
- package/dist/components/forms/DatePicker/DatePicker.theme.js +2 -2
- package/dist/components/forms/DatePicker/DatePickerBody.js +1 -1
- package/dist/components/forms/DatePicker/DatePickerTrigger.js +2 -2
- package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +3 -3
- package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +1 -1
- package/dist/components/forms/DateRangePicker/DateRangePickerPresets.js +2 -2
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +2 -2
- package/dist/components/forms/FileUpload/FileUpload.js +4 -4
- package/dist/components/forms/FileUpload/FileUpload.theme.js +5 -1
- package/dist/components/forms/FileUpload/FileUploadProgress.js +7 -7
- package/dist/components/forms/Input/Input.js +6 -7
- package/dist/components/forms/Input/Input.theme.js +19 -15
- package/dist/components/forms/InputAddress/InputAddress.js +10 -3
- package/dist/components/forms/InputCounter/InputCounter.js +3 -3
- package/dist/components/forms/InputOTP/InputOTP.d.ts +3 -12
- package/dist/components/forms/InputOTP/InputOTP.js +140 -151
- package/dist/components/forms/InputOTP/InputOTP.theme.d.ts +5 -1
- package/dist/components/forms/InputOTP/InputOTP.theme.js +40 -5
- package/dist/components/forms/InputOTP/InputOTP.types.d.ts +45 -2
- package/dist/components/forms/InputOTP/index.d.ts +1 -1
- package/dist/components/forms/InputTag/InputTag.js +5 -5
- package/dist/components/forms/InputTag/InputTag.theme.js +1 -2
- package/dist/components/forms/Mention/Mention.theme.js +3 -5
- package/dist/components/forms/Radio/Radio.js +3 -2
- package/dist/components/forms/Radio/Radio.theme.js +2 -2
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +22 -23
- package/dist/components/forms/Select/Select.d.ts +5 -1
- package/dist/components/forms/Select/Select.js +12 -11
- package/dist/components/forms/Select/Select.theme.js +4 -4
- package/dist/components/forms/Select/Select.types.d.ts +4 -1
- package/dist/components/forms/SelectableCard/SelectableCard.animations.d.ts +1 -2
- package/dist/components/forms/SelectableCard/SelectableCard.animations.js +6 -7
- package/dist/components/forms/SelectableCard/SelectableCard.js +1 -1
- package/dist/components/forms/Signature/Signature.js +1 -1
- package/dist/components/forms/Slider/Slider.js +24 -11
- package/dist/components/forms/Slider/Slider.theme.js +9 -3
- package/dist/components/forms/Switch/Switch.theme.js +1 -1
- package/dist/components/forms/TimePicker/TimePicker.theme.js +6 -7
- package/dist/components/forms/TimePicker/TimePickerContent.js +1 -1
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +3 -3
- package/dist/components/forms/forms.theme.js +23 -12
- package/dist/components/forms/index.js +1 -1
- package/dist/components/generative/registry.js +195 -2
- package/dist/components/generative/resolvers.js +16 -16
- package/dist/components/generative/schema.generated.js +2687 -379
- package/dist/components/layout/Section/Section.theme.js +1 -1
- package/dist/generative.schema.json +2687 -379
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useElapsedTime.d.ts +28 -0
- package/dist/hooks/useElapsedTime.js +37 -0
- package/dist/hooks/useSelectPortal.d.ts +10 -2
- package/dist/hooks/useSelectPortal.js +6 -3
- package/dist/index.js +1 -1
- package/dist/registry.json +835 -261
- package/dist/shadcn-compat.css +1 -1
- package/dist/styles.css +1 -1
- package/dist/theme.css +5 -1
- package/dist/themes/ember.css +131 -0
- package/dist/themes/iris.css +94 -0
- package/dist/themes/jade.css +139 -0
- package/dist/tools.json +2705 -361
- package/dist/utils/colorUtils.d.ts +6 -6
- package/dist/utils/colorUtils.js +8 -8
- package/package.json +9 -6
- package/src/global.scss +15 -4
- package/src/styles/theme.css +440 -152
- package/tailwind.preset.cjs +52 -34
- package/dist/components/core/Pill/Pill.animations.d.ts +0 -32
- package/dist/components/core/Pill/Pill.animations.js +0 -16
|
@@ -1,195 +1,184 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsxs, jsx
|
|
3
|
-
import React__default, { useRef, useState,
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import React__default, { useRef, useState, useEffect, useCallback } from 'react';
|
|
4
|
+
import { useReducedMotion, AnimatePresence, motion } from 'motion/react';
|
|
5
|
+
import '../../../theme/ThemeContext.js';
|
|
6
|
+
import '../../../theme/SurfaceContext.js';
|
|
7
|
+
import { springs } from '../../../theme/animations.js';
|
|
4
8
|
import { cn } from '../../../utils/cn.js';
|
|
5
|
-
import { Input } from '../Input/Input.js';
|
|
6
|
-
import '../Input/Input.theme.js';
|
|
7
9
|
import { FormLabel } from '../FormLabel/FormLabel.js';
|
|
8
10
|
import { inputOTPTheme } from './InputOTP.theme.js';
|
|
11
|
+
import { formsBaseTheme } from '../forms.theme.js';
|
|
9
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Per-charset input configuration: one source drives the sanitizer, the
|
|
15
|
+
* native `pattern`, and the mobile keyboard (`inputMode`) so they can never
|
|
16
|
+
* disagree (an alphanumeric code must never summon a digits-only keypad).
|
|
17
|
+
*/
|
|
18
|
+
const CHARSET_CONFIG = {
|
|
19
|
+
numeric: { strip: /[^0-9]/g, pattern: '[0-9]*', inputMode: 'numeric' },
|
|
20
|
+
alphanumeric: {
|
|
21
|
+
strip: /[^a-zA-Z0-9]/g,
|
|
22
|
+
pattern: '[a-zA-Z0-9]*',
|
|
23
|
+
inputMode: 'text',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
// Size-independent digit paint. The font-size/line-height come from the slot
|
|
27
|
+
// WRAPPER (theme.charSizes) and are inherited, so the caret below can size in
|
|
28
|
+
// `em` instead of needing its own token family.
|
|
29
|
+
const charClass = 'font-semibold text-[var(--color-text-primary)]';
|
|
30
|
+
const caretClass = 'w-[1.5px] h-[1.1em] bg-[var(--color-primary-600)] dark:bg-[var(--color-primary-500)] rounded-full absolute';
|
|
31
|
+
/**
|
|
32
|
+
* One rendered code slot — the single source for the char pop + blinking
|
|
33
|
+
* caret markup, shared by every grouping branch.
|
|
34
|
+
*/
|
|
35
|
+
const OtpSlot = ({ char, showCaret, className, reducedMotion }) => (jsxs("div", { className: className, children: [reducedMotion ? (char && jsx("span", { className: charClass, children: char })) : (jsx(AnimatePresence, { mode: "popLayout", initial: false, children: char ? (jsx(motion.span, { initial: { opacity: 0, scale: 0.85 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.85 }, transition: springs.snappy, className: charClass, children: char }, "char")) : null })), showCaret && (jsx("div", { className: cn(caretClass, !reducedMotion && 'animate-[flik-caret-blink_1s_step-end_infinite]') }))] }));
|
|
10
36
|
/**
|
|
11
37
|
* InputOTP component for entering one-time passwords or verification codes.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* ```tsx
|
|
16
|
-
* <InputOTP
|
|
17
|
-
* length={6}
|
|
18
|
-
* value={otpValue}
|
|
19
|
-
* onChange={setOtpValue}
|
|
20
|
-
* onComplete={handleOtpComplete}
|
|
21
|
-
* label="Enter verification code"
|
|
22
|
-
* />
|
|
23
|
-
* ```
|
|
38
|
+
* One invisible native input overlays the rendered slots: focus, typing,
|
|
39
|
+
* paste, and autofill all go through it, and the caret is pinned to the end
|
|
40
|
+
* of the value so the highlighted slot always matches where edits land.
|
|
24
41
|
*/
|
|
25
|
-
const InputOTP = React__default.forwardRef(({ length = 6, value, defaultValue, onChange, onComplete, state = 'default', disabled = false, label, helperText, errorMessage, error, autoFocus = true, className, id: idProp, ...props }, ref) => {
|
|
26
|
-
|
|
42
|
+
const InputOTP = React__default.forwardRef(({ length = 6, size = 'lg', value, defaultValue, onChange, onComplete, charset = 'alphanumeric', state = 'default', disabled = false, label, helperText, errorMessage, error, autoFocus = true, className, id: idProp, ...props }, ref) => {
|
|
43
|
+
var _a;
|
|
44
|
+
const inputRef = useRef(null);
|
|
27
45
|
const theme = inputOTPTheme;
|
|
46
|
+
const shouldReduceMotion = (_a = useReducedMotion()) !== null && _a !== void 0 ? _a : false;
|
|
28
47
|
const generatedId = React__default.useId();
|
|
29
|
-
const
|
|
48
|
+
const inputId = idProp !== null && idProp !== void 0 ? idProp : `otp-input-${generatedId}`;
|
|
49
|
+
const helperTextId = `${inputId}-helper`;
|
|
50
|
+
const labelId = `${inputId}-label`;
|
|
30
51
|
// Derive unified state flags for backward compat
|
|
31
52
|
const isDisabled = state === 'disabled' || disabled;
|
|
32
53
|
const isInvalid = state === 'invalid' || !!error;
|
|
33
54
|
// Internal state for uncontrolled usage — seeded with defaultValue
|
|
34
55
|
const [internalValue, setInternalValue] = useState(defaultValue !== null && defaultValue !== void 0 ? defaultValue : '');
|
|
56
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
35
57
|
// Use controlled value if provided, otherwise use internal state
|
|
36
58
|
const isControlled = value !== undefined;
|
|
37
59
|
const currentValue = isControlled ? value : internalValue;
|
|
38
|
-
|
|
60
|
+
// Dev warnings (§9): controlled/uncontrolled misuse
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
63
|
+
if (value !== undefined && defaultValue !== undefined) {
|
|
64
|
+
console.warn('[InputOTP]: Both `value` and `defaultValue` provided. InputOTP is controlled — `defaultValue` is ignored.');
|
|
65
|
+
}
|
|
66
|
+
if (value !== undefined && !onChange) {
|
|
67
|
+
console.warn('[InputOTP]: `value` provided without an `onChange` handler — the field will be read-only.');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
71
|
+
}, []);
|
|
72
|
+
const charsetConfig = CHARSET_CONFIG[charset];
|
|
73
|
+
const commitValue = useCallback((next) => {
|
|
39
74
|
if (!isControlled)
|
|
40
75
|
setInternalValue(next);
|
|
41
76
|
onChange === null || onChange === void 0 ? void 0 : onChange(next);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
77
|
+
if (next.length === length) {
|
|
78
|
+
onComplete === null || onComplete === void 0 ? void 0 : onComplete(next);
|
|
79
|
+
}
|
|
80
|
+
}, [isControlled, onChange, onComplete, length]);
|
|
81
|
+
const sanitize = useCallback((raw) => raw.replace(charsetConfig.strip, ''), [charsetConfig]);
|
|
82
|
+
const activeIndex = currentValue.length;
|
|
83
|
+
// Auto-focus hidden input on mount
|
|
46
84
|
useEffect(() => {
|
|
47
|
-
if (autoFocus &&
|
|
48
|
-
|
|
85
|
+
if (autoFocus && inputRef.current && !isDisabled) {
|
|
86
|
+
inputRef.current.focus();
|
|
49
87
|
}
|
|
50
88
|
}, [autoFocus, isDisabled]);
|
|
51
|
-
|
|
52
|
-
const handleChange = useCallback((index, newValue) => {
|
|
53
|
-
var _a;
|
|
89
|
+
const handleInputChange = (e) => {
|
|
54
90
|
if (isDisabled)
|
|
55
91
|
return;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const newValueString = newValueArray.join('').trimEnd();
|
|
63
|
-
setValue(newValueString);
|
|
64
|
-
// Auto-focus next input if character was entered
|
|
65
|
-
if (char && index < length - 1) {
|
|
66
|
-
(_a = inputRefs.current[index + 1]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
67
|
-
}
|
|
68
|
-
// Check if OTP is complete
|
|
69
|
-
if (newValueString.length === length) {
|
|
70
|
-
onComplete === null || onComplete === void 0 ? void 0 : onComplete(newValueString);
|
|
71
|
-
}
|
|
72
|
-
}, [isDisabled, valueArray, length, setValue, onComplete]);
|
|
73
|
-
// Handle key down for navigation and deletion
|
|
74
|
-
const handleKeyDown = useCallback((index, event) => {
|
|
75
|
-
var _a, _b, _c, _d, _e;
|
|
92
|
+
commitValue(sanitize(e.target.value).slice(0, length));
|
|
93
|
+
};
|
|
94
|
+
// Paste must bypass the native maxLength: the browser truncates BEFORE we
|
|
95
|
+
// can strip separators ('123-456' → '123-45' → '12345', last digit lost).
|
|
96
|
+
// Read the clipboard directly, sanitize, then commit through the same path.
|
|
97
|
+
const handlePaste = (e) => {
|
|
76
98
|
if (isDisabled)
|
|
77
99
|
return;
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
// If current field is empty, move to previous and clear it
|
|
82
|
-
event.preventDefault();
|
|
83
|
-
(_a = inputRefs.current[index - 1]) === null || _a === void 0 ? void 0 : _a.focus();
|
|
84
|
-
handleChange(index - 1, '');
|
|
85
|
-
}
|
|
86
|
-
break;
|
|
87
|
-
case 'Delete':
|
|
88
|
-
// Clear current field
|
|
89
|
-
event.preventDefault();
|
|
90
|
-
handleChange(index, '');
|
|
91
|
-
break;
|
|
92
|
-
case 'ArrowLeft':
|
|
93
|
-
event.preventDefault();
|
|
94
|
-
if (index > 0) {
|
|
95
|
-
(_b = inputRefs.current[index - 1]) === null || _b === void 0 ? void 0 : _b.focus();
|
|
96
|
-
}
|
|
97
|
-
break;
|
|
98
|
-
case 'ArrowRight':
|
|
99
|
-
event.preventDefault();
|
|
100
|
-
if (index < length - 1) {
|
|
101
|
-
(_c = inputRefs.current[index + 1]) === null || _c === void 0 ? void 0 : _c.focus();
|
|
102
|
-
}
|
|
103
|
-
break;
|
|
104
|
-
case 'Home':
|
|
105
|
-
event.preventDefault();
|
|
106
|
-
(_d = inputRefs.current[0]) === null || _d === void 0 ? void 0 : _d.focus();
|
|
107
|
-
break;
|
|
108
|
-
case 'End':
|
|
109
|
-
event.preventDefault();
|
|
110
|
-
(_e = inputRefs.current[length - 1]) === null || _e === void 0 ? void 0 : _e.focus();
|
|
111
|
-
break;
|
|
112
|
-
}
|
|
113
|
-
}, [isDisabled, valueArray, length, handleChange]);
|
|
114
|
-
// Handle paste
|
|
115
|
-
const handlePaste = useCallback((event) => {
|
|
116
|
-
var _a;
|
|
117
|
-
if (isDisabled)
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
const pasted = sanitize(e.clipboardData.getData('text'));
|
|
102
|
+
if (!pasted)
|
|
118
103
|
return;
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
104
|
+
// A full code replaces whatever is typed; a fragment appends at the end
|
|
105
|
+
// (the caret is always pinned to the end).
|
|
106
|
+
const next = pasted.length >= length
|
|
107
|
+
? pasted.slice(0, length)
|
|
108
|
+
: (currentValue + pasted).slice(0, length);
|
|
109
|
+
commitValue(next);
|
|
110
|
+
};
|
|
111
|
+
// Pin the real caret to the end of the value. The visual "active slot"
|
|
112
|
+
// is derived from value length, so a caret moved by click/arrow keys
|
|
113
|
+
// would silently edit a different position than the one highlighted.
|
|
114
|
+
const pinCaretToEnd = () => {
|
|
115
|
+
const el = inputRef.current;
|
|
116
|
+
if (!el)
|
|
117
|
+
return;
|
|
118
|
+
const end = el.value.length;
|
|
119
|
+
if (el.selectionStart !== end || el.selectionEnd !== end) {
|
|
120
|
+
el.setSelectionRange(end, end);
|
|
130
121
|
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
// Blur logic can be added here if needed
|
|
138
|
-
}, []);
|
|
122
|
+
};
|
|
123
|
+
const handleContainerClick = () => {
|
|
124
|
+
if (!isDisabled && inputRef.current) {
|
|
125
|
+
inputRef.current.focus();
|
|
126
|
+
}
|
|
127
|
+
};
|
|
139
128
|
// State class for CSS selectors (matches Input component pattern)
|
|
140
129
|
const stateClass = isInvalid
|
|
141
|
-
?
|
|
130
|
+
? 'state-invalid'
|
|
142
131
|
: isDisabled
|
|
143
|
-
?
|
|
144
|
-
:
|
|
132
|
+
? 'state-disabled'
|
|
133
|
+
: 'state-default';
|
|
145
134
|
// Resolved label state for FormLabel
|
|
146
135
|
const labelState = isInvalid ? 'invalid' : isDisabled ? 'disabled' : 'default';
|
|
147
|
-
// Per-slot accessible name. Slot 0 stays linked to the visible <FormLabel>
|
|
148
|
-
// via htmlFor (no aria-label — an aria-label would override that
|
|
149
|
-
// association). Slots 2+ previously had no accessible name of their own;
|
|
150
|
-
// give each an indexed name so every slot is announced distinctly.
|
|
151
|
-
const getSlotAriaLabel = (index) => index === 0 ? undefined : `Digit ${index + 1} of ${length}`;
|
|
152
136
|
// Resolved message to display: errorMessage takes precedence while
|
|
153
137
|
// invalid, then helperText, then the deprecated `error` string.
|
|
154
138
|
const displayedHelperText = isInvalid && errorMessage ? errorMessage : (helperText !== null && helperText !== void 0 ? helperText : error);
|
|
155
139
|
// Build classes with shadcn pattern - user className has highest priority
|
|
156
140
|
const wrapperClasses = cn(theme.wrapperStyle, stateClass, className);
|
|
157
|
-
const containerClasses = cn(theme.containerStyle);
|
|
158
|
-
//
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
141
|
+
const containerClasses = cn(theme.containerStyle, 'relative');
|
|
142
|
+
// Slot grouping: 6-digit codes render as two groups of three; every other
|
|
143
|
+
// length is a single run. Slots only differ by group-edge rounding.
|
|
144
|
+
const groups = length === 6
|
|
145
|
+
? [
|
|
146
|
+
[0, 3],
|
|
147
|
+
[3, 6],
|
|
148
|
+
]
|
|
149
|
+
: [[0, length]];
|
|
150
|
+
// Slot paint comes from formsBaseTheme (inputGroupBaseStyle / states /
|
|
151
|
+
// focusStates) — the same inset-shadow border system as every other form
|
|
152
|
+
// control — plus this component's geometry from the theme file. The
|
|
153
|
+
// hidden input lives outside the slots, so the "focused" (active-slot)
|
|
154
|
+
// state is applied in JS via focusStates instead of :focus-within.
|
|
155
|
+
const getSlotClasses = (index, isActive) => {
|
|
156
|
+
const group = groups.find(([start, end]) => index >= start && index < end);
|
|
157
|
+
const isFirst = index === group[0];
|
|
158
|
+
const isLast = index === group[1] - 1;
|
|
159
|
+
return cn(formsBaseTheme.inputGroupBaseStyle, theme.inputGroupSizes[size], theme.charSizes[size], !isLast && 'rounded-r-none -mr-px', !isFirst && 'rounded-l-none', isDisabled && formsBaseTheme.states.disabled, !isDisabled && isInvalid && formsBaseTheme.states.invalid, !isDisabled &&
|
|
160
|
+
isActive &&
|
|
161
|
+
cn('z-10', isInvalid
|
|
162
|
+
? formsBaseTheme.focusStates.invalid
|
|
163
|
+
: cn(formsBaseTheme.focusStates.default, theme.slotActiveHoverGuard)));
|
|
178
164
|
};
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
165
|
+
const hasNodeLabel = Boolean(label) && typeof label !== 'string';
|
|
166
|
+
return (jsxs("div", { ref: ref, className: wrapperClasses, ...props, children: [label &&
|
|
167
|
+
(typeof label === 'string' ? (jsx(FormLabel, { htmlFor: inputId, state: labelState, children: label })) : (
|
|
168
|
+
// ReactNode labels can't be linked via htmlFor (they may not be a
|
|
169
|
+
// <label>), so wrap with an id and point aria-labelledby at it —
|
|
170
|
+
// the input must always have an accessible name.
|
|
171
|
+
jsx("div", { id: labelId, children: label }))), jsxs("div", { className: containerClasses, onClick: handleContainerClick, children: [jsx("input", { ref: inputRef, id: inputId, type: "text", pattern: charsetConfig.pattern, inputMode: charsetConfig.inputMode, autoComplete: "one-time-code", maxLength: length, value: currentValue, onChange: handleInputChange, onPaste: handlePaste, onSelect: pinCaretToEnd, onFocus: () => {
|
|
172
|
+
setIsFocused(true);
|
|
173
|
+
pinCaretToEnd();
|
|
174
|
+
}, onBlur: () => setIsFocused(false), disabled: isDisabled, "aria-invalid": isInvalid || undefined, "aria-labelledby": hasNodeLabel ? labelId : undefined, "aria-describedby": displayedHelperText ? helperTextId : undefined, className: "absolute inset-0 w-full h-full opacity-0 cursor-text select-none z-20 disabled:cursor-not-allowed" }), groups.map(([start, end], groupIndex) => (jsxs(React__default.Fragment, { children: [groupIndex > 0 && (jsx("span", { className: theme.separatorStyle, children: "\u2014" })), jsx("div", { className: "flex", children: Array.from({ length: end - start }, (_, i) => {
|
|
175
|
+
const index = start + i;
|
|
176
|
+
// While full, the last slot stays highlighted (Backspace
|
|
177
|
+
// edits it); otherwise the first empty slot is active.
|
|
178
|
+
const isActive = isFocused && index === Math.min(activeIndex, length - 1);
|
|
179
|
+
const char = currentValue[index];
|
|
180
|
+
return (jsx(OtpSlot, { char: char, showCaret: isActive && !char, className: getSlotClasses(index, isActive), reducedMotion: shouldReduceMotion }, index));
|
|
181
|
+
}) })] }, start)))] }), displayedHelperText && (jsx("div", { id: helperTextId, className: cn(theme.helperText), children: displayedHelperText }))] }));
|
|
193
182
|
});
|
|
194
183
|
InputOTP.displayName = 'InputOTP';
|
|
195
184
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { InputOTPTheme } from './InputOTP.types';
|
|
2
2
|
/**
|
|
3
3
|
* Default theme for InputOTP component - Clean & Organized
|
|
4
|
-
* Uses flat structure with design tokens following library patterns
|
|
4
|
+
* Uses flat structure with design tokens following library patterns.
|
|
5
|
+
*
|
|
6
|
+
* Slot borders/rings (resting, hover, invalid, disabled, active) come straight
|
|
7
|
+
* from `formsBaseTheme` (inputGroupBaseStyle / states / focusStates) — this file
|
|
8
|
+
* only adds OTP-specific geometry and the active-slot hover guard.
|
|
5
9
|
*/
|
|
6
10
|
export declare const inputOTPTheme: InputOTPTheme;
|
|
@@ -2,17 +2,52 @@ import { formsBaseTheme } from '../forms.theme.js';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Default theme for InputOTP component - Clean & Organized
|
|
5
|
-
* Uses flat structure with design tokens following library patterns
|
|
5
|
+
* Uses flat structure with design tokens following library patterns.
|
|
6
|
+
*
|
|
7
|
+
* Slot borders/rings (resting, hover, invalid, disabled, active) come straight
|
|
8
|
+
* from `formsBaseTheme` (inputGroupBaseStyle / states / focusStates) — this file
|
|
9
|
+
* only adds OTP-specific geometry and the active-slot hover guard.
|
|
6
10
|
*/
|
|
7
11
|
const inputOTPTheme = {
|
|
8
12
|
// Base container styles
|
|
9
13
|
wrapperStyle: formsBaseTheme.wrapperStyle,
|
|
10
14
|
// OTP input container
|
|
11
15
|
containerStyle: 'flex items-center gap-3',
|
|
12
|
-
//
|
|
13
|
-
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
+
// Slot geometry: square slots sized from --otp-slot-size-*, whose sm/md/lg
|
|
17
|
+
// alias the form heights so slots match an adjacent Input of the same size.
|
|
18
|
+
// Both w/h and min-w/min-h are set so a flex parent can never shrink a slot
|
|
19
|
+
// out of square. Written as full literal class strings — never interpolated
|
|
20
|
+
// — or Tailwind purges them.
|
|
21
|
+
inputGroupSizes: {
|
|
22
|
+
sm: 'items-center justify-center ' +
|
|
23
|
+
'w-[var(--otp-slot-size-sm)] min-w-[var(--otp-slot-size-sm)] ' +
|
|
24
|
+
'h-[var(--otp-slot-size-sm)] min-h-[var(--otp-slot-size-sm)]',
|
|
25
|
+
md: 'items-center justify-center ' +
|
|
26
|
+
'w-[var(--otp-slot-size-md)] min-w-[var(--otp-slot-size-md)] ' +
|
|
27
|
+
'h-[var(--otp-slot-size-md)] min-h-[var(--otp-slot-size-md)]',
|
|
28
|
+
lg: 'items-center justify-center ' +
|
|
29
|
+
'w-[var(--otp-slot-size-lg)] min-w-[var(--otp-slot-size-lg)] ' +
|
|
30
|
+
'h-[var(--otp-slot-size-lg)] min-h-[var(--otp-slot-size-lg)]',
|
|
31
|
+
xl: 'items-center justify-center ' +
|
|
32
|
+
'w-[var(--otp-slot-size-xl)] min-w-[var(--otp-slot-size-xl)] ' +
|
|
33
|
+
'h-[var(--otp-slot-size-xl)] min-h-[var(--otp-slot-size-xl)]',
|
|
34
|
+
},
|
|
35
|
+
// 🔴 `length:` hint is required — a bare text-[var(--x)] resolves to the
|
|
36
|
+
// COLOUR branch and silently no-ops. The paired /[var(--otp-line-height-*)]
|
|
37
|
+
// is equally required: text-[length:…] carries font-size only, so without it
|
|
38
|
+
// the digit's leading silently falls back to normal/1.5.
|
|
39
|
+
charSizes: {
|
|
40
|
+
sm: 'text-[length:var(--otp-text-size-sm)]/[var(--otp-line-height-sm)]',
|
|
41
|
+
md: 'text-[length:var(--otp-text-size-md)]/[var(--otp-line-height-md)]',
|
|
42
|
+
lg: 'text-[length:var(--otp-text-size-lg)]/[var(--otp-line-height-lg)]',
|
|
43
|
+
xl: 'text-[length:var(--otp-text-size-xl)]/[var(--otp-line-height-xl)]',
|
|
44
|
+
},
|
|
45
|
+
// Keep the primary border on hover for the JS-driven active slot (the hidden
|
|
46
|
+
// input sits outside the slot, so the base `not-focus-within:hover:` edge
|
|
47
|
+
// would otherwise win over the plain active border). Same not-focus-within
|
|
48
|
+
// re-assert technique as formsBaseTheme.states.invalid / Input.theme.ts.
|
|
49
|
+
slotActiveHoverGuard: 'not-focus-within:hover:shadow-[inset_0_0_0_1px_var(--color-primary-600)] ' +
|
|
50
|
+
'dark:not-focus-within:hover:shadow-[inset_0_0_0_1px_var(--color-primary-500)]',
|
|
16
51
|
// Separator between input groups
|
|
17
52
|
separatorStyle: 'text-[var(--color-text-muted)] font-medium select-none',
|
|
18
53
|
// Helper text with state handling (reuses form base)
|
|
@@ -2,12 +2,30 @@
|
|
|
2
2
|
* State variants for the InputOTP component
|
|
3
3
|
*/
|
|
4
4
|
export type InputOTPState = 'default' | 'disabled' | 'invalid';
|
|
5
|
+
/**
|
|
6
|
+
* Character sets accepted by the InputOTP component
|
|
7
|
+
*/
|
|
8
|
+
export type InputOTPCharset = 'numeric' | 'alphanumeric';
|
|
9
|
+
/**
|
|
10
|
+
* Slot size variants for the InputOTP component.
|
|
11
|
+
*
|
|
12
|
+
* `sm`/`md`/`lg` resolve to the same heights as the equivalent `Input` size,
|
|
13
|
+
* so an OTP lines up with adjacent controls. `xl` has no form-ramp
|
|
14
|
+
* counterpart — an OTP slot is a square display element and legitimately
|
|
15
|
+
* wants a size above the form ramp's top rung.
|
|
16
|
+
*/
|
|
17
|
+
export type InputOTPSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
5
18
|
export interface InputOTPProps extends Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange' | 'defaultValue'> {
|
|
6
19
|
/**
|
|
7
20
|
* Number of OTP input fields
|
|
8
21
|
* @default 6
|
|
9
22
|
*/
|
|
10
23
|
length?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Size of the OTP slots.
|
|
26
|
+
* @default 'lg'
|
|
27
|
+
*/
|
|
28
|
+
size?: InputOTPSize;
|
|
11
29
|
/**
|
|
12
30
|
* Current OTP value (controlled)
|
|
13
31
|
*/
|
|
@@ -24,6 +42,14 @@ export interface InputOTPProps extends Omit<React.HTMLAttributes<HTMLDivElement>
|
|
|
24
42
|
* Callback when OTP is complete (all fields filled)
|
|
25
43
|
*/
|
|
26
44
|
onComplete?: (value: string) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Which characters the code accepts. Drives the sanitizer (typed and pasted
|
|
47
|
+
* characters outside the set are stripped), the native `pattern` attribute,
|
|
48
|
+
* and the mobile keyboard via `inputMode` (`'numeric'` → numeric keypad,
|
|
49
|
+
* `'alphanumeric'` → standard text keyboard).
|
|
50
|
+
* @default 'alphanumeric'
|
|
51
|
+
*/
|
|
52
|
+
charset?: InputOTPCharset;
|
|
27
53
|
/**
|
|
28
54
|
* Visual and functional state of the OTP input group.
|
|
29
55
|
* Use 'disabled' instead of the deprecated `disabled` boolean prop.
|
|
@@ -65,8 +91,25 @@ export interface InputOTPProps extends Omit<React.HTMLAttributes<HTMLDivElement>
|
|
|
65
91
|
export interface InputOTPTheme {
|
|
66
92
|
wrapperStyle: string;
|
|
67
93
|
containerStyle: string;
|
|
68
|
-
|
|
69
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Slot geometry (square size + centering) per size, layered over
|
|
96
|
+
* `formsBaseTheme.inputGroupBaseStyle`.
|
|
97
|
+
*/
|
|
98
|
+
inputGroupSizes: Record<InputOTPSize, string>;
|
|
99
|
+
/**
|
|
100
|
+
* Digit type per size, applied to the slot WRAPPER (not the digit span) so
|
|
101
|
+
* the digit inherits it and the caret can size in `em`. Runs one rung
|
|
102
|
+
* hotter than form body text — a display numeral, not label text.
|
|
103
|
+
*/
|
|
104
|
+
charSizes: Record<InputOTPSize, string>;
|
|
105
|
+
/**
|
|
106
|
+
* Hover-edge guard for the JS-driven active slot. The hidden input lives
|
|
107
|
+
* outside the slot, so `:focus-within` never applies and the base theme's
|
|
108
|
+
* `not-focus-within:hover:` border change would otherwise override the
|
|
109
|
+
* active (primary) border on hover — this re-asserts primary under the same
|
|
110
|
+
* variant (mirrors `Input.theme.ts` states.invalid's hover re-assert).
|
|
111
|
+
*/
|
|
112
|
+
slotActiveHoverGuard: string;
|
|
70
113
|
separatorStyle: string;
|
|
71
114
|
helperText: string;
|
|
72
115
|
}
|
|
@@ -3,11 +3,11 @@ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
|
3
3
|
import React__default, { useMemo, useState, useRef, useEffect, useCallback } from 'react';
|
|
4
4
|
import { AnimatePresence, motion } from 'motion/react';
|
|
5
5
|
import { createPortal } from 'react-dom';
|
|
6
|
-
import {
|
|
6
|
+
import { Tag, Plus, Check } from '@phosphor-icons/react';
|
|
7
7
|
import { cn } from '../../../utils/cn.js';
|
|
8
8
|
import { Input } from '../Input/Input.js';
|
|
9
9
|
import '../Input/Input.theme.js';
|
|
10
|
-
import { Tag } from '../../core/Tag/Tag.js';
|
|
10
|
+
import { Tag as Tag$1 } from '../../core/Tag/Tag.js';
|
|
11
11
|
import { inputTagTheme } from './InputTag.theme.js';
|
|
12
12
|
import { useSelectPortal } from '../../../hooks/useSelectPortal.js';
|
|
13
13
|
import { createDropdownAnimations } from '../Select/Select.animations.js';
|
|
@@ -83,7 +83,7 @@ const InputTagOptions = ({ isOpen, onClose, triggerRef, filteredOptions, selecte
|
|
|
83
83
|
if (!option.disabled) {
|
|
84
84
|
onSelectOption(option, isCreateOption);
|
|
85
85
|
}
|
|
86
|
-
}, children: isCreateOption ? (jsxs("span", { className: "flex items-center gap-1", children: [jsx(
|
|
86
|
+
}, children: isCreateOption ? (jsxs("span", { className: "flex items-center gap-1", children: [jsx(Plus, { className: "size-4 shrink-0", weight: "bold" }), jsx("span", { className: "block truncate", children: option.label })] })) : (jsxs("span", { className: "flex items-center justify-between gap-2", children: [jsx("span", { className: "block truncate", children: option.label }), showSelectedIndicator && isSelected && (jsx(Check, { className: "size-4 shrink-0 text-[var(--color-primary)]", weight: "bold" }))] })) }, option.id));
|
|
87
87
|
}), 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"] }))] })) }) })) }));
|
|
88
88
|
if (portal && isClient) {
|
|
89
89
|
return createPortal(jsx("div", { "data-flikkui-portal": "", className: isDarkMode ? "dark" : undefined, children: optionsContent }), document.body);
|
|
@@ -492,13 +492,13 @@ suggestions, creatable = true, openOnFocus = true, filterFn = defaultFilterFn, e
|
|
|
492
492
|
}, [tags]);
|
|
493
493
|
// Helper text to display (for dropdown mode, we render it separately)
|
|
494
494
|
const displayHelperText = duplicateWarning || helperText;
|
|
495
|
-
return (jsxs("div", { ref: ref, className: cn("w-full", className), ...props, children: [jsxs("div", { ref: wrapperRef, className: "relative", children: [jsx(Input, { ref: inputRef, value: inputValue, onChange: handleInputChange, onKeyDown: handleKeyDown, onPaste: handlePaste, onFocus: handleFocus, onBlur: handleBlur, iconStart: jsx(
|
|
495
|
+
return (jsxs("div", { ref: ref, className: cn("w-full", className), ...props, children: [jsxs("div", { ref: wrapperRef, className: "relative", children: [jsx(Input, { ref: inputRef, value: inputValue, onChange: handleInputChange, onKeyDown: handleKeyDown, onPaste: handlePaste, onFocus: handleFocus, onBlur: handleBlur, iconStart: jsx(Tag, { className: "size-4" }), placeholder: maxTags && tags.length >= maxTags
|
|
496
496
|
? `Maximum ${maxTags} tags reached`
|
|
497
497
|
: placeholder, size: size, state: state, label: label,
|
|
498
498
|
// Only pass helperText to Input when NOT in dropdown mode
|
|
499
499
|
// Otherwise the dropdown positions below the helper text
|
|
500
500
|
helperText: hasDropdown ? undefined : displayHelperText, disabled: isInputDisabled, className: inputClassName, role: hasDropdown ? "combobox" : undefined, "aria-expanded": hasDropdown ? isOpen : undefined, "aria-haspopup": hasDropdown ? "listbox" : undefined, "aria-autocomplete": hasDropdown ? "list" : undefined, "aria-controls": hasDropdown ? listboxId : undefined }), hasDropdown && (jsx(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, elevation: elevation, lift: lift, listboxId: listboxId }))] }), hasDropdown && displayHelperText && (jsx("p", { className: "text-sm text-[var(--color-text-muted)] mt-1.5", children: displayHelperText })), jsx("div", { className: cn(theme.tagsContainerStyle, "empty:hidden", tagsContainerClassName), children: jsx(AnimatePresence, { mode: "popLayout", children: tags.map((tag, index) => {
|
|
501
|
-
return (jsx(Tag, { size: size, onRemove: () => removeTag(index), disabled: disabled, className: tagClassName, children: tag }, `${tag}-${occurrenceMap[index]}`));
|
|
501
|
+
return (jsx(Tag$1, { size: size, onRemove: () => removeTag(index), disabled: disabled, className: tagClassName, children: tag }, `${tag}-${occurrenceMap[index]}`));
|
|
502
502
|
}) }) })] }));
|
|
503
503
|
});
|
|
504
504
|
InputTag.displayName = "InputTag";
|
|
@@ -4,8 +4,7 @@ const inputTagTheme = {
|
|
|
4
4
|
// Tags container style
|
|
5
5
|
tagsContainerStyle: "flex flex-wrap gap-2 mt-2",
|
|
6
6
|
// Dropdown styles - reuse from formsBaseTheme for consistency
|
|
7
|
-
dropdownStyle: formsBaseTheme.dropdownStyles.container
|
|
8
|
-
" py-1",
|
|
7
|
+
dropdownStyle: formsBaseTheme.dropdownStyles.container,
|
|
9
8
|
optionStyle: formsBaseTheme.dropdownStyles.option,
|
|
10
9
|
selectedOptionStyle: formsBaseTheme.dropdownStyles.optionSelected,
|
|
11
10
|
highlightedOptionStyle: formsBaseTheme.dropdownStyles.optionHighlighted,
|
|
@@ -24,11 +24,9 @@ const mentionTheme = {
|
|
|
24
24
|
lg: `${formsBaseTheme.sizes.lg.text} ${formsBaseTheme.sizes.lg.padding} ${formsBaseTheme.sizes.lg.textareaHeight}`,
|
|
25
25
|
},
|
|
26
26
|
// Inline mention Tag chip styles.
|
|
27
|
-
// Dropdown container - reuses the shared dropdown styles
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
dropdownStyle: formsBaseTheme.dropdownStyles.container +
|
|
31
|
-
" p-1",
|
|
27
|
+
// Dropdown container - reuses the shared dropdown styles (which now carry the p-1
|
|
28
|
+
// inset that MenuItem rows' rounded highlight needs).
|
|
29
|
+
dropdownStyle: formsBaseTheme.dropdownStyles.container,
|
|
32
30
|
// Suggestion rows render via the shared MenuItem primitive (avatar →
|
|
33
31
|
// startContent, label → children, description → subtitle). These two styles
|
|
34
32
|
// are only used by the custom `renderSuggestion` escape hatch, which wraps
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
3
|
import React__default, { useId, useState, useEffect } from 'react';
|
|
4
|
-
import { useReducedMotion, motion } from 'motion/react';
|
|
4
|
+
import { useReducedMotion, AnimatePresence, motion } from 'motion/react';
|
|
5
5
|
import { radioTheme } from './Radio.theme.js';
|
|
6
6
|
import { formLabelTheme } from '../FormLabel/FormLabel.theme.js';
|
|
7
7
|
import { cn } from '../../../utils/cn.js';
|
|
8
8
|
import { useRadioGroupContext } from './RadioContext.js';
|
|
9
9
|
import { RadioGroup } from './RadioGroup.js';
|
|
10
10
|
import { reducedMotionDotVariants, radioDotVariants } from './Radio.animations.js';
|
|
11
|
+
import { CHECK_RIPPLE_MOTION } from '../../core/shared/interaction.animations.js';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Radio component for selecting one option from a group
|
|
@@ -54,7 +55,7 @@ const RadioComponent = React__default.forwardRef(({ id: idProp, name: nameProp,
|
|
|
54
55
|
}
|
|
55
56
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
56
57
|
}, []);
|
|
57
|
-
return (jsxs("div", { className: cn(radioTheme.baseStyle, className), children: [jsxs("div", { className: cn(radioTheme.inputContainerStyle, radioTheme.sizes[size]), children: [jsx("input", { ref: ref, type: "radio", ...restProps, id: id, name: name, value: value, ...inputProps, onChange: handleChange, disabled: isDisabled, "aria-describedby": description ? `${id}-description` : undefined, "data-state": isDisabled ? "disabled" : isInvalid ? "invalid" : "default", className: cn("peer", radioTheme.inputStyle, radioTheme.sizes[size]) }), jsx(motion.div, { className: cn(radioTheme.dotStyle, radioTheme.dotSizes[size], isDisabled && "saturate-0 opacity-80"), initial: false, animate: isChecked ? "checked" : "unchecked", variants: shouldReduceMotion ? reducedMotionDotVariants : radioDotVariants })] }), (label || description) && (jsxs("label", { htmlFor: id, className: cn("cursor-pointer", isDisabled && "cursor-not-allowed"), children: [label && (typeof label === 'string' ? (jsx("span", { className: cn(formLabelTheme.baseStyle,
|
|
58
|
+
return (jsxs("div", { className: cn(radioTheme.baseStyle, className), children: [jsxs("div", { className: cn(radioTheme.inputContainerStyle, radioTheme.sizes[size]), children: [jsx("input", { ref: ref, type: "radio", ...restProps, id: id, name: name, value: value, ...inputProps, onChange: handleChange, disabled: isDisabled, "aria-describedby": description ? `${id}-description` : undefined, "data-state": isDisabled ? "disabled" : isInvalid ? "invalid" : "default", className: cn("peer", radioTheme.inputStyle, radioTheme.sizes[size]) }), jsx(AnimatePresence, { initial: false, children: isChecked && !shouldReduceMotion && (jsx(motion.div, { ...CHECK_RIPPLE_MOTION, className: "absolute inset-0 rounded-full bg-[var(--color-primary)]/40 pointer-events-none z-0" }, "ripple")) }), jsx(motion.div, { className: cn(radioTheme.dotStyle, radioTheme.dotSizes[size], isDisabled && "saturate-0 opacity-80"), initial: false, animate: isChecked ? "checked" : "unchecked", variants: shouldReduceMotion ? reducedMotionDotVariants : radioDotVariants })] }), (label || description) && (jsxs("label", { htmlFor: id, className: cn("cursor-pointer", isDisabled && "cursor-not-allowed"), children: [label && (typeof label === 'string' ? (jsx("span", { className: cn(formLabelTheme.baseStyle,
|
|
58
59
|
// Message-led invalid: option labels stay neutral (only the
|
|
59
60
|
// group's asterisk + error message are red). Disabled still dims.
|
|
60
61
|
formLabelTheme.stateStyles[state === "disabled" ? "disabled" : "default"], "mb-0"), children: label })) : label), description && (typeof description === 'string' ? (jsx("div", { id: `${id}-description`, className: radioTheme.descriptionStyle, children: description })) : description)] }))] }));
|
|
@@ -23,8 +23,8 @@ const radioTheme = {
|
|
|
23
23
|
"disabled:bg-[var(--color-background-disabled)] disabled:checked:outline-0 " +
|
|
24
24
|
"data-[state=disabled]:border-[var(--color-border)] data-[state=disabled]:bg-[var(--color-background-disabled)] data-[state=disabled]:cursor-not-allowed " +
|
|
25
25
|
// Disabled state - dark
|
|
26
|
-
"dark:disabled:bg-[var(--color-
|
|
27
|
-
"dark:data-[state=disabled]:bg-[var(--color-
|
|
26
|
+
"dark:disabled:bg-[var(--color-mono-700)] dark:disabled:border-[var(--color-mono-600)] " +
|
|
27
|
+
"dark:data-[state=disabled]:bg-[var(--color-mono-700)] dark:data-[state=disabled]:border-[var(--color-mono-600)]",
|
|
28
28
|
// Invalid is message-led: the control stays neutral. The group's required
|
|
29
29
|
// asterisk + error message carry the error (see RadioGroup). `data-state=invalid`
|
|
30
30
|
// is still emitted on the input as an unstyled consumer/test hook.
|