torch-glare 2.4.0 → 2.4.1

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 (264) hide show
  1. package/README.md +8 -0
  2. package/apps/lib/components/ActionButton.tsx +7 -12
  3. package/apps/lib/components/ActionsGroup.tsx +4 -15
  4. package/apps/lib/components/AlertDialog.tsx +173 -191
  5. package/apps/lib/components/Avatar.tsx +13 -16
  6. package/apps/lib/components/Badge.tsx +1 -3
  7. package/apps/lib/components/BadgeField.tsx +76 -71
  8. package/apps/lib/components/Breadcrumb.tsx +49 -85
  9. package/apps/lib/components/Button.tsx +20 -19
  10. package/apps/lib/components/ButtonGroup.tsx +31 -41
  11. package/apps/lib/components/Calendar.tsx +51 -29
  12. package/apps/lib/components/Card.tsx +2 -13
  13. package/apps/lib/components/ChartBlockTool.ts +17 -52
  14. package/apps/lib/components/Checkbox.tsx +9 -16
  15. package/apps/lib/components/ContextMenu.tsx +97 -96
  16. package/apps/lib/components/CountBadge.tsx +4 -6
  17. package/apps/lib/components/DataTable.tsx +196 -216
  18. package/apps/lib/components/DataViews/DataViewRadio.tsx +1 -6
  19. package/apps/lib/components/DataViews/DataViewsConfigPanel.tsx +13 -47
  20. package/apps/lib/components/DataViews/DataViewsHeader.tsx +1 -3
  21. package/apps/lib/components/DataViews/DataViewsLayout.tsx +5 -12
  22. package/apps/lib/components/DataViews/FilterPanel.tsx +155 -149
  23. package/apps/lib/components/DataViews/HeaderSearch.tsx +1 -5
  24. package/apps/lib/components/DataViews/InboxView.tsx +22 -68
  25. package/apps/lib/components/DataViews/InboxViewCard.tsx +68 -77
  26. package/apps/lib/components/DataViews/KanbanView.tsx +8 -25
  27. package/apps/lib/components/DataViews/PanelControls.tsx +3 -13
  28. package/apps/lib/components/DataViews/SettingsPanel.tsx +83 -89
  29. package/apps/lib/components/DataViews/TableView.tsx +8 -28
  30. package/apps/lib/components/DataViews/TreeView.tsx +12 -40
  31. package/apps/lib/components/DataViews/badgeAdapter.ts +32 -28
  32. package/apps/lib/components/DataViews/fieldRenderers.tsx +106 -141
  33. package/apps/lib/components/DataViews/filters/DatePickerRangeFilter.tsx +30 -23
  34. package/apps/lib/components/DataViews/filters/DateRangePopover.tsx +39 -32
  35. package/apps/lib/components/DataViews/filters/PresetChips.tsx +15 -15
  36. package/apps/lib/components/DataViews/filters/RangeSliderWithInputs.tsx +69 -58
  37. package/apps/lib/components/DataViews/index.ts +26 -26
  38. package/apps/lib/components/DataViews/tree/TreeDrawer.tsx +12 -16
  39. package/apps/lib/components/DataViews/tree/TreeSidebar.tsx +28 -31
  40. package/apps/lib/components/DataViews/types.ts +104 -115
  41. package/apps/lib/components/DatePicker.tsx +198 -196
  42. package/apps/lib/components/Dialog.tsx +92 -105
  43. package/apps/lib/components/Divider.tsx +21 -24
  44. package/apps/lib/components/Drawer.tsx +79 -139
  45. package/apps/lib/components/DropdownMenu.tsx +131 -171
  46. package/apps/lib/components/FieldHint.tsx +4 -11
  47. package/apps/lib/components/Form.tsx +146 -155
  48. package/apps/lib/components/FormStepper.tsx +9 -39
  49. package/apps/lib/components/HeaderBar.tsx +1 -9
  50. package/apps/lib/components/ImageAttachment.tsx +111 -73
  51. package/apps/lib/components/InnerLabelField.tsx +20 -16
  52. package/apps/lib/components/Input.tsx +171 -172
  53. package/apps/lib/components/InputField.tsx +44 -45
  54. package/apps/lib/components/InputOTP.tsx +68 -69
  55. package/apps/lib/components/Label.tsx +31 -35
  56. package/apps/lib/components/LabelField.tsx +4 -5
  57. package/apps/lib/components/LabeledCheckBox.tsx +8 -12
  58. package/apps/lib/components/LabeledRadio.tsx +7 -5
  59. package/apps/lib/components/LinkButton.tsx +2 -9
  60. package/apps/lib/components/LoginButton.tsx +4 -9
  61. package/apps/lib/components/PasswordLevel.tsx +5 -5
  62. package/apps/lib/components/Popover.tsx +15 -33
  63. package/apps/lib/components/ProfileMenu.tsx +67 -72
  64. package/apps/lib/components/Radio.tsx +1 -5
  65. package/apps/lib/components/RadioCard.tsx +9 -28
  66. package/apps/lib/components/ScrollArea.tsx +45 -43
  67. package/apps/lib/components/SearchField.tsx +49 -39
  68. package/apps/lib/components/SearchableSelect.tsx +8 -14
  69. package/apps/lib/components/SearchableTable.tsx +108 -118
  70. package/apps/lib/components/SearchableTree.tsx +469 -0
  71. package/apps/lib/components/SearchableTreeDialog.tsx +493 -0
  72. package/apps/lib/components/SectionBlock.tsx +4 -16
  73. package/apps/lib/components/Select.tsx +53 -80
  74. package/apps/lib/components/SimpleSelect.tsx +196 -187
  75. package/apps/lib/components/Skeleton.tsx +9 -12
  76. package/apps/lib/components/SlideDatePicker.tsx +215 -166
  77. package/apps/lib/components/SpinLoading.tsx +33 -49
  78. package/apps/lib/components/Stepper.tsx +13 -56
  79. package/apps/lib/components/Switch.tsx +8 -10
  80. package/apps/lib/components/TabFormItem.tsx +3 -12
  81. package/apps/lib/components/TabSwitch.tsx +7 -34
  82. package/apps/lib/components/Table.tsx +60 -75
  83. package/apps/lib/components/TableDnDWrapper.ts +15 -48
  84. package/apps/lib/components/TextEditor.tsx +93 -104
  85. package/apps/lib/components/Textarea.tsx +8 -12
  86. package/apps/lib/components/Timeline.tsx +21 -41
  87. package/apps/lib/components/Toast.tsx +83 -83
  88. package/apps/lib/components/Toggle.tsx +112 -114
  89. package/apps/lib/components/ToggleButton.tsx +1 -1
  90. package/apps/lib/components/Tooltip.tsx +24 -19
  91. package/apps/lib/components/TransparentLabel.tsx +61 -62
  92. package/apps/lib/components/TreeDropDown.tsx +86 -52
  93. package/apps/lib/components/TreeFolder/TreeFolder.tsx +15 -48
  94. package/apps/lib/components/TreeFolder/TreeFolderBreadcrumb.tsx +16 -21
  95. package/apps/lib/components/TreeFolder/TreeFolderRow.tsx +9 -20
  96. package/apps/lib/components/TreeFolder/TreeFolderStyles.tsx +3 -3
  97. package/apps/lib/components/TreeFolder/icons.tsx +14 -17
  98. package/apps/lib/components/TreeFolder/index.ts +15 -8
  99. package/apps/lib/components/TreeFolder/treeFolderUtils.ts +47 -52
  100. package/apps/lib/components/TreeFolder/types.ts +32 -32
  101. package/apps/lib/components/TreeFolder/useTreeFolderDnD.ts +117 -123
  102. package/apps/lib/hooks/useActiveTreeItem.ts +52 -55
  103. package/apps/lib/hooks/useClickOutside.ts +21 -20
  104. package/apps/lib/hooks/useDataViewsState.ts +86 -80
  105. package/apps/lib/hooks/useIsMobile.ts +12 -12
  106. package/apps/lib/hooks/useResize.ts +54 -65
  107. package/apps/lib/hooks/useTagSelection.ts +201 -201
  108. package/apps/lib/layouts/CNLayout.tsx +181 -140
  109. package/apps/lib/layouts/DataViewCard.tsx +4 -18
  110. package/apps/lib/layouts/FieldSection.tsx +2 -6
  111. package/apps/lib/layouts/TreeSubLayout.tsx +24 -38
  112. package/apps/lib/providers/ThemeProvider.tsx +8 -23
  113. package/apps/lib/registry.json +1004 -0
  114. package/apps/lib/tsconfig.json +9 -0
  115. package/apps/lib/utils/cn.ts +1 -1
  116. package/apps/lib/utils/dataViews/columnUtils.ts +46 -44
  117. package/apps/lib/utils/dataViews/fieldUtils.ts +70 -71
  118. package/apps/lib/utils/dataViews/nestedDataUtils.tsx +129 -122
  119. package/apps/lib/utils/dataViews/pathUtils.ts +74 -67
  120. package/apps/lib/utils/dataViews/rangeUtils.ts +121 -112
  121. package/apps/lib/utils/dataViews/treeUtils.ts +204 -211
  122. package/apps/lib/utils/dateFormat.ts +64 -54
  123. package/apps/lib/utils/markdownParser.ts +2 -7
  124. package/apps/lib/utils/resize.ts +21 -25
  125. package/apps/lib/utils/types.ts +13 -13
  126. package/dist/bin/index.js +8 -2
  127. package/dist/bin/index.js.map +1 -1
  128. package/dist/src/shared/getAvailableFiles.d.ts.map +1 -1
  129. package/dist/src/shared/getAvailableFiles.js +6 -1
  130. package/dist/src/shared/getAvailableFiles.js.map +1 -1
  131. package/dist/src/shared/loadRegistry.d.ts +7 -0
  132. package/dist/src/shared/loadRegistry.d.ts.map +1 -0
  133. package/dist/src/shared/loadRegistry.js +31 -0
  134. package/dist/src/shared/loadRegistry.js.map +1 -0
  135. package/dist/src/shared/resolveInstallPlan.d.ts +12 -0
  136. package/dist/src/shared/resolveInstallPlan.d.ts.map +1 -0
  137. package/dist/src/shared/resolveInstallPlan.js +39 -0
  138. package/dist/src/shared/resolveInstallPlan.js.map +1 -0
  139. package/dist/src/types/main.d.ts +24 -0
  140. package/dist/src/types/main.d.ts.map +1 -1
  141. package/docs/README.md +10 -2
  142. package/docs/components/action-button.md +30 -24
  143. package/docs/components/actions-group.md +26 -20
  144. package/docs/components/alert-dialog.md +45 -45
  145. package/docs/components/avatar.md +6 -2
  146. package/docs/components/badge-field.md +6 -2
  147. package/docs/components/badge.md +7 -1
  148. package/docs/components/breadcrumb.md +15 -40
  149. package/docs/components/button-group.md +13 -10
  150. package/docs/components/button.md +58 -33
  151. package/docs/components/calendar.md +26 -19
  152. package/docs/components/card.md +19 -16
  153. package/docs/components/chart-block-tool.md +128 -0
  154. package/docs/components/checkbox.md +16 -8
  155. package/docs/components/cn-layout.md +7 -1
  156. package/docs/components/context-menu.md +18 -25
  157. package/docs/components/count-badge.md +7 -1
  158. package/docs/components/data-table.md +45 -33
  159. package/docs/components/data-views-config-panel.md +15 -11
  160. package/docs/components/data-views-layout.md +13 -16
  161. package/docs/components/date-picker.md +30 -23
  162. package/docs/components/dialog.md +43 -42
  163. package/docs/components/divider.md +10 -4
  164. package/docs/components/drawer.md +8 -33
  165. package/docs/components/dropdown-menu.md +25 -34
  166. package/docs/components/field-hint.md +37 -25
  167. package/docs/components/field-section.md +9 -3
  168. package/docs/components/form-stepper.md +6 -0
  169. package/docs/components/form.md +17 -20
  170. package/docs/components/header-bar.md +6 -0
  171. package/docs/components/image-attachment.md +33 -41
  172. package/docs/components/inbox-view.md +16 -5
  173. package/docs/components/inner-label-field.md +34 -24
  174. package/docs/components/input-field.md +18 -10
  175. package/docs/components/input-otp.md +14 -11
  176. package/docs/components/input.md +22 -16
  177. package/docs/components/kanban-view.md +15 -5
  178. package/docs/components/label-field.md +32 -22
  179. package/docs/components/label.md +43 -32
  180. package/docs/components/labeled-check-box.md +16 -8
  181. package/docs/components/labeled-radio.md +18 -11
  182. package/docs/components/link-button.md +7 -1
  183. package/docs/components/login-button.md +8 -2
  184. package/docs/components/password-level.md +37 -24
  185. package/docs/components/popover.md +26 -23
  186. package/docs/components/profile-menu.md +38 -27
  187. package/docs/components/radio-card.md +16 -9
  188. package/docs/components/radio.md +16 -8
  189. package/docs/components/scroll-area.md +6 -2
  190. package/docs/components/search-field.md +14 -6
  191. package/docs/components/searchable-select.md +19 -14
  192. package/docs/components/searchable-table.md +18 -13
  193. package/docs/components/searchable-tree-dialog.md +190 -0
  194. package/docs/components/searchable-tree.md +200 -0
  195. package/docs/components/section-block.md +12 -4
  196. package/docs/components/select.md +23 -18
  197. package/docs/components/simple-select.md +14 -10
  198. package/docs/components/skeleton.md +43 -33
  199. package/docs/components/slide-date-picker.md +37 -29
  200. package/docs/components/spin-loading.md +32 -23
  201. package/docs/components/stepper.md +6 -0
  202. package/docs/components/switch.md +14 -6
  203. package/docs/components/tab-form-item.md +7 -1
  204. package/docs/components/tab-switch.md +13 -6
  205. package/docs/components/table-dnd-wrapper.md +105 -0
  206. package/docs/components/table-view.md +15 -5
  207. package/docs/components/table.md +28 -28
  208. package/docs/components/text-editor.md +19 -13
  209. package/docs/components/textarea.md +15 -7
  210. package/docs/components/timeline.md +6 -0
  211. package/docs/components/toast.md +35 -26
  212. package/docs/components/toggle-button.md +24 -21
  213. package/docs/components/toggle.md +23 -15
  214. package/docs/components/tooltip.md +42 -30
  215. package/docs/components/transparent-label.md +29 -20
  216. package/docs/components/tree-drop-down.md +31 -23
  217. package/docs/components/tree-sub-layout.md +6 -2
  218. package/docs/components/tree-view.md +16 -5
  219. package/docs/explanation/architecture.md +54 -0
  220. package/docs/explanation/design-system.md +52 -0
  221. package/docs/how-to/data-views-from-backend-response.md +6 -4
  222. package/docs/how-to/guides.md +24 -18
  223. package/docs/migration/changelog.md +38 -0
  224. package/docs/reference/cli.md +87 -0
  225. package/docs/reference/components.md +11 -12
  226. package/docs/reference/hooks.md +8 -8
  227. package/docs/reference/providers.md +22 -22
  228. package/docs/reference/tailwind-plugins.md +2 -2
  229. package/docs/reference/theme.md +85 -0
  230. package/docs/reference/types.md +4 -4
  231. package/docs/reference/utilities.md +19 -26
  232. package/docs/tutorials/building-first-form.md +20 -13
  233. package/docs/tutorials/component-composition.md +34 -50
  234. package/docs/tutorials/getting-started.md +2 -2
  235. package/docs/tutorials/theming-basics.md +13 -10
  236. package/package.json +31 -3
  237. package/dist/src/commands/block.d.ts +0 -5
  238. package/dist/src/commands/block.d.ts.map +0 -1
  239. package/dist/src/commands/block.js +0 -255
  240. package/dist/src/commands/block.js.map +0 -1
  241. package/dist/src/shared/configureFonts.d.ts +0 -6
  242. package/dist/src/shared/configureFonts.d.ts.map +0 -1
  243. package/dist/src/shared/configureFonts.js +0 -106
  244. package/dist/src/shared/configureFonts.js.map +0 -1
  245. package/dist/src/shared/configureGlobalCss.d.ts +0 -6
  246. package/dist/src/shared/configureGlobalCss.d.ts.map +0 -1
  247. package/dist/src/shared/configureGlobalCss.js +0 -154
  248. package/dist/src/shared/configureGlobalCss.js.map +0 -1
  249. package/dist/src/shared/configureTailwind.d.ts +0 -7
  250. package/dist/src/shared/configureTailwind.d.ts.map +0 -1
  251. package/dist/src/shared/configureTailwind.js +0 -163
  252. package/dist/src/shared/configureTailwind.js.map +0 -1
  253. package/dist/src/shared/detectFramework.d.ts +0 -23
  254. package/dist/src/shared/detectFramework.d.ts.map +0 -1
  255. package/dist/src/shared/detectFramework.js +0 -119
  256. package/dist/src/shared/detectFramework.js.map +0 -1
  257. package/dist/src/shared/installBaseUtils.d.ts +0 -5
  258. package/dist/src/shared/installBaseUtils.d.ts.map +0 -1
  259. package/dist/src/shared/installBaseUtils.js +0 -79
  260. package/dist/src/shared/installBaseUtils.js.map +0 -1
  261. package/dist/src/shared/resolveAliases.d.ts +0 -24
  262. package/dist/src/shared/resolveAliases.d.ts.map +0 -1
  263. package/dist/src/shared/resolveAliases.js +0 -98
  264. package/dist/src/shared/resolveAliases.js.map +0 -1
