@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.
Files changed (295) hide show
  1. package/CHANGELOG.md +97 -0
  2. package/dist/components/ai/AgentStatus/AgentStatus.d.ts +3 -0
  3. package/dist/components/ai/AgentStatus/AgentStatus.js +144 -0
  4. package/dist/components/ai/AgentStatus/AgentStatus.theme.d.ts +2 -0
  5. package/dist/components/ai/AgentStatus/AgentStatus.theme.js +37 -0
  6. package/dist/components/ai/AgentStatus/AgentStatus.types.d.ts +116 -0
  7. package/dist/components/ai/AgentStatus/index.d.ts +3 -0
  8. package/dist/components/ai/CodeBlock/CodeBlock.js +3 -3
  9. package/dist/components/ai/CodeBlock/CodeBlock.theme.js +1 -1
  10. package/dist/components/ai/PromptInput/PromptInput.js +13 -14
  11. package/dist/components/ai/PromptInput/PromptInput.theme.js +1 -1
  12. package/dist/components/ai/PromptInput/VoiceRecorder.js +3 -3
  13. package/dist/components/ai/Reasoning/Reasoning.d.ts +5 -0
  14. package/dist/components/ai/Reasoning/Reasoning.js +16 -0
  15. package/dist/components/ai/Reasoning/Reasoning.types.d.ts +20 -0
  16. package/dist/components/ai/Reasoning/index.d.ts +2 -0
  17. package/dist/components/ai/SourceCitation/SourceCitation.d.ts +4 -0
  18. package/dist/components/ai/SourceCitation/SourceCitation.js +29 -0
  19. package/dist/components/ai/SourceCitation/SourceCitation.theme.d.ts +2 -0
  20. package/dist/components/ai/SourceCitation/SourceCitation.theme.js +23 -0
  21. package/dist/components/ai/SourceCitation/SourceCitation.types.d.ts +55 -0
  22. package/dist/components/ai/SourceCitation/index.d.ts +3 -0
  23. package/dist/components/ai/StreamingResponse/ErrorDisplay.js +2 -2
  24. package/dist/components/ai/StreamingResponse/StreamingResponse.js +1 -3
  25. package/dist/components/ai/StreamingResponse/StreamingResponse.theme.js +3 -1
  26. package/dist/components/ai/TokenCounter/TokenCounter.js +2 -2
  27. package/dist/components/ai/index.d.ts +3 -0
  28. package/dist/components/ai/index.js +6 -3
  29. package/dist/components/ai/shared/StatusIndicator.d.ts +8 -0
  30. package/dist/components/ai/shared/StatusIndicator.js +34 -0
  31. package/dist/components/canvas/CanvasToolbar.js +2 -2
  32. package/dist/components/canvas/NodeControls.js +2 -2
  33. package/dist/components/canvas/NodeRenderer.js +2 -2
  34. package/dist/components/charts/ActivityRings/ActivityRings.js +2 -2
  35. package/dist/components/charts/AreaChart/AreaChart.js +21 -32
  36. package/dist/components/charts/AreaChart/AreaChart.theme.d.ts +1 -6
  37. package/dist/components/charts/AreaChart/AreaChart.theme.js +7 -19
  38. package/dist/components/charts/AreaChart/AreaChart.types.d.ts +18 -11
  39. package/dist/components/charts/AreaChart/AreaChart.types.js +10 -3
  40. package/dist/components/charts/AreaChart/index.d.ts +1 -1
  41. package/dist/components/charts/BarChart/BarChart.js +9 -28
  42. package/dist/components/charts/BarChart/BarChart.types.d.ts +20 -16
  43. package/dist/components/charts/BarChart/BarChart.types.js +11 -4
  44. package/dist/components/charts/ComboChart/BarRenderer.js +8 -5
  45. package/dist/components/charts/ComboChart/ComboChart.js +11 -24
  46. package/dist/components/charts/ComboChart/ComboChart.types.d.ts +15 -13
  47. package/dist/components/charts/ComboChart/ComboChart.types.js +14 -2
  48. package/dist/components/charts/ComboChart/LineRenderer.js +15 -10
  49. package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.d.ts +16 -0
  50. package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.js +237 -0
  51. package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.types.d.ts +62 -0
  52. package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.types.js +21 -0
  53. package/dist/components/charts/HorizontalBarChart/index.d.ts +3 -0
  54. package/dist/components/charts/LineChart/LineChart.js +9 -26
  55. package/dist/components/charts/LineChart/LineChart.types.d.ts +11 -14
  56. package/dist/components/charts/LineChart/LineChart.types.js +11 -3
  57. package/dist/components/charts/LollipopChart/LollipopChart.d.ts +3 -0
  58. package/dist/components/charts/LollipopChart/LollipopChart.js +361 -0
  59. package/dist/components/charts/LollipopChart/LollipopChart.types.d.ts +42 -0
  60. package/dist/components/charts/LollipopChart/LollipopChart.types.js +12 -0
  61. package/dist/components/charts/LollipopChart/index.d.ts +3 -0
  62. package/dist/components/charts/ScatterPlot/ScatterPlot.js +5 -4
  63. package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +14 -19
  64. package/dist/components/charts/ScatterPlot/ScatterPlot.types.js +9 -2
  65. package/dist/components/charts/StackedBarChart/StackedBarChart.js +150 -132
  66. package/dist/components/charts/StackedBarChart/StackedBarChart.types.d.ts +20 -18
  67. package/dist/components/charts/StackedBarChart/StackedBarChart.types.js +10 -4
  68. package/dist/components/charts/hooks/useChartAccessibility.d.ts +1 -1
  69. package/dist/components/charts/hooks/useChartAccessibility.js +3 -0
  70. package/dist/components/charts/hooks/useTooltipPosition.js +18 -6
  71. package/dist/components/charts/index.d.ts +6 -0
  72. package/dist/components/charts/index.js +6 -0
  73. package/dist/components/charts/shared/ChartAxis/CategoryYAxis.d.ts +14 -0
  74. package/dist/components/charts/shared/ChartAxis/CategoryYAxis.js +35 -0
  75. package/dist/components/charts/shared/ChartAxis/ChartAxis.types.d.ts +69 -8
  76. package/dist/components/charts/shared/ChartAxis/ValueXAxis.d.ts +14 -0
  77. package/dist/components/charts/shared/ChartAxis/ValueXAxis.js +64 -0
  78. package/dist/components/charts/shared/ChartAxis/XAxis.js +1 -1
  79. package/dist/components/charts/shared/ChartAxis/YAxis.js +2 -2
  80. package/dist/components/charts/shared/ChartAxis/index.d.ts +3 -1
  81. package/dist/components/charts/shared/ChartGrid/HorizontalGrid.d.ts +0 -2
  82. package/dist/components/charts/shared/ChartGrid/HorizontalGrid.js +1 -1
  83. package/dist/components/charts/shared/ChartGrid/VerticalGrid.d.ts +29 -0
  84. package/dist/components/charts/shared/ChartGrid/VerticalGrid.js +26 -0
  85. package/dist/components/charts/shared/ChartGrid/index.d.ts +2 -0
  86. package/dist/components/charts/shared/ChartMarker/ChartMarker.js +7 -1
  87. package/dist/components/charts/theme/chart.theme.js +18 -23
  88. package/dist/components/charts/types/chart.types.d.ts +9 -5
  89. package/dist/components/charts/types/chart.types.js +1 -7
  90. package/dist/components/charts/utils/animation-utils.js +1 -3
  91. package/dist/components/charts/utils/bar-emphasis.d.ts +32 -3
  92. package/dist/components/charts/utils/bar-emphasis.js +24 -8
  93. package/dist/components/charts/utils/index.d.ts +1 -0
  94. package/dist/components/charts/utils/series-color.d.ts +7 -0
  95. package/dist/components/charts/utils/series-color.js +20 -0
  96. package/dist/components/core/Accordion/Accordion.theme.js +2 -2
  97. package/dist/components/core/Accordion/AccordionTrigger.js +2 -2
  98. package/dist/components/core/Alert/Alert.animations.d.ts +2 -1
  99. package/dist/components/core/Alert/Alert.animations.js +21 -6
  100. package/dist/components/core/Alert/Alert.js +7 -7
  101. package/dist/components/core/Avatar/Avatar.js +50 -42
  102. package/dist/components/core/Avatar/Avatar.theme.d.ts +8 -4
  103. package/dist/components/core/Avatar/Avatar.theme.js +64 -8
  104. package/dist/components/core/Avatar/Avatar.types.d.ts +13 -2
  105. package/dist/components/core/Avatar/index.d.ts +1 -1
  106. package/dist/components/core/AvatarGroup/AvatarGroup.js +6 -8
  107. package/dist/components/core/AvatarGroup/AvatarGroup.theme.d.ts +1 -2
  108. package/dist/components/core/AvatarGroup/AvatarGroup.theme.js +2 -6
  109. package/dist/components/core/AvatarGroup/AvatarGroup.types.d.ts +0 -6
  110. package/dist/components/core/AvatarGroup/index.d.ts +1 -1
  111. package/dist/components/core/Badge/Badge.animations.js +4 -2
  112. package/dist/components/core/Badge/Badge.js +9 -4
  113. package/dist/components/core/Badge/Badge.theme.js +18 -12
  114. package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.d.ts +1 -4
  115. package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +2 -2
  116. package/dist/components/core/Button/Button.js +9 -17
  117. package/dist/components/core/Button/Button.ripple.js +2 -2
  118. package/dist/components/core/Button/Button.theme.d.ts +2 -0
  119. package/dist/components/core/Button/Button.theme.js +34 -21
  120. package/dist/components/core/Button/Button.types.d.ts +18 -1
  121. package/dist/components/core/Calendar/Calendar.js +20 -5
  122. package/dist/components/core/Calendar/Calendar.theme.js +14 -22
  123. package/dist/components/core/Calendar/Calendar.types.d.ts +0 -1
  124. package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +3 -3
  125. package/dist/components/core/Card/Card.theme.js +12 -4
  126. package/dist/components/core/Card/CardBody.js +1 -1
  127. package/dist/components/core/Card/CardFooter.js +1 -1
  128. package/dist/components/core/Card/CardHeader.js +1 -1
  129. package/dist/components/core/Carousel/CarouselNext.js +2 -2
  130. package/dist/components/core/Carousel/CarouselPrevious.js +2 -2
  131. package/dist/components/core/CommandPalette/CommandPalette.js +5 -4
  132. package/dist/components/core/CommandPalette/CommandPalette.theme.js +3 -7
  133. package/dist/components/core/CommandPalette/CommandPalette.types.d.ts +2 -0
  134. package/dist/components/core/ContextMenu/ContextMenuItem.js +2 -2
  135. package/dist/components/core/ContextMenu/ContextMenuSubTrigger.js +2 -2
  136. package/dist/components/core/Drawer/Drawer.theme.js +7 -4
  137. package/dist/components/core/Drawer/DrawerHeader.js +2 -2
  138. package/dist/components/core/Dropdown/Dropdown.theme.js +3 -1
  139. package/dist/components/core/Dropdown/DropdownItem.js +2 -2
  140. package/dist/components/core/Dropdown/DropdownTrigger.js +16 -5
  141. package/dist/components/core/HeroCard/HeroCard.theme.js +5 -2
  142. package/dist/components/core/Kbd/Kbd.theme.js +4 -2
  143. package/dist/components/core/Link/Link.js +6 -4
  144. package/dist/components/core/Message/Message.js +2 -2
  145. package/dist/components/core/Message/Message.theme.js +5 -7
  146. package/dist/components/core/Message/MessageAudio.js +2 -2
  147. package/dist/components/core/Message/MessageFile.js +2 -2
  148. package/dist/components/core/Message/MessageLink.js +2 -2
  149. package/dist/components/core/Message/MessageStatusIndicator.js +5 -5
  150. package/dist/components/core/Modal/Modal.d.ts +1 -1
  151. package/dist/components/core/Modal/Modal.js +2 -2
  152. package/dist/components/core/Modal/Modal.theme.js +9 -4
  153. package/dist/components/core/Modal/ModalHeader.js +2 -2
  154. package/dist/components/core/ModalStack/ModalStackModal.js +2 -2
  155. package/dist/components/core/NavItem/NavItem.js +6 -6
  156. package/dist/components/core/OfflineIndicator/OfflineIndicator.animations.d.ts +22 -0
  157. package/dist/components/core/OfflineIndicator/OfflineIndicator.animations.js +54 -0
  158. package/dist/components/core/OfflineIndicator/OfflineIndicator.js +23 -24
  159. package/dist/components/core/OfflineIndicator/OfflineIndicator.theme.js +25 -6
  160. package/dist/components/core/OfflineIndicator/OfflineIndicator.types.d.ts +17 -0
  161. package/dist/components/core/PageHeading/PageHeading.theme.js +1 -1
  162. package/dist/components/core/PageHeading/PageHeadingBackButton.js +2 -2
  163. package/dist/components/core/Pagination/Pagination.js +3 -3
  164. package/dist/components/core/Pagination/Pagination.theme.js +3 -3
  165. package/dist/components/core/Pill/Pill.js +4 -2
  166. package/dist/components/core/Pill/Pill.theme.js +6 -4
  167. package/dist/components/core/Rating/Rating.js +4 -5
  168. package/dist/components/core/Rating/Rating.theme.js +1 -1
  169. package/dist/components/core/Segmented/Segmented.theme.js +9 -6
  170. package/dist/components/core/Sidebar/Sidebar.types.d.ts +4 -2
  171. package/dist/components/core/Sidebar/SidebarHeader.js +2 -2
  172. package/dist/components/core/Sidebar/SidebarMobileTrigger.js +2 -2
  173. package/dist/components/core/Sidebar/SidebarSearch.js +3 -3
  174. package/dist/components/core/Sidebar/SidebarToggle.js +3 -3
  175. package/dist/components/core/Sidebar/SidebarUserProfile.js +2 -2
  176. package/dist/components/core/Sortable/Sortable.js +2 -2
  177. package/dist/components/core/Tag/Tag.js +2 -2
  178. package/dist/components/core/Tag/Tag.theme.js +4 -2
  179. package/dist/components/core/Toast/Toast.js +3 -3
  180. package/dist/components/core/Toast/Toast.types.d.ts +1 -1
  181. package/dist/components/core/Toast/useToast.js +5 -5
  182. package/dist/components/core/Tooltip/Tooltip.d.ts +1 -1
  183. package/dist/components/core/Tooltip/Tooltip.js +42 -10
  184. package/dist/components/core/Tooltip/Tooltip.types.d.ts +25 -0
  185. package/dist/components/core/Tree/Tree.js +2 -2
  186. package/dist/components/core/shared/interaction.animations.d.ts +51 -0
  187. package/dist/components/core/shared/interaction.animations.js +59 -0
  188. package/dist/components/data-display/Feed/Feed.js +2 -2
  189. package/dist/components/data-display/GanttChart/GanttChart.theme.js +1 -1
  190. package/dist/components/data-display/GanttChart/GanttChartPanel.js +2 -2
  191. package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +3 -3
  192. package/dist/components/data-display/GanttChart/GanttTaskContextMenu.js +2 -2
  193. package/dist/components/data-display/GanttChart/GanttToolbar.js +2 -2
  194. package/dist/components/data-display/KPI/KPI.js +2 -2
  195. package/dist/components/data-display/KPI/KPI.theme.js +4 -4
  196. package/dist/components/data-display/Metric/Metric.js +2 -2
  197. package/dist/components/data-display/Metric/Metric.theme.js +25 -8
  198. package/dist/components/data-display/Metric/Metric.types.d.ts +1 -1
  199. package/dist/components/data-display/Table/Table.theme.js +9 -9
  200. package/dist/components/data-display/Table/TableActions.js +2 -2
  201. package/dist/components/data-display/Table/TableActionsMenu.js +1 -1
  202. package/dist/components/data-display/Table/TableColumnManager.js +2 -2
  203. package/dist/components/data-display/Table/TableDeclarative.js +3 -3
  204. package/dist/components/data-display/Table/TableFilter.js +4 -4
  205. package/dist/components/data-display/Table/TableHeader.js +3 -3
  206. package/dist/components/data-display/Table/TableRow.js +2 -2
  207. package/dist/components/data-display/Table/TableSelectionHeader.js +3 -3
  208. package/dist/components/data-display/Timeline/TimelineMarker.js +2 -2
  209. package/dist/components/effects/CustomCursor/CustomCursor.js +2 -2
  210. package/dist/components/effects/CustomCursor/CustomCursor.theme.js +2 -2
  211. package/dist/components/effects/DotPattern/DotPattern.js +6 -0
  212. package/dist/components/effects/DotPattern/DotPattern.types.d.ts +4 -2
  213. package/dist/components/effects/MeshGradient/MeshGradient.utils.d.ts +1 -1
  214. package/dist/components/effects/MeshGradient/MeshGradient.utils.js +1 -1
  215. package/dist/components/forms/Checkbox/Checkbox.js +2 -1
  216. package/dist/components/forms/Checkbox/Checkbox.theme.js +2 -2
  217. package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +2 -2
  218. package/dist/components/forms/ColorPicker/ColorPickerGradient.js +2 -2
  219. package/dist/components/forms/ColorPicker/ColorPickerHeader.js +2 -2
  220. package/dist/components/forms/ColorPicker/ColorPickerImage.js +3 -3
  221. package/dist/components/forms/ColorPicker/ColorPickerInput.js +2 -2
  222. package/dist/components/forms/ColorPicker/ColorPickerPresets.js +2 -2
  223. package/dist/components/forms/ColorPicker/ColorPickerSavedColors.js +2 -2
  224. package/dist/components/forms/Combobox/Combobox.js +3 -3
  225. package/dist/components/forms/Combobox/Combobox.theme.js +2 -2
  226. package/dist/components/forms/DatePicker/DatePicker.animations.js +16 -25
  227. package/dist/components/forms/DatePicker/DatePicker.theme.js +2 -2
  228. package/dist/components/forms/DatePicker/DatePickerBody.js +1 -1
  229. package/dist/components/forms/DatePicker/DatePickerTrigger.js +2 -2
  230. package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +3 -3
  231. package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +1 -1
  232. package/dist/components/forms/DateRangePicker/DateRangePickerPresets.js +2 -2
  233. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +2 -2
  234. package/dist/components/forms/FileUpload/FileUpload.js +4 -4
  235. package/dist/components/forms/FileUpload/FileUpload.theme.js +5 -1
  236. package/dist/components/forms/FileUpload/FileUploadProgress.js +7 -7
  237. package/dist/components/forms/Input/Input.js +6 -7
  238. package/dist/components/forms/Input/Input.theme.js +19 -15
  239. package/dist/components/forms/InputAddress/InputAddress.js +10 -3
  240. package/dist/components/forms/InputCounter/InputCounter.js +3 -3
  241. package/dist/components/forms/InputOTP/InputOTP.d.ts +3 -12
  242. package/dist/components/forms/InputOTP/InputOTP.js +140 -151
  243. package/dist/components/forms/InputOTP/InputOTP.theme.d.ts +5 -1
  244. package/dist/components/forms/InputOTP/InputOTP.theme.js +40 -5
  245. package/dist/components/forms/InputOTP/InputOTP.types.d.ts +45 -2
  246. package/dist/components/forms/InputOTP/index.d.ts +1 -1
  247. package/dist/components/forms/InputTag/InputTag.js +5 -5
  248. package/dist/components/forms/InputTag/InputTag.theme.js +1 -2
  249. package/dist/components/forms/Mention/Mention.theme.js +3 -5
  250. package/dist/components/forms/Radio/Radio.js +3 -2
  251. package/dist/components/forms/Radio/Radio.theme.js +2 -2
  252. package/dist/components/forms/RichTextEditor/RichTextEditor.js +22 -23
  253. package/dist/components/forms/Select/Select.d.ts +5 -1
  254. package/dist/components/forms/Select/Select.js +12 -11
  255. package/dist/components/forms/Select/Select.theme.js +4 -4
  256. package/dist/components/forms/Select/Select.types.d.ts +4 -1
  257. package/dist/components/forms/SelectableCard/SelectableCard.animations.d.ts +1 -2
  258. package/dist/components/forms/SelectableCard/SelectableCard.animations.js +6 -7
  259. package/dist/components/forms/SelectableCard/SelectableCard.js +1 -1
  260. package/dist/components/forms/Signature/Signature.js +1 -1
  261. package/dist/components/forms/Slider/Slider.js +24 -11
  262. package/dist/components/forms/Slider/Slider.theme.js +9 -3
  263. package/dist/components/forms/Switch/Switch.theme.js +1 -1
  264. package/dist/components/forms/TimePicker/TimePicker.theme.js +6 -7
  265. package/dist/components/forms/TimePicker/TimePickerContent.js +1 -1
  266. package/dist/components/forms/TimePicker/TimePickerTrigger.js +3 -3
  267. package/dist/components/forms/forms.theme.js +23 -12
  268. package/dist/components/forms/index.js +1 -1
  269. package/dist/components/generative/registry.js +195 -2
  270. package/dist/components/generative/resolvers.js +16 -16
  271. package/dist/components/generative/schema.generated.js +2687 -379
  272. package/dist/components/layout/Section/Section.theme.js +1 -1
  273. package/dist/generative.schema.json +2687 -379
  274. package/dist/hooks/index.d.ts +2 -0
  275. package/dist/hooks/useElapsedTime.d.ts +28 -0
  276. package/dist/hooks/useElapsedTime.js +37 -0
  277. package/dist/hooks/useSelectPortal.d.ts +10 -2
  278. package/dist/hooks/useSelectPortal.js +6 -3
  279. package/dist/index.js +1 -1
  280. package/dist/registry.json +835 -261
  281. package/dist/shadcn-compat.css +1 -1
  282. package/dist/styles.css +1 -1
  283. package/dist/theme.css +5 -1
  284. package/dist/themes/ember.css +131 -0
  285. package/dist/themes/iris.css +94 -0
  286. package/dist/themes/jade.css +139 -0
  287. package/dist/tools.json +2705 -361
  288. package/dist/utils/colorUtils.d.ts +6 -6
  289. package/dist/utils/colorUtils.js +8 -8
  290. package/package.json +9 -6
  291. package/src/global.scss +15 -4
  292. package/src/styles/theme.css +440 -152
  293. package/tailwind.preset.cjs +52 -34
  294. package/dist/components/core/Pill/Pill.animations.d.ts +0 -32
  295. package/dist/components/core/Pill/Pill.animations.js +0 -16
