@flikk/ui 1.0.0-beta.22 → 1.0.0-beta.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (161) hide show
  1. package/dist/components/ai/PromptInput/PromptInput.js +139 -7
  2. package/dist/components/ai/PromptInput/VoiceRecorder.js +84 -0
  3. package/dist/components/charts/AreaChart/AreaChart.types.d.ts +2 -2
  4. package/dist/components/charts/BarChart/BarChart.types.d.ts +2 -2
  5. package/dist/components/charts/ComboChart/ComboChart.types.d.ts +2 -2
  6. package/dist/components/charts/LineChart/LineChart.types.d.ts +2 -2
  7. package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +2 -2
  8. package/dist/components/charts/StackedBarChart/StackedBarChart.types.d.ts +2 -2
  9. package/dist/components/core/Accordion/AccordionTrigger.js +28 -1
  10. package/dist/components/core/Alert/Alert.js +2 -1
  11. package/dist/components/core/AlertDialog/AlertDialog.js +1 -1
  12. package/dist/components/core/Badge/Badge.js +6 -17
  13. package/dist/components/core/Breadcrumbs/Breadcrumbs.js +17 -28
  14. package/dist/components/core/Calendar/Calendar.js +1 -1
  15. package/dist/components/core/Carousel/CarouselBody.js +6 -1
  16. package/dist/components/core/ContextMenu/ContextMenuBody.js +6 -3
  17. package/dist/components/core/ContextMenu/ContextMenuSubBody.js +6 -3
  18. package/dist/components/core/DragDrop/DragDrop.js +170 -17
  19. package/dist/components/core/DragDrop/DragDrop.types.d.ts +6 -0
  20. package/dist/components/core/Drawer/Drawer.d.ts +1 -1
  21. package/dist/components/core/Drawer/Drawer.js +2 -0
  22. package/dist/components/core/Drawer/Drawer.types.d.ts +2 -0
  23. package/dist/components/core/Drawer/DrawerTitle.d.ts +1 -1
  24. package/dist/components/core/Drawer/DrawerTitle.js +2 -2
  25. package/dist/components/core/Dropdown/DropdownMenu.js +2 -30
  26. package/dist/components/core/Masonry/Masonry.js +7 -2
  27. package/dist/components/core/Modal/Modal.d.ts +1 -1
  28. package/dist/components/core/Modal/Modal.js +3 -3
  29. package/dist/components/core/Modal/Modal.types.d.ts +2 -0
  30. package/dist/components/core/Modal/ModalTitle.d.ts +1 -1
  31. package/dist/components/core/Modal/ModalTitle.js +2 -2
  32. package/dist/components/core/NavItem/NavItem.js +21 -6
  33. package/dist/components/core/NavItem/NavItem.theme.js +2 -2
  34. package/dist/components/core/Pagination/Pagination.js +1 -1
  35. package/dist/components/core/Popover/PopoverBody.js +3 -27
  36. package/dist/components/core/Popover/PopoverTrigger.js +1 -1
  37. package/dist/components/core/Progress/Progress.js +4 -4
  38. package/dist/components/core/Rating/Rating.js +1 -1
  39. package/dist/components/core/ScrollArea/ScrollArea.js +3 -2
  40. package/dist/components/core/Segmented/Segmented.js +6 -6
  41. package/dist/components/core/Segmented/SegmentedItem.js +28 -2
  42. package/dist/components/core/Sidebar/Sidebar.d.ts +1 -0
  43. package/dist/components/core/Sidebar/Sidebar.js +5 -3
  44. package/dist/components/core/Sidebar/Sidebar.types.d.ts +7 -1
  45. package/dist/components/core/Sidebar/SidebarContent.js +1 -1
  46. package/dist/components/core/Sidebar/SidebarFooter.js +1 -1
  47. package/dist/components/core/Sidebar/SidebarHeader.js +5 -2
  48. package/dist/components/core/Sidebar/SidebarLogo.d.ts +3 -0
  49. package/dist/components/core/Sidebar/SidebarLogo.js +17 -0
  50. package/dist/components/core/Sidebar/SidebarNavGroup.js +2 -1
  51. package/dist/components/core/Sidebar/SidebarToggle.js +2 -4
  52. package/dist/components/core/Sidebar/SidebarUserProfile.js +3 -6
  53. package/dist/components/core/Sidebar/index.d.ts +2 -1
  54. package/dist/components/core/Skeleton/Skeleton.js +2 -2
  55. package/dist/components/core/Sortable/Sortable.d.ts +1 -1
  56. package/dist/components/core/Sortable/Sortable.js +20 -4
  57. package/dist/components/core/Sortable/Sortable.types.d.ts +2 -0
  58. package/dist/components/core/Tabs/Tabs.js +6 -4
  59. package/dist/components/core/Tabs/Tabs.types.d.ts +2 -0
  60. package/dist/components/core/Tabs/TabsBody.js +3 -3
  61. package/dist/components/core/Tabs/TabsList.js +19 -21
  62. package/dist/components/core/Tabs/TabsTrigger.js +47 -28
  63. package/dist/components/core/Toast/Toast.js +1 -1
  64. package/dist/components/core/Toast/ToastProvider.js +3 -2
  65. package/dist/components/core/Tooltip/Tooltip.d.ts +1 -1
  66. package/dist/components/core/Tooltip/Tooltip.js +26 -35
  67. package/dist/components/core/Tooltip/Tooltip.types.d.ts +5 -1
  68. package/dist/components/core/Tree/Tree.js +178 -8
  69. package/dist/components/core/index.js +1 -0
  70. package/dist/components/data-display/GanttChart/GanttSplitter.js +15 -1
  71. package/dist/components/data-display/Table/Table.js +2 -2
  72. package/dist/components/data-display/Table/Table.types.d.ts +2 -0
  73. package/dist/components/data-display/Table/TableFilter.js +21 -18
  74. package/dist/components/effects/CustomCursor/CustomCursor.js +3 -7
  75. package/dist/components/effects/Overlay/Overlay.js +4 -9
  76. package/dist/components/effects/PageTransition/PageTransition.js +17 -6
  77. package/dist/components/effects/SpotlightBorder/SpotlightBorder.js +3 -4
  78. package/dist/components/forms/Checkbox/Checkbox.js +11 -1
  79. package/dist/components/forms/Checkbox/Checkbox.theme.js +4 -1
  80. package/dist/components/forms/Checkbox/Checkbox.types.d.ts +7 -5
  81. package/dist/components/forms/Checkbox/CheckboxGroup.js +9 -4
  82. package/dist/components/forms/ColorPicker/ColorPicker.js +7 -4
  83. package/dist/components/forms/ColorPicker/ColorPicker.types.d.ts +2 -2
  84. package/dist/components/forms/ColorPicker/ColorPickerBody.js +5 -3
  85. package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +3 -3
  86. package/dist/components/forms/ColorPicker/ColorPickerGradient.js +8 -8
  87. package/dist/components/forms/ColorPicker/ColorPickerInput.js +1 -1
  88. package/dist/components/forms/ColorPicker/index.d.ts +1 -1
  89. package/dist/components/forms/Combobox/Combobox.js +20 -45
  90. package/dist/components/forms/Combobox/Combobox.types.d.ts +4 -2
  91. package/dist/components/forms/CronInput/CronInput.js +10 -4
  92. package/dist/components/forms/CronInput/CronInput.types.d.ts +12 -1
  93. package/dist/components/forms/DatePicker/DatePicker.js +2 -2
  94. package/dist/components/forms/DatePicker/DatePicker.types.d.ts +4 -2
  95. package/dist/components/forms/DatePicker/DatePickerBody.js +1 -29
  96. package/dist/components/forms/DatePicker/DatePickerTrigger.js +2 -2
  97. package/dist/components/forms/DateRangePicker/DateRangePicker.js +3 -3
  98. package/dist/components/forms/DateRangePicker/DateRangePicker.types.d.ts +4 -2
  99. package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +1 -28
  100. package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +2 -2
  101. package/dist/components/forms/FileUpload/FileUpload.js +99 -10
  102. package/dist/components/forms/FileUpload/FileUpload.types.d.ts +15 -0
  103. package/dist/components/forms/FormLabel/FormLabel.js +2 -2
  104. package/dist/components/forms/Input/Input.js +5 -3
  105. package/dist/components/forms/Input/Input.types.d.ts +2 -0
  106. package/dist/components/forms/InputAddress/InputAddress.d.ts +1 -1
  107. package/dist/components/forms/InputAddress/InputAddress.js +8 -9
  108. package/dist/components/forms/InputCounter/InputCounter.js +25 -18
  109. package/dist/components/forms/InputCounter/InputCounter.types.d.ts +8 -1
  110. package/dist/components/forms/InputCreditCard/InputCreditCard.d.ts +1 -1
  111. package/dist/components/forms/InputCreditCard/InputCreditCard.js +27 -9
  112. package/dist/components/forms/InputCreditCard/InputCreditCard.types.d.ts +5 -0
  113. package/dist/components/forms/InputOTP/InputOTP.js +26 -17
  114. package/dist/components/forms/InputOTP/InputOTP.types.d.ts +21 -4
  115. package/dist/components/forms/InputTag/InputTag.js +12 -39
  116. package/dist/components/forms/InputTag/InputTag.types.d.ts +2 -2
  117. package/dist/components/forms/Mention/Mention.js +13 -6
  118. package/dist/components/forms/Mention/Mention.types.d.ts +9 -0
  119. package/dist/components/forms/Radio/Radio.js +12 -2
  120. package/dist/components/forms/Radio/Radio.theme.js +5 -1
  121. package/dist/components/forms/Radio/Radio.types.d.ts +7 -5
  122. package/dist/components/forms/Radio/RadioGroup.js +8 -3
  123. package/dist/components/forms/RichTextEditor/RichTextEditor.js +21 -21
  124. package/dist/components/forms/RichTextEditor/RichTextEditor.types.d.ts +2 -2
  125. package/dist/components/forms/Select/Select.d.ts +2 -0
  126. package/dist/components/forms/Select/Select.js +26 -56
  127. package/dist/components/forms/Select/Select.types.d.ts +14 -0
  128. package/dist/components/forms/Signature/Signature.js +1 -1
  129. package/dist/components/forms/Signature/Signature.types.d.ts +3 -3
  130. package/dist/components/forms/Slider/Slider.js +18 -4
  131. package/dist/components/forms/Slider/Slider.types.d.ts +2 -2
  132. package/dist/components/forms/Switch/Switch.js +12 -19
  133. package/dist/components/forms/Switch/Switch.theme.js +12 -2
  134. package/dist/components/forms/Switch/Switch.types.d.ts +20 -2
  135. package/dist/components/forms/Textarea/Textarea.js +8 -3
  136. package/dist/components/forms/Textarea/Textarea.types.d.ts +2 -0
  137. package/dist/components/forms/TimePicker/TimePicker.js +1 -1
  138. package/dist/components/forms/TimePicker/TimePicker.types.d.ts +6 -2
  139. package/dist/components/forms/TimePicker/TimePickerContent.js +88 -30
  140. package/dist/components/forms/TimePicker/TimePickerTrigger.js +2 -2
  141. package/dist/components/forms/index.js +86 -1
  142. package/dist/components/layout/FormLayout/FormLayout.js +87 -1
  143. package/dist/components/layout/index.js +4 -0
  144. package/dist/hooks/index.d.ts +1 -0
  145. package/dist/hooks/useClickOutside.d.ts +10 -4
  146. package/dist/hooks/useClickOutside.js +44 -24
  147. package/dist/hooks/useDarkMode.d.ts +1 -0
  148. package/dist/hooks/useDarkMode.js +42 -0
  149. package/dist/hooks/useFormStateMachine.d.ts +1 -1
  150. package/dist/hooks/useNetworkStatus.js +1 -1
  151. package/dist/hooks/useOverlay.js +4 -0
  152. package/dist/hooks/useScaleBackground.js +5 -2
  153. package/dist/hooks/useSelectPortal.js +14 -5
  154. package/dist/index.d.ts +0 -2
  155. package/dist/index.js +174 -223
  156. package/dist/shadcn-compat.css +80 -0
  157. package/dist/styles.css +1 -1
  158. package/dist/utils/stateMachine.d.ts +4 -4
  159. package/package.json +12 -10
  160. package/tailwind.preset.cjs +32 -3
  161. /package/{src/theme-plugin.css → dist/theme.css} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