@@ -1,214 +1,214 @@
1
1
  import { useState, useEffect } from "react";
2
2
 
3
3
  export interface Tag {
4
- id: string;
5
- name: string;
6
- variant?: string;
7
- value?: string;
8
- isSelected: boolean;
9
- [key: string]: any;
4
+ id: string;
5
+ name: string;
6
+ variant?: string;
7
+ value?: string;
8
+ isSelected: boolean;
9
+ [key: string]: unknown;
10
10
  }
11
11
 
12
12
  export const useTagSelection = ({
13
- Tags,
14
- onTagsChange,
15
- inputRef,
16
- singleSelect = false
13
+ Tags,
14
+ onTagsChange,
15
+ inputRef,
16
+ singleSelect = false,
17
17
  }: {
18
- Tags: Tag[],
19
- onTagsChange?: (selectedTags: Tag[]) => void,
20
- inputRef?: React.RefObject<HTMLInputElement | null>,
21
- singleSelect?: boolean
18
+ Tags: Tag[];
19
+ onTagsChange?: (selectedTags: Tag[]) => void;
20
+ inputRef?: React.RefObject<HTMLInputElement | null>;
21
+ singleSelect?: boolean;
22
22
  }) => {
23
- // Split initial tags into selected and unselected
24
- const initialSelectedTags = Tags.filter(tag => tag.isSelected);
25
- const initialUnselectedTags = Tags.filter(tag => !tag.isSelected);
26
-
27
- // Initialize with available tags (excluding any initially selected ones)
28
- const [tags, setTags] = useState<Tag[]>(initialUnselectedTags);
29
-
30
- // Initialize with any pre-selected tags
31
- const [selectedTagsStack, setSelectedTagsStack] = useState<Tag[]>(
32
- singleSelect && initialSelectedTags.length > 0
33
- ? [initialSelectedTags[0]]
34
- : initialSelectedTags
35
- );
36
- const [searchTags, filterTagsBySearch] = useState('');
37
- const [focusedTagIndex, setFocusedTagIndex] = useState<number | null>(null);
38
- const [focusedPopoverIndex, setFocusedPopoverIndex] = useState<number | null>(null);
39
- const [isPopoverOpen, setIsPopoverOpen] = useState(false);
40
-
41
- // Update internal state when Tags prop changes
42
- useEffect(() => {
43
- // Only update if the Tags array reference has changed
44
- const selectedIds = selectedTagsStack.map(tag => tag.id);
45
- setTags(Tags.filter(tag => !selectedIds.includes(tag.id)));
46
- }, [Tags]);
47
-
48
- // Notify parent component when tags change
49
- useEffect(() => {
50
- if (onTagsChange) {
51
- onTagsChange(selectedTagsStack);
23
+ // Split initial tags into selected and unselected
24
+ const initialSelectedTags = Tags.filter((tag) => tag.isSelected);
25
+ const initialUnselectedTags = Tags.filter((tag) => !tag.isSelected);
26
+
27
+ // Initialize with available tags (excluding any initially selected ones)
28
+ const [tags, setTags] = useState<Tag[]>(initialUnselectedTags);
29
+
30
+ // Initialize with any pre-selected tags
31
+ const [selectedTagsStack, setSelectedTagsStack] = useState<Tag[]>(
32
+ singleSelect && initialSelectedTags.length > 0 ? [initialSelectedTags[0]] : initialSelectedTags,
33
+ );
34
+ const [searchTags, filterTagsBySearch] = useState("");
35
+ const [focusedTagIndex, setFocusedTagIndex] = useState<number | null>(null);
36
+ const [focusedPopoverIndex, setFocusedPopoverIndex] = useState<number | null>(null);
37
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
38
+
39
+ // Update internal state when Tags prop changes
40
+ useEffect(() => {
41
+ // Only update if the Tags array reference has changed
42
+ const selectedIds = selectedTagsStack.map((tag) => tag.id);
43
+ setTags(Tags.filter((tag) => !selectedIds.includes(tag.id)));
44
+ }, [Tags]);
45
+
46
+ // Notify parent component when tags change
47
+ useEffect(() => {
48
+ if (onTagsChange) {
49
+ onTagsChange(selectedTagsStack);
50
+ }
51
+ }, [selectedTagsStack]);
52
+
53
+ // Filter tags based on search input
54
+ const filteredTags = tags.filter((tag) =>
55
+ tag.name.toLowerCase().includes(searchTags.toLowerCase()),
56
+ );
57
+
58
+ // Filter selected tags based on search input
59
+ const filteredSelectedTags =
60
+ searchTags.length > 0
61
+ ? selectedTagsStack.filter((tag) => tag.name.toLowerCase().includes(searchTags.toLowerCase()))
62
+ : selectedTagsStack;
63
+
64
+ const handleSelectTag = (id: string) => {
65
+ const tagToSelect = tags.find((tag) => tag.id === id);
66
+ if (tagToSelect) {
67
+ // If in single select mode, replace the current selection
68
+ if (singleSelect) {
69
+ // Move any currently selected tag back to available tags
70
+ if (selectedTagsStack.length > 0) {
71
+ const currentSelected = selectedTagsStack[0];
72
+ setTags((prev) => [...prev, { ...currentSelected, isSelected: false }]);
52
73
  }
53
- }, [selectedTagsStack]);
54
-
55
- // Filter tags based on search input
56
- const filteredTags = tags.filter(tag =>
57
- tag.name.toLowerCase().includes(searchTags.toLowerCase())
58
- );
59
-
60
- // Filter selected tags based on search input
61
- const filteredSelectedTags = searchTags.length > 0
62
- ? selectedTagsStack.filter(tag =>
63
- tag.name.toLowerCase().includes(searchTags.toLowerCase()))
64
- : selectedTagsStack;
65
-
66
- const handleSelectTag = (id: string) => {
67
- const tagToSelect = tags.find(tag => tag.id === id);
68
- if (tagToSelect) {
69
- // If in single select mode, replace the current selection
70
- if (singleSelect) {
71
- // Move any currently selected tag back to available tags
72
- if (selectedTagsStack.length > 0) {
73
- const currentSelected = selectedTagsStack[0];
74
- setTags(prev => [...prev, { ...currentSelected, isSelected: false }]);
75
- }
76
- setSelectedTagsStack([{ ...tagToSelect, isSelected: true }]);
77
- setTags(prev => prev.filter(tag => tag.id !== id));
78
- } else {
79
- // Multi-select behavior (original)
80
- setSelectedTagsStack(prev => [...prev, { ...tagToSelect, isSelected: true }]);
81
- setTags(prev => prev.filter(tag => tag.id !== id));
82
- }
83
- }
84
- filterTagsBySearch('');
85
- setFocusedPopoverIndex(null);
86
- };
87
-
88
- const handleUnselectTag = (id: string) => {
89
- const tagToUnselect = selectedTagsStack.find(tag => tag.id === id);
90
- if (tagToUnselect) {
91
- setSelectedTagsStack(prev => prev.filter(tag => tag.id !== id));
92
- setTags(prev => [...prev, { ...tagToUnselect, isSelected: false }]);
93
- }
94
- setFocusedTagIndex(null);
95
- };
96
-
97
- // Reset the hook state with new data
98
- const reset = (newTags: Tag[] = [], newSelectedTags: Tag[] = []) => {
99
- // In single select mode, ensure we only have at most one selected tag
100
- const selectedTags = singleSelect && newSelectedTags.length > 0
101
- ? [newSelectedTags[0]]
102
- : newSelectedTags;
103
-
104
- setTags(newTags);
105
- setSelectedTagsStack(selectedTags);
106
- filterTagsBySearch('');
74
+ setSelectedTagsStack([{ ...tagToSelect, isSelected: true }]);
75
+ setTags((prev) => prev.filter((tag) => tag.id !== id));
76
+ } else {
77
+ // Multi-select behavior (original)
78
+ setSelectedTagsStack((prev) => [...prev, { ...tagToSelect, isSelected: true }]);
79
+ setTags((prev) => prev.filter((tag) => tag.id !== id));
80
+ }
81
+ }
82
+ filterTagsBySearch("");
83
+ setFocusedPopoverIndex(null);
84
+ };
85
+
86
+ const handleUnselectTag = (id: string) => {
87
+ const tagToUnselect = selectedTagsStack.find((tag) => tag.id === id);
88
+ if (tagToUnselect) {
89
+ setSelectedTagsStack((prev) => prev.filter((tag) => tag.id !== id));
90
+ setTags((prev) => [...prev, { ...tagToUnselect, isSelected: false }]);
91
+ }
92
+ setFocusedTagIndex(null);
93
+ };
94
+
95
+ // Reset the hook state with new data
96
+ const reset = (newTags: Tag[] = [], newSelectedTags: Tag[] = []) => {
97
+ // In single select mode, ensure we only have at most one selected tag
98
+ const selectedTags =
99
+ singleSelect && newSelectedTags.length > 0 ? [newSelectedTags[0]] : newSelectedTags;
100
+
101
+ setTags(newTags);
102
+ setSelectedTagsStack(selectedTags);
103
+ filterTagsBySearch("");
104
+ setFocusedTagIndex(null);
105
+ setFocusedPopoverIndex(null);
106
+ };
107
+
108
+ const handleNavigationWithinSelectedTags = (e: React.KeyboardEvent<HTMLElement>) => {
109
+ if (e.key === "ArrowLeft") {
110
+ e.preventDefault();
111
+ setFocusedTagIndex((prev: number | null) =>
112
+ prev === 0 ? selectedTagsStack.length - 1 : prev! - 1,
113
+ );
114
+ } else if (e.key === "ArrowRight") {
115
+ e.preventDefault();
116
+ setFocusedTagIndex((prev: number | null) =>
117
+ prev === selectedTagsStack.length - 1 ? 0 : prev! + 1,
118
+ );
119
+ } else if (e.key === "Delete" || e.key === "Backspace") {
120
+ e.preventDefault();
121
+ const tagToRemove = selectedTagsStack[focusedTagIndex!];
122
+ handleUnselectTag(tagToRemove.id);
123
+ if (selectedTagsStack.length > 1) {
124
+ setFocusedTagIndex(Math.min(focusedTagIndex!, selectedTagsStack.length - 2));
125
+ } else {
107
126
  setFocusedTagIndex(null);
108
- setFocusedPopoverIndex(null);
109
- };
110
-
111
- const handleNavigationWithinSelectedTags = (e: React.KeyboardEvent<HTMLElement>) => {
112
- if (e.key === 'ArrowLeft') {
113
- e.preventDefault();
114
- setFocusedTagIndex((prev: any) => (prev === 0 ? selectedTagsStack.length - 1 : prev - 1));
115
- } else if (e.key === 'ArrowRight') {
116
- e.preventDefault();
117
- setFocusedTagIndex((prev: any) => (prev === selectedTagsStack.length - 1 ? 0 : prev + 1));
118
- } else if (e.key === 'Delete' || e.key === 'Backspace') {
119
- e.preventDefault();
120
- const tagToRemove = selectedTagsStack[focusedTagIndex!];
121
- handleUnselectTag(tagToRemove.id);
122
- if (selectedTagsStack.length > 1) {
123
- setFocusedTagIndex(Math.min(focusedTagIndex!, selectedTagsStack.length - 2));
124
- } else {
125
- setFocusedTagIndex(null);
126
- }
127
- } else if (e.key === 'Escape') {
128
- e.preventDefault();
129
- setFocusedTagIndex(null);
130
- } else if (e.key === 'ArrowDown') {
131
- e.preventDefault();
132
- setIsPopoverOpen(true);
133
- setFocusedPopoverIndex(0);
134
- setFocusedTagIndex(null);
127
+ }
128
+ } else if (e.key === "Escape") {
129
+ e.preventDefault();
130
+ setFocusedTagIndex(null);
131
+ } else if (e.key === "ArrowDown") {
132
+ e.preventDefault();
133
+ setIsPopoverOpen(true);
134
+ setFocusedPopoverIndex(0);
135
+ setFocusedTagIndex(null);
136
+ } else {
137
+ inputRef?.current?.focus();
138
+ }
139
+ };
140
+
141
+ const handleNavigationWithinPopover = (e: React.KeyboardEvent<HTMLElement>) => {
142
+ if (e.key === "ArrowUp") {
143
+ e.preventDefault();
144
+ if (focusedPopoverIndex === 0 || focusedPopoverIndex === null) {
145
+ if (selectedTagsStack.length > 0) {
146
+ setIsPopoverOpen(false);
147
+ setFocusedTagIndex(0);
148
+ setFocusedPopoverIndex(null);
135
149
  } else {
136
- inputRef?.current?.focus();
150
+ setFocusedPopoverIndex(filteredTags.length - 1);
137
151
  }
138
- };
139
-
140
- const handleNavigationWithinPopover = (e: React.KeyboardEvent<HTMLElement>) => {
141
- if (e.key === 'ArrowUp') {
142
- e.preventDefault();
143
- if (focusedPopoverIndex === 0 || focusedPopoverIndex === null) {
144
- if (selectedTagsStack.length > 0) {
145
- setIsPopoverOpen(false);
146
- setFocusedTagIndex(0);
147
- setFocusedPopoverIndex(null);
148
- } else {
149
- setFocusedPopoverIndex(filteredTags.length - 1);
150
- }
151
- } else {
152
- setFocusedPopoverIndex(prev => (prev === null ? 0 : prev - 1));
153
- }
154
- } else if (e.key === 'ArrowDown') {
155
- e.preventDefault();
156
- setFocusedPopoverIndex(prev =>
157
- prev === null ? 0 : (prev === filteredTags.length - 1 ? 0 : prev + 1)
158
- );
159
- } else if (e.key === 'Enter' && focusedPopoverIndex !== null) {
160
- e.preventDefault();
161
- handleSelectTag(filteredTags[focusedPopoverIndex].id);
162
- } else if (e.key === 'Escape') {
163
- e.preventDefault();
164
- setIsPopoverOpen(false);
165
- setFocusedPopoverIndex(null);
166
- } else {
167
- inputRef?.current?.focus();
168
- }
169
- };
170
-
171
- const handleInitialFocus = (e: React.KeyboardEvent<HTMLElement>) => {
172
- if ((e.key === 'ArrowLeft' || e.key === 'ArrowRight') && selectedTagsStack.length > 0) {
173
- e.preventDefault();
174
- setFocusedTagIndex(e.key === 'ArrowLeft' ? selectedTagsStack.length - 1 : 0);
175
- } else if (e.key === 'ArrowDown' && !isPopoverOpen && filteredTags.length > 0) {
176
- e.preventDefault();
177
- setIsPopoverOpen(true);
178
- setFocusedPopoverIndex(0);
179
- } else {
180
- inputRef?.current?.focus();
181
- }
182
- };
183
-
184
- const handleKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
185
- if (focusedTagIndex !== null && !isPopoverOpen) {
186
- handleNavigationWithinSelectedTags(e);
187
- } else if (isPopoverOpen && filteredTags.length > 0) {
188
- handleNavigationWithinPopover(e);
189
- } else {
190
- handleInitialFocus(e);
191
- }
192
- };
193
-
194
- return {
195
- tags,
196
- selectedTagsStack,
197
- searchTags,
198
- handleSelectTag,
199
- handleUnselectTag,
200
- handleKeyDown,
201
- setFocusedTagIndex,
202
- setFocusedPopoverIndex,
203
- filterTagsBySearch,
204
- filteredTags,
205
- filteredSelectedTags,
206
- focusedTagIndex,
207
- focusedPopoverIndex,
208
- isPopoverOpen,
209
- setIsPopoverOpen,
210
- reset,
211
- singleSelect
212
- };
152
+ } else {
153
+ setFocusedPopoverIndex((prev) => (prev === null ? 0 : prev - 1));
154
+ }
155
+ } else if (e.key === "ArrowDown") {
156
+ e.preventDefault();
157
+ setFocusedPopoverIndex((prev) =>
158
+ prev === null ? 0 : prev === filteredTags.length - 1 ? 0 : prev + 1,
159
+ );
160
+ } else if (e.key === "Enter" && focusedPopoverIndex !== null) {
161
+ e.preventDefault();
162
+ handleSelectTag(filteredTags[focusedPopoverIndex].id);
163
+ } else if (e.key === "Escape") {
164
+ e.preventDefault();
165
+ setIsPopoverOpen(false);
166
+ setFocusedPopoverIndex(null);
167
+ } else {
168
+ inputRef?.current?.focus();
169
+ }
170
+ };
171
+
172
+ const handleInitialFocus = (e: React.KeyboardEvent<HTMLElement>) => {
173
+ if ((e.key === "ArrowLeft" || e.key === "ArrowRight") && selectedTagsStack.length > 0) {
174
+ e.preventDefault();
175
+ setFocusedTagIndex(e.key === "ArrowLeft" ? selectedTagsStack.length - 1 : 0);
176
+ } else if (e.key === "ArrowDown" && !isPopoverOpen && filteredTags.length > 0) {
177
+ e.preventDefault();
178
+ setIsPopoverOpen(true);
179
+ setFocusedPopoverIndex(0);
180
+ } else {
181
+ inputRef?.current?.focus();
182
+ }
183
+ };
184
+
185
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
186
+ if (focusedTagIndex !== null && !isPopoverOpen) {
187
+ handleNavigationWithinSelectedTags(e);
188
+ } else if (isPopoverOpen && filteredTags.length > 0) {
189
+ handleNavigationWithinPopover(e);
190
+ } else {
191
+ handleInitialFocus(e);
192
+ }
193
+ };
194
+
195
+ return {
196
+ tags,
197
+ selectedTagsStack,
198
+ searchTags,
199
+ handleSelectTag,
200
+ handleUnselectTag,
201
+ handleKeyDown,
202
+ setFocusedTagIndex,
203
+ setFocusedPopoverIndex,
204
+ filterTagsBySearch,
205
+ filteredTags,
206
+ filteredSelectedTags,
207
+ focusedTagIndex,
208
+ focusedPopoverIndex,
209
+ isPopoverOpen,
210
+ setIsPopoverOpen,
211
+ reset,
212
+ singleSelect,
213
+ };
213
214
  };
214
-