@@ -1,195 +1,184 @@
1
1
  "use client";
2
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
- import React__default, { useRef, useState, useCallback, useEffect } from 'react';
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
- * @component
14
- * @example
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
- const inputRefs = useRef([]);
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 helperTextId = `${idProp !== null && idProp !== void 0 ? idProp : generatedId}-helper`;
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
- const setValue = useCallback((next) => {
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
- }, [isControlled, onChange]);
43
- // Convert value to array of characters
44
- const valueArray = currentValue.padEnd(length, '').split('').slice(0, length);
45
- // Auto-focus first input on mount
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 && inputRefs.current[0] && !isDisabled) {
48
- inputRefs.current[0].focus();
85
+ if (autoFocus && inputRef.current && !isDisabled) {
86
+ inputRef.current.focus();
49
87
  }
50
88
  }, [autoFocus, isDisabled]);
51
- // Handle input change
52
- const handleChange = useCallback((index, newValue) => {
53
- var _a;
89
+ const handleInputChange = (e) => {
54
90
  if (isDisabled)
55
91
  return;
56
- // Only allow single character (take last character if multiple)
57
- const char = newValue.slice(-1);
58
- // Create new value array
59
- const newValueArray = [...valueArray];
60
- newValueArray[index] = char;
61
- // Convert back to string and remove trailing spaces
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
- switch (event.key) {
79
- case 'Backspace':
80
- if (!valueArray[index] && index > 0) {
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
- event.preventDefault();
120
- const pastedData = event.clipboardData.getData('text').slice(0, length);
121
- if (pastedData) {
122
- setValue(pastedData);
123
- // Focus the next empty field or last field
124
- const nextIndex = Math.min(pastedData.length, length - 1);
125
- (_a = inputRefs.current[nextIndex]) === null || _a === void 0 ? void 0 : _a.focus();
126
- // Check if OTP is complete
127
- if (pastedData.length === length) {
128
- onComplete === null || onComplete === void 0 ? void 0 : onComplete(pastedData);
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
- }, [isDisabled, length, setValue, onComplete]);
132
- // Handle focus and blur (simplified since we removed focus state)
133
- const handleFocus = useCallback((index) => {
134
- // Focus logic can be added here if needed
135
- }, []);
136
- const handleBlur = useCallback(() => {
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
- ? "state-invalid"
130
+ ? 'state-invalid'
142
131
  : isDisabled
143
- ? "state-disabled"
144
- : "state-default";
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
- // Get input classes based on position within group
159
- const getInputClasses = (index) => {
160
- if (length === 6) {
161
- // For 6-digit: two groups of 3
162
- const isFirst = index === 0 || index === 3;
163
- const isLast = index === 2 || index === 5;
164
- return cn(theme.inputGroupStyle, 'relative focus-within:z-10', {
165
- 'rounded-r-none -mr-px': !isLast,
166
- 'rounded-l-none': !isFirst,
167
- });
168
- }
169
- else {
170
- // For other lengths: one group
171
- const isFirst = index === 0;
172
- const isLast = index === length - 1;
173
- return cn(theme.inputGroupStyle, 'relative focus-within:z-10', {
174
- 'rounded-r-none -mr-px': !isLast,
175
- 'rounded-l-none': !isFirst,
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
- return (jsxs("div", { ref: ref, className: wrapperClasses, ...props, children: [label && (typeof label === 'string' ? (jsx(FormLabel, { htmlFor: "otp-input-0", state: labelState, children: label })) : (label)), jsx("div", { className: containerClasses, children: length === 6 ? (
180
- // For 6-digit OTP, create two groups of 3
181
- jsxs(Fragment, { children: [jsx("div", { className: "flex", children: Array.from({ length: 3 }, (_, i) => (jsx(Input, { ref: (el) => {
182
- inputRefs.current[i] = el;
183
- }, id: `otp-input-${i}`, type: "text", value: valueArray[i] || '', onChange: (e) => handleChange(i, e.target.value), onKeyDown: (e) => handleKeyDown(i, e), onFocus: () => handleFocus(i), onBlur: handleBlur, onPaste: i === 0 ? handlePaste : undefined, disabled: isDisabled, state: isInvalid ? 'invalid' : isDisabled ? 'disabled' : 'default', "aria-label": getSlotAriaLabel(i), "aria-describedby": displayedHelperText ? helperTextId : undefined, maxLength: 1, className: theme.inputStyle, inputGroupClassName: getInputClasses(i), wrapperClassName: "space-y-0 w-auto" }, i))) }), jsx("span", { className: theme.separatorStyle, children: "\u2014" }), jsx("div", { className: "flex", children: Array.from({ length: 3 }, (_, i) => {
184
- const index = i + 3;
185
- return (jsx(Input, { ref: (el) => {
186
- inputRefs.current[index] = el;
187
- }, id: `otp-input-${index}`, type: "text", value: valueArray[index] || '', onChange: (e) => handleChange(index, e.target.value), onKeyDown: (e) => handleKeyDown(index, e), onFocus: () => handleFocus(index), onBlur: handleBlur, disabled: isDisabled, state: isInvalid ? 'invalid' : isDisabled ? 'disabled' : 'default', "aria-label": getSlotAriaLabel(index), "aria-describedby": displayedHelperText ? helperTextId : undefined, maxLength: 1, className: theme.inputStyle, inputGroupClassName: getInputClasses(index), wrapperClassName: "space-y-0 w-auto" }, index));
188
- }) })] })) : (
189
- // For other lengths, create one group
190
- jsx("div", { className: "flex", children: Array.from({ length }, (_, index) => (jsx(Input, { ref: (el) => {
191
- inputRefs.current[index] = el;
192
- }, id: `otp-input-${index}`, type: "text", value: valueArray[index] || '', onChange: (e) => handleChange(index, e.target.value), onKeyDown: (e) => handleKeyDown(index, e), onFocus: () => handleFocus(index), onBlur: handleBlur, onPaste: index === 0 ? handlePaste : undefined, disabled: isDisabled, state: isInvalid ? 'invalid' : isDisabled ? 'disabled' : 'default', "aria-label": getSlotAriaLabel(index), "aria-describedby": displayedHelperText ? helperTextId : undefined, maxLength: 1, className: theme.inputStyle, inputGroupClassName: getInputClasses(index), wrapperClassName: "space-y-0 w-auto" }, index))) })) }), displayedHelperText && (jsx("div", { id: helperTextId, className: cn(theme.helperText), children: displayedHelperText }))] }));
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
- // Individual input field styles with design tokens
13
- inputStyle: 'text-center text-lg text-[var(--color-text-primary)] h-full !p-0',
14
- // Input group wrapper for grouped inputs (overrides default Input styling)
15
- inputGroupStyle: 'w-10 min-w-10 h-10 min-h-10 sm:w-12 sm:min-w-12 sm:h-12 sm:min-h-12',
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
- inputStyle: string;
69
- inputGroupStyle: string;
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
  }
@@ -1,3 +1,3 @@
1
1
  export { InputOTP } from './InputOTP';
2
- export type { InputOTPProps, InputOTPTheme } from './InputOTP.types';
2
+ export type { InputOTPProps, InputOTPTheme, InputOTPSize } from './InputOTP.types';
3
3
  export { inputOTPTheme } from './InputOTP.theme';
@@ -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 { TagIcon, PlusIcon, CheckIcon } from '@heroicons/react/24/outline';
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(PlusIcon, { className: "size-4 shrink-0", strokeWidth: 2 }), 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(CheckIcon, { className: "size-4 shrink-0 text-[var(--color-primary)]", strokeWidth: 2 }))] })) }, option.id));
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(TagIcon, { className: "size-4" }), placeholder: maxTags && tags.length >= maxTags
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
- // p-1 (not py-1) so MenuItem rows' rounded highlight insets evenly on all
29
- // sides instead of running flush to the container's left/right edges.
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-neutral-700)] dark:disabled:border-[var(--color-neutral-600)] " +
27
- "dark:data-[state=disabled]:bg-[var(--color-neutral-700)] dark:data-[state=disabled]:border-[var(--color-neutral-600)]",
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.