- import { useRef, useCallback } from 'react';
2
+ import { useRef, useEffect, useCallback } from 'react';
3
3
  import { cn } from '../../../utils/cn.js';
4
4
  import { useGanttContext } from './GanttChart.js';
5
5
 
@@ -10,6 +10,18 @@ const GanttSplitter = () => {
10
10
  const { theme, tablePanelWidth, setTablePanelWidth } = useGanttContext();
11
11
  const startX = useRef(0);
12
12
  const startWidth = useRef(0);
13
+ // Track active pointer listeners so they can be cleaned up on unmount during a drag
14
+ const activeListenersRef = useRef({ move: null, up: null });
15
+ // Cleanup on unmount — removes any lingering listeners if component unmounts during a drag
16
+ useEffect(() => {
17
+ return () => {
18
+ const { move, up } = activeListenersRef.current;
19
+ if (move)
20
+ document.removeEventListener("pointermove", move);
21
+ if (up)
22
+ document.removeEventListener("pointerup", up);
23
+ };
24
+ }, []);
13
25
  const onPointerDown = useCallback((e) => {
14
26
  e.preventDefault();
15
27
  startX.current = e.clientX;
@@ -20,9 +32,11 @@ const GanttSplitter = () => {
20
32
  setTablePanelWidth(next);
21
33
  };
22
34
  const onPointerUp = () => {
35
+ activeListenersRef.current = { move: null, up: null };
23
36
  document.removeEventListener("pointermove", onPointerMove);
24
37
  document.removeEventListener("pointerup", onPointerUp);
25
38
  };
39
+ activeListenersRef.current = { move: onPointerMove, up: onPointerUp };
26
40
  document.addEventListener("pointermove", onPointerMove);
27
41
  document.addEventListener("pointerup", onPointerUp);
28
42
  }, [tablePanelWidth, setTablePanelWidth]);
@@ -38,7 +38,7 @@ const TableContext = createContext({
38
38
  */
39
39
  function TableComponent(props) {
40
40
  var _a;
41
- const { data, columns, rowKey, variant = "default", theme: themeOverrides, className = "", freezeHeader = false, freezeFirstColumn = false, freezeLastColumn = false, sortable = false, sortConfig = [], onSortChange, filterable = false, filterConfig = [], onFilterChange, filterLogic: controlledFilterLogic, enableBuiltInFilter = false, expandable = false, expandedRowRender, expandedRows = [], onExpandedRowsChange, selectable = false, selectionType = "checkbox", selectedRows = [], onSelectionChange, bulkActions = [], showColumnManager = false, visibleColumns: initialVisibleColumns, onVisibleColumnsChange, onColumnReorder, pagination, children, } = props;
41
+ const { data, columns, rowKey, variant = "default", theme: themeOverrides, className = "", freezeHeader = false, freezeFirstColumn = false, freezeLastColumn = false, sortable = false, sortConfig = [], onSortChange, filterable = false, filterConfig = [], onFilterChange, filterLogic: controlledFilterLogic, enableBuiltInFilter = false, expandable = false, expandedRowRender, expandedRows = [], onExpandedRowsChange, selectable = false, selectionType = "checkbox", selectedRows = [], onSelectionChange, bulkActions = [], showColumnManager = false, visibleColumns: initialVisibleColumns, onVisibleColumnsChange, onColumnReorder, pagination, children, caption, } = props;
42
42
  // Column management hook
43
43
  const { visibleColumns, columnOrder, visibleColumnsList, handleToggleColumn, handleColumnReorder, handleResetToDefault, } = useTableColumns({
44
44
  columns,
@@ -148,7 +148,7 @@ function TableComponent(props) {
148
148
  selectedRows,
149
149
  onSelectionChange,
150
150
  ]);
151
- return (jsx(TableContext.Provider, { value: contextValue, children: jsxs("div", { className: "relative", children: [!isDeclarativeAPI && columns && ((filterable && onFilterChange) || showColumnManager) && (jsxs("div", { className: theme.columnManagerContainer, children: [filterable && onFilterChange && (jsx(TableFilter, { columns: columns, filterConfig: filterConfig, onFilterChange: onFilterChange, filterLogic: filterLogic, onFilterLogicChange: handleFilterLogicChange, data: data })), showColumnManager && (jsx(TableColumnManager, { columns: columns, visibleColumns: visibleColumns, columnOrder: columnOrder, onToggleColumn: handleToggleColumn, onResetToDefault: handleResetToDefault, onColumnReorder: onColumnReorder ? handleColumnReorder : undefined }))] })), !isDeclarativeAPI && selectable && selectedRows.length > 0 && (jsx(TableSelectionHeader, { selectedCount: selectedRows.length, totalCount: paginatedData.length, selectedRows: selectedRows, bulkActions: bulkActions, onClearSelection: handleClearSelection })), jsx("div", { className: "overflow-x-auto", ref: scrollContainerRef, children: jsx("table", { className: cn(baseClasses, variantClasses, className), role: "table", "aria-label": isDeclarativeAPI ? "Data table" : `Data table with ${paginatedData.length} rows`, "aria-rowcount": pagination ? pagination.totalItems : data === null || data === void 0 ? void 0 : data.length, "aria-colcount": visibleColumnsList.length, children: isDeclarativeAPI ? (children) : (jsxs(Fragment, { children: [jsx(TableHeader, { columns: visibleColumnsList, sortConfig: sortConfig, onSortChange: onSortChange, filterConfig: filterConfig, onFilterChange: onFilterChange, freezeHeader: freezeHeader, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, expandable: expandable, expandedRowRender: expandedRowRender, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, totalRowCount: paginatedData.length, theme: theme, data: paginatedData, rowKey: rowKey }), jsx(TableBody, { data: paginatedData, columns: visibleColumnsList, rowKey: rowKey, expandedRowRender: expandedRowRender, expandedRows: expandedRows, onExpandedRowsChange: onExpandedRowsChange, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, theme: theme })] })) }) })] }) }));
151
+ return (jsx(TableContext.Provider, { value: contextValue, children: jsxs("div", { className: "relative", children: [!isDeclarativeAPI && columns && ((filterable && onFilterChange) || showColumnManager) && (jsxs("div", { className: theme.columnManagerContainer, children: [filterable && onFilterChange && (jsx(TableFilter, { columns: columns, filterConfig: filterConfig, onFilterChange: onFilterChange, filterLogic: filterLogic, onFilterLogicChange: handleFilterLogicChange, data: data })), showColumnManager && (jsx(TableColumnManager, { columns: columns, visibleColumns: visibleColumns, columnOrder: columnOrder, onToggleColumn: handleToggleColumn, onResetToDefault: handleResetToDefault, onColumnReorder: onColumnReorder ? handleColumnReorder : undefined }))] })), !isDeclarativeAPI && selectable && selectedRows.length > 0 && (jsx(TableSelectionHeader, { selectedCount: selectedRows.length, totalCount: paginatedData.length, selectedRows: selectedRows, bulkActions: bulkActions, onClearSelection: handleClearSelection })), jsx("div", { className: "overflow-x-auto", ref: scrollContainerRef, children: jsxs("table", { className: cn(baseClasses, variantClasses, className), role: "table", "aria-label": isDeclarativeAPI ? "Data table" : `Data table with ${paginatedData.length} rows`, "aria-rowcount": pagination ? pagination.totalItems : data === null || data === void 0 ? void 0 : data.length, "aria-colcount": visibleColumnsList.length, children: [caption && (jsx("caption", { className: "sr-only", children: caption })), isDeclarativeAPI ? (children) : (jsxs(Fragment, { children: [jsx(TableHeader, { columns: visibleColumnsList, sortConfig: sortConfig, onSortChange: onSortChange, filterConfig: filterConfig, onFilterChange: onFilterChange, freezeHeader: freezeHeader, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, expandable: expandable, expandedRowRender: expandedRowRender, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, totalRowCount: paginatedData.length, theme: theme, data: paginatedData, rowKey: rowKey }), jsx(TableBody, { data: paginatedData, columns: visibleColumnsList, rowKey: rowKey, expandedRowRender: expandedRowRender, expandedRows: expandedRows, onExpandedRowsChange: onExpandedRowsChange, selectable: selectable, selectionType: selectionType, selectedRows: selectedRows, onSelectionChange: onSelectionChange, freezeFirstColumn: freezeFirstColumn, freezeLastColumn: freezeLastColumn, theme: theme })] }))] }) })] }) }));
152
152
  }
153
153
  TableComponent.displayName = "Table";
154
154
  const Table = TableComponent;
@@ -193,6 +193,8 @@ export interface TableProps<T = unknown> {
193
193
  totalItems: number;
194
194
  };
195
195
  children?: ReactNode;
196
+ /** Optional accessible caption for the table, rendered as a visually-hidden <caption> element */
197
+ caption?: string;
196
198
  }
197
199
  export interface TableHeaderProps {
198
200
  children?: ReactNode;
@@ -1,6 +1,6 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { useState, useCallback } from 'react';
3
- import { inferFilterType, getDefaultOperator, isUnaryOperator, getOperatorsForFilterType, isBetweenOperator } from './Table.filterUtils.js';
2
+ import { useRef, useState, useCallback } from 'react';
3
+ import { inferFilterType, getDefaultOperator, isUnaryOperator, isBetweenOperator, getOperatorsForFilterType } from './Table.filterUtils.js';
4
4
  import { Button } from '../../core/Button/Button.js';
5
5
  import { Input } from '../../forms/Input/Input.js';
6
6
  import '../../forms/Input/Input.theme.js';
@@ -57,6 +57,7 @@ function FilterValueInput({ filter, column, filterType, onValueChange, }) {
57
57
  }
58
58
  function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: controlledLogic, onFilterLogicChange, data, className, }) {
59
59
  const filterableColumns = columns.filter((col) => col.filterable);
60
+ const filterIdRef = useRef(0);
60
61
  // Internal filter logic state
61
62
  const [internalLogic, setInternalLogic] = useState("and");
62
63
  const filterLogic = controlledLogic !== null && controlledLogic !== void 0 ? controlledLogic : internalLogic;
@@ -87,7 +88,7 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
87
88
  const resolvedType = inferFilterType(defaultColumn, data);
88
89
  return [
89
90
  {
90
- id: `filter-${Date.now()}`,
91
+ id: `filter-${++filterIdRef.current}`,
91
92
  columnId: defaultColumn.id,
92
93
  operator: getDefaultOperator(resolvedType),
93
94
  value: "",
@@ -107,7 +108,7 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
107
108
  return "text";
108
109
  return inferFilterType(column, data);
109
110
  };
110
- const syncFilters = (filters) => {
111
+ const syncFilters = useCallback((filters) => {
111
112
  const newFilterConfig = filters
112
113
  .filter((f) => {
113
114
  if (!f.columnId)
@@ -131,8 +132,8 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
131
132
  operator: f.operator,
132
133
  }));
133
134
  onFilterChange(newFilterConfig);
134
- };
135
- const handleAddFilter = () => {
135
+ }, [onFilterChange]);
136
+ const handleAddFilter = useCallback(() => {
136
137
  const defaultColumn = filterableColumns[0];
137
138
  if (!defaultColumn)
138
139
  return;
@@ -141,21 +142,22 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
141
142
  setActiveFilters((prev) => [
142
143
  ...prev,
143
144
  {
144
- id: `filter-${Date.now()}`,
145
+ id: `filter-${++filterIdRef.current}`,
145
146
  columnId: defaultColumn.id,
146
147
  operator: defaultOp,
147
148
  value: "",
148
149
  },
149
150
  ]);
150
- };
151
- const handleRemoveFilter = (filterId) => {
151
+ // eslint-disable-next-line react-hooks/exhaustive-deps
152
+ }, [filterableColumns, onFilterChange]);
153
+ const handleRemoveFilter = useCallback((filterId) => {
152
154
  setActiveFilters((prev) => {
153
155
  const updated = prev.filter((f) => f.id !== filterId);
154
156
  syncFilters(updated);
155
157
  return updated;
156
158
  });
157
- };
158
- const handleFilterColumnChange = (filterId, columnId) => {
159
+ }, [syncFilters]);
160
+ const handleFilterColumnChange = useCallback((filterId, columnId) => {
159
161
  setActiveFilters((prev) => {
160
162
  const updated = prev.map((f) => {
161
163
  if (f.id !== filterId)
@@ -168,8 +170,9 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
168
170
  syncFilters(updated);
169
171
  return updated;
170
172
  });
171
- };
172
- const handleFilterOperatorChange = (filterId, operator) => {
173
+ // eslint-disable-next-line react-hooks/exhaustive-deps
174
+ }, [filterableColumns, data, syncFilters]);
175
+ const handleFilterOperatorChange = useCallback((filterId, operator) => {
173
176
  setActiveFilters((prev) => {
174
177
  const updated = prev.map((f) => {
175
178
  if (f.id !== filterId)
@@ -188,18 +191,18 @@ function TableFilter({ columns, filterConfig, onFilterChange, filterLogic: contr
188
191
  syncFilters(updated);
189
192
  return updated;
190
193
  });
191
- };
192
- const handleFilterValueChange = (filterId, value) => {
194
+ }, [syncFilters]);
195
+ const handleFilterValueChange = useCallback((filterId, value) => {
193
196
  setActiveFilters((prev) => {
194
197
  const updated = prev.map((f) => f.id === filterId ? { ...f, value } : f);
195
198
  syncFilters(updated);
196
199
  return updated;
197
200
  });
198
- };
199
- const handleClearAll = () => {
201
+ }, [syncFilters]);
202
+ const handleClearAll = useCallback(() => {
200
203
  setActiveFilters([]);
201
204
  onFilterChange([]);
202
- };
205
+ }, [onFilterChange]);
203
206
  const appliedFilterCount = activeFilters.filter((f) => {
204
207
  if (!f.columnId)
205
208
  return false;
@@ -204,13 +204,9 @@ const CustomCursorFollow = React.forwardRef(({ children, sideOffset = 15, align
204
204
  const { cursorPos, isActive, cursorRef } = useCustomCursor();
205
205
  const cursorFollowRef = React.useRef(null);
206
206
  const shouldReduceMotion = useReducedMotion();
207
- // Select random color for user variant if not provided (stable across renders)
208
- const resolvedColor = React.useMemo(() => {
209
- if (color)
210
- return color;
211
- const randomIndex = Math.floor(Math.random() * userVariantColors.length);
212
- return userVariantColors[randomIndex];
213
- }, [color]);
207
+ // Select random color for user variant if not provided (stable across renders via ref)
208
+ const randomColorRef = React.useRef(userVariantColors[Math.floor(Math.random() * userVariantColors.length)]);
209
+ const resolvedColor = color !== null && color !== void 0 ? color : randomColorRef.current;
214
210
  React.useImperativeHandle(ref, () => cursorFollowRef.current);
215
211
  const x = useMotionValue(0);
216
212
  const y = useMotionValue(0);
@@ -1,11 +1,12 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import React__default, { useRef, useCallback, useEffect, useMemo } from 'react';
2
+ import React__default, { useRef, useCallback, useEffect } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { useReducedMotion, AnimatePresence, motion } from 'motion/react';
5
5
  import { cn } from '../../../utils/cn.js';
6
6
  import { useOverlay, lockScroll, unlockScroll } from '../../../hooks/useOverlay.js';
7
7
  import { useFocusTrap } from '../../../hooks/useFocusTrap.js';
8
8
  import { useIsClient } from '../../../hooks/useIsClient.js';
9
+ import { useDarkMode } from '../../../hooks/useDarkMode.js';
9
10
  import { overlayTheme, overlayPositionVariants } from './Overlay.theme.js';
10
11
 
11
12
  /**
@@ -138,14 +139,8 @@ const Overlay = React__default.forwardRef(({ isOpen, onClose, closeOnOverlayClic
138
139
  };
139
140
  }
140
141
  }
141
- // Detect dark mode from document (memoized to avoid DOM traversal on every animation frame)
142
- const isDarkMode = useMemo(() => {
143
- if (!isClient)
144
- return false;
145
- return (document.documentElement.classList.contains("dark") ||
146
- document.body.classList.contains("dark") ||
147
- document.querySelector(".dark") !== null);
148
- }, [isClient]);
142
+ // Detect dark mode reactively via a shared MutationObserver-backed hook (SSR-safe)
143
+ const isDarkMode = useDarkMode();
149
144
  // Determine portal target
150
145
  const target = portalTarget !== undefined
151
146
  ? portalTarget
@@ -1,5 +1,5 @@
1
1
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
- import { useState, useEffect } from 'react';
2
+ import { useState, useRef, useEffect } from 'react';
3
3
  import { motion } from 'motion/react';
4
4
  import { cn } from '../../../utils/cn.js';
5
5
  import { buildCurtainVariants, buildLogoParallaxVariants, buildSplitVariants } from './PageTransition.animations.js';
@@ -7,7 +7,14 @@ import { buildCurtainVariants, buildLogoParallaxVariants, buildSplitVariants } f
7
7
  const PageTransition = ({ children, logo, type = "slide", direction = "up", duration = 0.8, delay = 0.8, color = "bg-[var(--color-primary)]", className, parallax = true, onStart, onComplete, }) => {
8
8
  const [phase, setPhase] = useState("covered");
9
9
  const [completedPanels, setCompletedPanels] = useState(0);
10
+ // Stable refs for callbacks to avoid stale closures without re-running the effect
11
+ const onStartRef = useRef(onStart);
12
+ const onCompleteRef = useRef(onComplete);
13
+ useEffect(() => { onStartRef.current = onStart; }, [onStart]);
14
+ useEffect(() => { onCompleteRef.current = onComplete; }, [onComplete]);
10
15
  useEffect(() => {
16
+ if (typeof window === "undefined" || typeof document === "undefined")
17
+ return;
11
18
  let cancelled = false;
12
19
  const loadPromise = new Promise((resolve) => {
13
20
  if (document.readyState === "complete") {
@@ -24,8 +31,9 @@ const PageTransition = ({ children, logo, type = "slide", direction = "up", dura
24
31
  if (!cancelled) {
25
32
  setPhase("exit");
26
33
  setTimeout(() => {
34
+ var _a;
27
35
  if (!cancelled)
28
- onStart === null || onStart === void 0 ? void 0 : onStart();
36
+ (_a = onStartRef.current) === null || _a === void 0 ? void 0 : _a.call(onStartRef);
29
37
  }, 250);
30
38
  }
31
39
  });
@@ -34,10 +42,11 @@ const PageTransition = ({ children, logo, type = "slide", direction = "up", dura
34
42
  };
35
43
  }, [delay]);
36
44
  useEffect(() => {
45
+ var _a;
37
46
  if (type === "split" && completedPanels === 2) {
38
- onComplete === null || onComplete === void 0 ? void 0 : onComplete();
47
+ (_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef);
39
48
  }
40
- }, [completedPanels, type, onComplete]);
49
+ }, [completedPanels, type]);
41
50
  if (type === "split") {
42
51
  const axis = direction === "up" || direction === "down" ? "h" : "v";
43
52
  const splitVars = buildSplitVariants(axis, duration);
@@ -58,13 +67,15 @@ const PageTransition = ({ children, logo, type = "slide", direction = "up", dura
58
67
  if (type === "slide" && parallax) {
59
68
  const logoVariants = buildLogoParallaxVariants(direction, duration);
60
69
  return (jsxs(Fragment, { children: [children, jsx(motion.div, { className: cn("fixed inset-0 z-[9999] flex items-center justify-center", color, className), style: { willChange: "transform" }, variants: curtainVariants, initial: "covered", animate: phase, onAnimationComplete: (def) => {
70
+ var _a;
61
71
  if (def === "exit")
62
- onComplete === null || onComplete === void 0 ? void 0 : onComplete();
72
+ (_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef);
63
73
  }, children: logo && (jsx(motion.div, { className: "relative z-10 select-none", variants: logoVariants, initial: "covered", animate: phase, children: logo })) })] }));
64
74
  }
65
75
  return (jsxs(Fragment, { children: [children, jsx(motion.div, { className: cn("fixed inset-0 z-[9999] flex items-center justify-center", color, className), style: { willChange: "transform" }, variants: curtainVariants, initial: "covered", animate: phase, onAnimationComplete: (def) => {
76
+ var _a;
66
77
  if (def === "exit")
67
- onComplete === null || onComplete === void 0 ? void 0 : onComplete();
78
+ (_a = onCompleteRef.current) === null || _a === void 0 ? void 0 : _a.call(onCompleteRef);
68
79
  }, children: logo && jsx("div", { className: "relative z-10 select-none", children: logo }) })] }));
69
80
  };
70
81
 
@@ -1,5 +1,6 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { useRef, useEffect } from 'react';
3
+ import { useReducedMotion } from 'motion/react';
3
4
  import * as THREE from 'three';
4
5
  import { cn } from '../../../utils/cn.js';
5
6
  import { variationMap } from './SpotlightBorder.types.js';
@@ -21,12 +22,10 @@ import { fragmentShader, vertexShader } from './SpotlightBorder.shaders.js';
21
22
  * ```
22
23
  */
23
24
  const SpotlightBorder = ({ children, variation = "rounded-rect", rounded, borderWidth = 2, color = "#ffffff", spotlightSize = 0.4, spotlightEdge = 0.5, glowExtent = 20, glowIntensity = 0.6, tiltIntensity = 5, shouldAnimate = true, className, style, ...props }) => {
24
- var _a;
25
25
  const outerRef = useRef(null);
26
26
  const containerRef = useRef(null);
27
- // Check for reduced motion preference
28
- const prefersReducedMotion = typeof window !== "undefined" &&
29
- ((_a = window.matchMedia) === null || _a === void 0 ? void 0 : _a.call(window, "(prefers-reduced-motion: reduce)").matches);
27
+ // useReducedMotion is SSR-safe (returns null on server, actual value after hydration)
28
+ const prefersReducedMotion = useReducedMotion();
30
29
  const isAnimating = shouldAnimate && !prefersReducedMotion;
31
30
  // Refs to store THREE.js objects across effects
32
31
  const rendererRef = useRef(null);
@@ -22,6 +22,7 @@ const CheckboxComponent = React__default.forwardRef(({ id: idProp, name: namePro
22
22
  const size = (_c = groupContext === null || groupContext === void 0 ? void 0 : groupContext.size) !== null && _c !== void 0 ? _c : sizeProp;
23
23
  const state = stateProp === "disabled" ? "disabled" : ((_d = groupContext === null || groupContext === void 0 ? void 0 : groupContext.state) !== null && _d !== void 0 ? _d : stateProp);
24
24
  const isDisabled = state === "disabled";
25
+ const isInvalid = state === "invalid";
25
26
  // Determine checked state
26
27
  const isInGroup = groupContext !== undefined;
27
28
  const [internalChecked, setInternalChecked] = useState(false);
@@ -42,6 +43,15 @@ const CheckboxComponent = React__default.forwardRef(({ id: idProp, name: namePro
42
43
  inputRef.current.indeterminate = indeterminate;
43
44
  }
44
45
  }, [indeterminate]);
46
+ // Dev-only warning when no accessible name is provided
47
+ useEffect(() => {
48
+ if (process.env.NODE_ENV !== 'production') {
49
+ if (!label && !restProps['aria-label'] && !restProps['aria-labelledby']) {
50
+ console.warn('[Checkbox]: No accessible name provided. Add a `label` prop, `aria-label`, or `aria-labelledby`.');
51
+ }
52
+ }
53
+ // eslint-disable-next-line react-hooks/exhaustive-deps
54
+ }, []);
45
55
  const handleChange = (e) => {
46
56
  if (isDisabled)
47
57
  return;
@@ -59,7 +69,7 @@ const CheckboxComponent = React__default.forwardRef(({ id: idProp, name: namePro
59
69
  ref(node);
60
70
  else if (ref)
61
71
  ref.current = node;
62
- }, type: "checkbox", id: id, name: name, value: value, checked: isChecked, onChange: handleChange, disabled: isDisabled, "aria-describedby": description ? `${id}-description` : undefined, "data-state": isDisabled ? "disabled" : "default", className: cn(checkboxTheme.inputStyle, checkboxTheme.sizes[size], indeterminate && "bg-neutral-200") }), jsxs(AnimatePresence, { mode: "wait", children: [indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-neutral-600", size === "sm" ? "size-3" : "size-4"), children: jsx(motion.path, { d: "M3.5 8 L12.5 8", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", variants: shouldReduceMotion ? reducedMotionVariants : indeterminateVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "indeterminate")), isChecked && !indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-white", size === "sm" ? "size-3" : "size-4"), children: jsx(motion.path, { d: "M3.5 8.5 L6.5 11.5 L12.5 4.5", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", variants: shouldReduceMotion ? reducedMotionVariants : checkmarkVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "checkmark"))] })] }), (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, formLabelTheme.stateStyles[state], "mb-0"), children: label })) : (label)), description && (typeof description === 'string' ? (jsx("div", { id: `${id}-description`, className: checkboxTheme.descriptionStyle, children: description })) : (description))] }))] }));
72
+ }, type: "checkbox", id: id, name: name, value: value, checked: isChecked, onChange: handleChange, disabled: isDisabled, "aria-describedby": description ? `${id}-description` : undefined, "aria-invalid": isInvalid || undefined, "data-state": isDisabled ? "disabled" : isInvalid ? "invalid" : "default", className: cn(checkboxTheme.inputStyle, checkboxTheme.sizes[size], indeterminate && "bg-neutral-200") }), jsxs(AnimatePresence, { mode: "wait", children: [indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-neutral-600", size === "sm" ? "size-3" : size === "lg" ? "size-5" : "size-4"), children: jsx(motion.path, { d: "M3.5 8 L12.5 8", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", variants: shouldReduceMotion ? reducedMotionVariants : indeterminateVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "indeterminate")), isChecked && !indeterminate && (jsx(motion.svg, { viewBox: "0 0 16 16", fill: "none", className: cn("pointer-events-none col-start-1 row-start-1 self-center justify-self-center text-white", size === "sm" ? "size-3" : size === "lg" ? "size-5" : "size-4"), children: jsx(motion.path, { d: "M3.5 8.5 L6.5 11.5 L12.5 4.5", stroke: "currentColor", strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", variants: shouldReduceMotion ? reducedMotionVariants : checkmarkVariants, initial: "unchecked", animate: "checked", exit: "unchecked" }) }, "checkmark"))] })] }), (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, formLabelTheme.stateStyles[state], "mb-0"), children: label })) : (label)), description && (typeof description === 'string' ? (jsx("div", { id: `${id}-description`, className: checkboxTheme.descriptionStyle, children: description })) : (description))] }))] }));
63
73
  });
64
74
  CheckboxComponent.displayName = "Checkbox";
65
75
  const Checkbox = Object.assign(CheckboxComponent, {
@@ -24,12 +24,15 @@ const checkboxTheme = {
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
26
  "dark:disabled:bg-[var(--color-neutral-800)] 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)]",
27
+ "dark:data-[state=disabled]:bg-[var(--color-neutral-700)] dark:data-[state=disabled]:border-[var(--color-neutral-600)] " +
28
+ // Invalid state
29
+ "data-[state=invalid]:border-[var(--color-danger)] data-[state=invalid]:focus-visible:outline-[var(--color-danger)]",
28
30
  // Description text - light and dark
29
31
  descriptionStyle: "text-sm mt-0.5 " + "text-[var(--color-text-secondary)]/80 " + " ",
30
32
  sizes: {
31
33
  sm: "size-5 mt-0.25",
32
34
  md: "size-6",
35
+ lg: "size-7",
33
36
  },
34
37
  // Group styles
35
38
  groupStyle: "flex flex-col gap-1.5",
@@ -2,11 +2,11 @@ import React from 'react';
2
2
  /**
3
3
  * State variants for the checkbox component
4
4
  */
5
- export type CheckboxState = 'default' | 'disabled';
5
+ export type CheckboxState = 'default' | 'disabled' | 'invalid';
6
6
  /**
7
7
  * Size variants for the checkbox component
8
8
  */
9
- export type CheckboxSize = 'sm' | 'md';
9
+ export type CheckboxSize = 'sm' | 'md' | 'lg';
10
10
  /**
11
11
  * Orientation for Checkbox.Group layout
12
12
  */
@@ -14,11 +14,9 @@ export type CheckboxGroupOrientation = 'vertical' | 'horizontal';
14
14
  /**
15
15
  * Checkbox component props
16
16
  */
17
- export interface CheckboxProps extends Omit<React.HTMLAttributes<HTMLInputElement>, 'onChange' | 'size'> {
17
+ export interface CheckboxProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'size'> {
18
18
  id?: string;
19
- name?: string;
20
19
  value: string;
21
- checked?: boolean;
22
20
  indeterminate?: boolean;
23
21
  onChange?: (checked: boolean, value: string) => void;
24
22
  label?: React.ReactNode;
@@ -40,7 +38,11 @@ export interface CheckboxGroupProps extends Omit<React.HTMLAttributes<HTMLFieldS
40
38
  orientation?: CheckboxGroupOrientation;
41
39
  label?: React.ReactNode;
42
40
  description?: React.ReactNode;
41
+ /**
42
+ * @deprecated Use `state='invalid'` with `errorMessage` instead.
43
+ */
43
44
  error?: React.ReactNode;
45
+ errorMessage?: React.ReactNode;
44
46
  required?: boolean;
45
47
  className?: string;
46
48
  }
@@ -5,7 +5,12 @@ import { checkboxTheme } from './Checkbox.theme.js';
5
5
  import { formLabelTheme } from '../FormLabel/FormLabel.theme.js';
6
6
  import { CheckboxGroupContext } from './CheckboxContext.js';
7
7
 
8
- const CheckboxGroup = React__default.forwardRef(({ value: controlledValue, defaultValue, onChange, name, size = "md", state = "default", orientation = "vertical", label, description, error, required, className, children, ...props }, ref) => {
8
+ const CheckboxGroup = React__default.forwardRef(({ value: controlledValue, defaultValue, onChange, name, size = "md", state: stateProp = "default", orientation = "vertical", label, description, error, errorMessage, required, className, children, ...props }, ref) => {
9
+ // Prefer errorMessage; fall back to deprecated error prop
10
+ const resolvedError = errorMessage !== null && errorMessage !== void 0 ? errorMessage : error;
11
+ // If error (deprecated) is provided and state not explicitly set to something else,
12
+ // implicitly treat the group as invalid
13
+ const state = resolvedError && stateProp === "default" ? "invalid" : stateProp;
9
14
  const autoId = useId();
10
15
  const groupName = name !== null && name !== void 0 ? name : `checkbox-group-${autoId}`;
11
16
  const labelId = `${autoId}-label`;
@@ -31,9 +36,9 @@ const CheckboxGroup = React__default.forwardRef(({ value: controlledValue, defau
31
36
  size,
32
37
  state,
33
38
  }), [groupName, currentValue, size, state]);
34
- const labelState = error ? "invalid" : state === "disabled" ? "disabled" : "default";
35
- return (jsx(CheckboxGroupContext.Provider, { value: contextValue, children: jsxs("fieldset", { ref: ref, role: "group", "aria-labelledby": label ? labelId : undefined, "aria-describedby": description ? descriptionId : undefined, "aria-required": required || undefined, "aria-invalid": error ? true : undefined, "aria-errormessage": error ? errorId : undefined, className: cn("border-0 p-0 m-0", checkboxTheme.groupStyle, className), ...props, children: [label && (typeof label === 'string' ? (jsxs("legend", { id: labelId, className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[labelState]), children: [label, required && (jsx("span", { className: formLabelTheme.requiredIndicatorStyle, children: "*" }))] })) : (label)), description && (typeof description === 'string' ? (jsx("p", { id: descriptionId, className: checkboxTheme.groupDescriptionStyle, children: description })) : (description)), jsx("div", { className: cn(checkboxTheme.groupItemsStyle, orientation === "horizontal" &&
36
- checkboxTheme.groupHorizontalStyle), children: children }), error && (typeof error === 'string' ? (jsx("p", { id: errorId, role: "alert", className: checkboxTheme.groupErrorStyle, children: error })) : (error))] }) }));
39
+ const labelState = state === "invalid" ? "invalid" : state === "disabled" ? "disabled" : "default";
40
+ return (jsx(CheckboxGroupContext.Provider, { value: contextValue, children: jsxs("fieldset", { ref: ref, role: "group", "aria-labelledby": label ? labelId : undefined, "aria-describedby": description ? descriptionId : undefined, "aria-required": required || undefined, "aria-invalid": resolvedError ? true : undefined, "aria-errormessage": resolvedError ? errorId : undefined, className: cn("border-0 p-0 m-0", checkboxTheme.groupStyle, className), ...props, children: [label && (typeof label === 'string' ? (jsxs("legend", { id: labelId, className: cn(formLabelTheme.baseStyle, formLabelTheme.stateStyles[labelState]), children: [label, required && (jsx("span", { className: formLabelTheme.requiredIndicatorStyle, children: "*" }))] })) : (label)), description && (typeof description === 'string' ? (jsx("p", { id: descriptionId, className: checkboxTheme.groupDescriptionStyle, children: description })) : (description)), jsx("div", { className: cn(checkboxTheme.groupItemsStyle, orientation === "horizontal" &&
41
+ checkboxTheme.groupHorizontalStyle), children: children }), resolvedError && (typeof resolvedError === 'string' ? (jsx("p", { id: errorId, role: "alert", className: checkboxTheme.groupErrorStyle, children: resolvedError })) : (resolvedError))] }) }));
37
42
  });
38
43
  CheckboxGroup.displayName = "Checkbox.Group";
39
44
 
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import React__default, { useMemo, useState, useCallback, useRef, useEffect } from 'react';
2
+ import React__default, { useRef, useMemo, useState, useCallback, useEffect } from 'react';
3
3
  import { cn } from '../../../utils/cn.js';
4
4
  import { FormLabel } from '../FormLabel/FormLabel.js';
5
5
  import { colorPickerTheme } from './ColorPicker.theme.js';
@@ -31,7 +31,10 @@ const DEFAULT_GRADIENT = {
31
31
  * ColorPicker Root Component
32
32
  * Follows Flikkui compound component pattern (similar to Accordion, Tabs, Popover)
33
33
  */
34
- const ColorPickerRoot = React__default.forwardRef(({ value, defaultValue = '#000000', onChange, format: initialFormat = 'hex', showAlpha = true, showEyeDropper = true, presetColors, savedColors: savedColorsProp, onSavedColorsChange, defaultMode = 'solid', mode: modeProp, onModeChange, gradientValue: gradientProp, onGradientChange, size = 'md', state = 'default', disabled = false, label, helperText, required = false, theme, className, children, ...props }, ref) => {
34
+ const ColorPickerRoot = React__default.forwardRef(({ value, defaultValue = '#000000', onChange, format: initialFormat = 'hex', showAlpha = true, showEyeDropper = true, presetColors, savedColors: savedColorsProp, onSavedColorsChange, defaultMode = 'solid', mode: modeProp, onModeChange, gradientValue: gradientProp, onGradientChange, size = 'md', state = 'default', disabled = false, label, helperText, required = false, theme, className, id, children, ...props }, ref) => {
35
+ const generatedId = React__default.useId();
36
+ const componentId = id || generatedId;
37
+ const stopIdRef = useRef(0);
35
38
  // Parse initial color value
36
39
  const initialColor = useMemo(() => {
37
40
  const parsed = parseColor(value || defaultValue);
@@ -71,7 +74,7 @@ const ColorPickerRoot = React__default.forwardRef(({ value, defaultValue = '#000
71
74
  }, [gradient, setGradient]);
72
75
  const addGradientStop = useCallback((position) => {
73
76
  const newStop = {
74
- id: `stop-${Date.now()}`,
77
+ id: `stop-${++stopIdRef.current}`,
75
78
  color: formatHex(color.h, color.s, color.l),
76
79
  position: Math.round(position),
77
80
  };
@@ -252,7 +255,7 @@ const ColorPickerRoot = React__default.forwardRef(({ value, defaultValue = '#000
252
255
  addSavedColor,
253
256
  removeSavedColor,
254
257
  ]);
255
- return (jsx(ColorPickerContext.Provider, { value: contextValue, children: jsxs("div", { ref: ref, className: cn(mergedTheme.baseStyle, className), ...props, children: [label && (jsx(FormLabel, { htmlFor: "color-picker", required: required, state: state, children: label })), children, helperText && (jsx("div", { className: cn('mt-1.5 text-sm text-[var(--color-text-secondary)]', state === 'invalid' && 'text-[var(--color-danger)]'), children: helperText }))] }) }));
258
+ return (jsx(ColorPickerContext.Provider, { value: contextValue, children: jsxs("div", { ref: ref, className: cn(mergedTheme.baseStyle, className), ...props, children: [label && (typeof label === 'string' ? (jsx(FormLabel, { htmlFor: componentId, required: required, state: state, children: label })) : (label)), children, helperText && (typeof helperText === 'string' ? (jsx("div", { id: `${componentId}-helper`, className: cn('mt-1.5 text-sm text-[var(--color-text-secondary)]', state === 'invalid' && 'text-[var(--color-danger)]'), children: helperText })) : (helperText))] }) }));
256
259
  });
257
260
  ColorPickerRoot.displayName = 'ColorPicker';
258
261
  /**
@@ -83,11 +83,11 @@ export interface ColorPickerProps extends Omit<React.HTMLAttributes<HTMLDivEleme
83
83
  /**
84
84
  * Label for the color picker
85
85
  */
86
- label?: string;
86
+ label?: React.ReactNode;
87
87
  /**
88
88
  * Helper text displayed below the picker
89
89
  */
90
- helperText?: string;
90
+ helperText?: React.ReactNode;
91
91
  /**
92
92
  * Whether the picker is required
93
93
  * @default false
@@ -66,9 +66,11 @@ const ColorPickerBody = ({ children, className, portal = true, animation = true,
66
66
  damping: 30,
67
67
  }, "data-state": isOpen ? 'open' : 'closed', ...domProps, children: children })) }));
68
68
  // Use portal if specified (same as Dropdown)
69
- return portal && isClient
70
- ? createPortal(content, document.body)
71
- : content;
69
+ if (portal) {
70
+ const target = isClient ? document.body : null;
71
+ return target ? createPortal(content, target) : null;
72
+ }
73
+ return content;
72
74
  };
73
75
  ColorPickerBody.displayName = 'ColorPicker.Body';
74
76
 
@@ -1,19 +1,19 @@
1
1
  import { jsx } from 'react/jsx-runtime';
2
2
  import { EyeDropperIcon } from '@heroicons/react/20/solid';
3
- import { useContext } from 'react';
3
+ import { useContext, useMemo } from 'react';
4
4
  import { cn } from '../../../utils/cn.js';
5
5
  import { Button } from '../../core/Button/Button.js';
6
6
  import { ColorPickerContext } from './ColorPickerContext.js';
7
7
  import { parseColor } from './colorUtils.js';
8
8
 
9
- // Check if browser supports EyeDropper API
10
- const supportsEyeDropper = typeof window !== 'undefined' && 'EyeDropper' in window;
11
9
  /**
12
10
  * ColorPickerEyeDropper - Eyedropper tool to pick colors from screen
13
11
  * Uses browser EyeDropper API (if available)
14
12
  */
15
13
  const ColorPickerEyeDropper = ({ className, ...props }) => {
16
14
  const context = useContext(ColorPickerContext);
15
+ // Check if browser supports EyeDropper API (inside component to avoid SSR module-caching issues)
16
+ const supportsEyeDropper = useMemo(() => typeof window !== 'undefined' && 'EyeDropper' in window, []);
17
17
  if (!context) {
18
18
  throw new Error('ColorPickerEyeDropper must be used within a ColorPicker');
19
19
  }
@@ -113,14 +113,14 @@ const ColorPickerGradient = ({ className, ...props }) => {
113
113
  setDragStopId(null);
114
114
  }, []);
115
115
  useEffect(() => {
116
- if (dragStopId) {
117
- window.addEventListener('pointermove', handlePointerMove);
118
- window.addEventListener('pointerup', handlePointerUp);
119
- return () => {
120
- window.removeEventListener('pointermove', handlePointerMove);
121
- window.removeEventListener('pointerup', handlePointerUp);
122
- };
123
- }
116
+ if (!dragStopId)
117
+ return; // no listeners to add; no cleanup needed
118
+ window.addEventListener('pointermove', handlePointerMove);
119
+ window.addEventListener('pointerup', handlePointerUp);
120
+ return () => {
121
+ window.removeEventListener('pointermove', handlePointerMove);
122
+ window.removeEventListener('pointerup', handlePointerUp);
123
+ };
124
124
  }, [dragStopId, handlePointerMove, handlePointerUp]);
125
125
  // --- Selected stop color input editing ---
126
126
  const [colorInput, setColorInput] = useState((_c = selectedStop === null || selectedStop === void 0 ? void 0 : selectedStop.color) !== null && _c !== void 0 ? _c : '');
@@ -20,7 +20,7 @@ const ColorPickerInput = ({ className, ...props }) => {
20
20
  if (!isInvalid) {
21
21
  setInputValue(getFormattedValue());
22
22
  }
23
- }, [color, format]); // eslint-disable-line react-hooks/exhaustive-deps
23
+ }, [color, format, getFormattedValue, isInvalid]);
24
24
  const handleCopy = async () => {
25
25
  try {
26
26
  await navigator.clipboard.writeText(getFormattedValue());
@@ -3,7 +3,7 @@
3
3
  * Compound component following Flikkui patterns
4
4
  */
5
5
  export { ColorPicker } from './ColorPicker';
6
- export { useColorPicker, ColorPickerContext } from './ColorPickerContext';
6
+ export { useColorPicker } from './ColorPickerContext';
7
7
  export { ColorPickerTrigger } from './ColorPickerTrigger';
8
8
  export { ColorPickerBody } from './ColorPickerBody';
9
9
  export { ColorPicker2DCanvas } from './ColorPicker2DCanvas';