@shohojdhara/atomix 0.3.15 → 0.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 (276) hide show
  1. package/build-tools/index.d.ts +31 -30
  2. package/build-tools/package.json +4 -21
  3. package/dist/atomix.css +20234 -2027
  4. package/dist/atomix.css.map +1 -1
  5. package/dist/atomix.min.css +76 -2
  6. package/dist/atomix.min.css.map +1 -1
  7. package/dist/build-tools/index.d.ts +31 -30
  8. package/dist/build-tools/package.json +4 -21
  9. package/dist/charts.js +4 -5
  10. package/dist/charts.js.map +1 -1
  11. package/dist/core.d.ts +87 -10
  12. package/dist/core.js +673 -480
  13. package/dist/core.js.map +1 -1
  14. package/dist/forms.d.ts +15 -3
  15. package/dist/forms.js +530 -97
  16. package/dist/forms.js.map +1 -1
  17. package/dist/heavy.js +5 -6
  18. package/dist/heavy.js.map +1 -1
  19. package/dist/index.d.ts +644 -277
  20. package/dist/index.esm.js +1948 -1347
  21. package/dist/index.esm.js.map +1 -1
  22. package/dist/index.js +3333 -2728
  23. package/dist/index.js.map +1 -1
  24. package/dist/index.min.js +1 -1
  25. package/dist/index.min.js.map +1 -1
  26. package/dist/layout.js.map +1 -1
  27. package/dist/theme.d.ts +9 -9
  28. package/dist/theme.js.map +1 -1
  29. package/package.json +2 -2
  30. package/scripts/atomix-cli.js +10 -1
  31. package/scripts/cli/__tests__/utils.test.js +6 -2
  32. package/scripts/cli/migration-tools.js +2 -2
  33. package/scripts/cli/theme-bridge.js +7 -9
  34. package/scripts/cli/utils.js +2 -1
  35. package/src/components/Accordion/Accordion.stories.tsx +72 -23
  36. package/src/components/Accordion/Accordion.test.tsx +70 -50
  37. package/src/components/Accordion/Accordion.tsx +219 -96
  38. package/src/components/Accordion/AccordionCompound.test.tsx +70 -0
  39. package/src/components/AtomixGlass/AtomixGlass.test.tsx +1 -1
  40. package/src/components/AtomixGlass/GlassFilter.tsx +9 -16
  41. package/src/components/AtomixGlass/glass-utils.ts +4 -3
  42. package/src/components/AtomixGlass/shader-utils.ts +128 -52
  43. package/src/components/AtomixGlass/stories/Playground.stories.tsx +1 -1
  44. package/src/components/AtomixGlass/stories/Shaders.stories.tsx +1 -1
  45. package/src/components/Avatar/Avatar.stories.tsx +45 -62
  46. package/src/components/Avatar/Avatar.tsx +58 -56
  47. package/src/components/Badge/Badge.stories.tsx +20 -9
  48. package/src/components/Badge/Badge.test.tsx +41 -41
  49. package/src/components/Badge/Badge.tsx +64 -62
  50. package/src/components/Block/Block.stories.tsx +14 -4
  51. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +9 -8
  52. package/src/components/Breadcrumb/Breadcrumb.tsx +173 -65
  53. package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +84 -0
  54. package/src/components/Button/Button.stories.tsx +13 -22
  55. package/src/components/Button/Button.test.tsx +97 -81
  56. package/src/components/Button/Button.tsx +46 -14
  57. package/src/components/Button/ButtonGroup.stories.tsx +37 -32
  58. package/src/components/Button/ButtonGroup.tsx +4 -15
  59. package/src/components/Callout/Callout.stories.tsx +166 -918
  60. package/src/components/Callout/Callout.tsx +196 -84
  61. package/src/components/Callout/CalloutCompound.test.tsx +72 -0
  62. package/src/components/Card/Card.stories.tsx +67 -36
  63. package/src/components/Card/Card.tsx +30 -14
  64. package/src/components/Chart/AreaChart.tsx +1 -1
  65. package/src/components/Chart/CandlestickChart.tsx +23 -16
  66. package/src/components/Chart/Chart.stories.tsx +4 -9
  67. package/src/components/Chart/Chart.tsx +40 -44
  68. package/src/components/Chart/ChartRenderer.tsx +39 -12
  69. package/src/components/Chart/ChartToolbar.tsx +21 -5
  70. package/src/components/Chart/DonutChart.tsx +1 -1
  71. package/src/components/Chart/FunnelChart.tsx +4 -1
  72. package/src/components/Chart/GaugeChart.tsx +3 -1
  73. package/src/components/Chart/HeatmapChart.tsx +50 -37
  74. package/src/components/Chart/LineChart.tsx +3 -2
  75. package/src/components/Chart/MultiAxisChart.tsx +24 -16
  76. package/src/components/Chart/RadarChart.tsx +19 -17
  77. package/src/components/Chart/ScatterChart.tsx +29 -21
  78. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +6 -2
  79. package/src/components/ColorModeToggle/ColorModeToggle.tsx +15 -3
  80. package/src/components/Countdown/Countdown.stories.tsx +7 -7
  81. package/src/components/DataTable/DataTable.stories.tsx +43 -38
  82. package/src/components/DataTable/DataTable.test.tsx +26 -148
  83. package/src/components/DataTable/DataTable.tsx +485 -456
  84. package/src/components/DatePicker/DatePicker.stories.tsx +32 -47
  85. package/src/components/DatePicker/DatePicker.tsx +31 -26
  86. package/src/components/Dropdown/Dropdown.stories.tsx +2 -5
  87. package/src/components/Dropdown/Dropdown.tsx +425 -298
  88. package/src/components/Dropdown/DropdownCompound.test.tsx +64 -0
  89. package/src/components/EdgePanel/EdgePanel.stories.tsx +6 -19
  90. package/src/components/EdgePanel/EdgePanel.tsx +163 -113
  91. package/src/components/EdgePanel/EdgePanelCompound.test.tsx +53 -0
  92. package/src/components/Footer/Footer.stories.tsx +21 -16
  93. package/src/components/Footer/Footer.tsx +130 -128
  94. package/src/components/Footer/FooterLink.tsx +2 -2
  95. package/src/components/Form/Checkbox.test.tsx +49 -49
  96. package/src/components/Form/Checkbox.tsx +108 -100
  97. package/src/components/Form/Form.stories.tsx +2 -10
  98. package/src/components/Form/Input.stories.tsx +22 -39
  99. package/src/components/Form/Input.test.tsx +38 -44
  100. package/src/components/Form/Radio.stories.tsx +6 -12
  101. package/src/components/Form/Radio.tsx +68 -66
  102. package/src/components/Form/Select.stories.tsx +23 -0
  103. package/src/components/Form/Select.test.tsx +99 -0
  104. package/src/components/Form/Select.tsx +239 -186
  105. package/src/components/Form/SelectOption.tsx +88 -0
  106. package/src/components/Form/Textarea.test.tsx +27 -32
  107. package/src/components/Hero/Hero.stories.tsx +93 -23
  108. package/src/components/Hero/Hero.test.tsx +142 -0
  109. package/src/components/Hero/Hero.tsx +343 -58
  110. package/src/components/Icon/index.ts +7 -1
  111. package/src/components/List/List.test.tsx +62 -0
  112. package/src/components/List/List.tsx +32 -25
  113. package/src/components/List/ListItem.tsx +20 -0
  114. package/src/components/Modal/Modal.stories.tsx +67 -2
  115. package/src/components/Modal/Modal.tsx +208 -125
  116. package/src/components/Modal/ModalCompound.test.tsx +94 -0
  117. package/src/components/Navigation/Menu/MegaMenu.tsx +70 -70
  118. package/src/components/Navigation/Nav/NavDropdown.tsx +1 -5
  119. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +128 -28
  120. package/src/components/Navigation/SideMenu/SideMenu.tsx +5 -7
  121. package/src/components/Navigation/SideMenu/SideMenuItem.tsx +4 -5
  122. package/src/components/Pagination/Pagination.stories.tsx +7 -4
  123. package/src/components/Pagination/Pagination.tsx +199 -202
  124. package/src/components/PhotoViewer/PhotoViewer.tsx +4 -1
  125. package/src/components/Popover/Popover.stories.tsx +99 -192
  126. package/src/components/Popover/Popover.tsx +41 -37
  127. package/src/components/Progress/Progress.stories.tsx +35 -44
  128. package/src/components/River/River.stories.tsx +2 -1
  129. package/src/components/SectionIntro/SectionIntro.stories.tsx +71 -71
  130. package/src/components/Slider/Slider.stories.tsx +12 -4
  131. package/src/components/Spinner/Spinner.stories.tsx +3 -1
  132. package/src/components/Spinner/Spinner.test.tsx +23 -23
  133. package/src/components/Spinner/Spinner.tsx +43 -46
  134. package/src/components/Steps/Steps.stories.tsx +8 -6
  135. package/src/components/Steps/Steps.tsx +124 -21
  136. package/src/components/Steps/StepsCompound.test.tsx +81 -0
  137. package/src/components/Tabs/Tabs.stories.tsx +12 -9
  138. package/src/components/Tabs/Tabs.tsx +230 -75
  139. package/src/components/Tabs/TabsCompound.test.tsx +64 -0
  140. package/src/components/Toggle/Toggle.stories.tsx +27 -13
  141. package/src/components/Toggle/Toggle.test.tsx +65 -70
  142. package/src/components/Toggle/Toggle.tsx +4 -1
  143. package/src/components/Tooltip/Tooltip.stories.tsx +24 -20
  144. package/src/components/Tooltip/Tooltip.tsx +104 -106
  145. package/src/components/Upload/Upload.stories.tsx +129 -127
  146. package/src/components/Upload/Upload.tsx +287 -283
  147. package/src/components/VideoPlayer/VideoPlayer.tsx +6 -1
  148. package/src/components/index.ts +13 -2
  149. package/src/layouts/Grid/Grid.stories.tsx +9 -3
  150. package/src/layouts/MasonryGrid/MasonryGrid.tsx +5 -1
  151. package/src/lib/__tests__/theme-tools.test.ts +32 -6
  152. package/src/lib/composables/index.ts +0 -4
  153. package/src/lib/composables/shared-mouse-tracker.ts +13 -14
  154. package/src/lib/composables/useAtomixGlass.ts +102 -60
  155. package/src/lib/composables/useChartExport.ts +1 -1
  156. package/src/lib/composables/useDataTable.ts +29 -17
  157. package/src/lib/composables/useHero.ts +58 -14
  158. package/src/lib/composables/useHeroBackgroundSlider.ts +2 -9
  159. package/src/lib/composables/useInput.ts +10 -8
  160. package/src/lib/composables/useSideMenu.ts +6 -5
  161. package/src/lib/composables/useTooltip.ts +1 -2
  162. package/src/lib/composables/useVideoPlayer.ts +44 -35
  163. package/src/lib/config/index.ts +154 -154
  164. package/src/lib/constants/cssVariables.ts +29 -29
  165. package/src/lib/hooks/__tests__/useComponentCustomization.test.ts +2 -6
  166. package/src/lib/hooks/index.ts +1 -1
  167. package/src/lib/hooks/useComponentCustomization.ts +11 -17
  168. package/src/lib/hooks/usePerformanceMonitor.ts +6 -7
  169. package/src/lib/patterns/__tests__/slots.test.ts +1 -1
  170. package/src/lib/patterns/index.ts +1 -1
  171. package/src/lib/patterns/slots.tsx +8 -13
  172. package/src/lib/storybook/InteractiveDemo.tsx +13 -18
  173. package/src/lib/storybook/PreviewContainer.tsx +1 -1
  174. package/src/lib/storybook/VariantsGrid.tsx +3 -7
  175. package/src/lib/storybook/index.ts +1 -1
  176. package/src/lib/theme/adapters/cssVariableMapper.ts +47 -74
  177. package/src/lib/theme/adapters/index.ts +3 -9
  178. package/src/lib/theme/adapters/themeAdapter.ts +41 -26
  179. package/src/lib/theme/config/index.ts +1 -1
  180. package/src/lib/theme/config/types.ts +2 -2
  181. package/src/lib/theme/config/validator.ts +10 -5
  182. package/src/lib/theme/constants/constants.ts +2 -2
  183. package/src/lib/theme/constants/index.ts +1 -2
  184. package/src/lib/theme/core/__tests__/createTheme.test.ts +20 -22
  185. package/src/lib/theme/core/composeTheme.ts +32 -26
  186. package/src/lib/theme/core/createTheme.ts +1 -1
  187. package/src/lib/theme/core/createThemeObject.ts +308 -301
  188. package/src/lib/theme/core/index.ts +3 -3
  189. package/src/lib/theme/devtools/CLI.ts +105 -111
  190. package/src/lib/theme/devtools/Comparator.tsx +50 -32
  191. package/src/lib/theme/devtools/DesignTokensCustomizer.stories.tsx +50 -48
  192. package/src/lib/theme/devtools/DesignTokensCustomizer.tsx +257 -63
  193. package/src/lib/theme/devtools/Inspector.tsx +75 -60
  194. package/src/lib/theme/devtools/LiveEditor.tsx +97 -76
  195. package/src/lib/theme/devtools/Preview.tsx +150 -106
  196. package/src/lib/theme/devtools/ThemeValidator.ts +29 -21
  197. package/src/lib/theme/devtools/index.ts +3 -9
  198. package/src/lib/theme/devtools/useHistory.ts +23 -21
  199. package/src/lib/theme/errors/errors.ts +12 -11
  200. package/src/lib/theme/errors/index.ts +2 -7
  201. package/src/lib/theme/generators/generateCSS.ts +9 -13
  202. package/src/lib/theme/generators/generateCSSNested.ts +1 -6
  203. package/src/lib/theme/generators/generateCSSVariables.ts +673 -630
  204. package/src/lib/theme/generators/index.ts +1 -4
  205. package/src/lib/theme/i18n/index.ts +1 -1
  206. package/src/lib/theme/i18n/rtl.ts +13 -13
  207. package/src/lib/theme/index.ts +7 -16
  208. package/src/lib/theme/runtime/ThemeApplicator.ts +4 -4
  209. package/src/lib/theme/runtime/ThemeContext.tsx +1 -1
  210. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +19 -23
  211. package/src/lib/theme/runtime/ThemeProvider.tsx +230 -239
  212. package/src/lib/theme/runtime/__tests__/ThemeProvider.integration.test.tsx +1 -1
  213. package/src/lib/theme/runtime/__tests__/ThemeProvider.test.tsx +24 -29
  214. package/src/lib/theme/runtime/index.ts +2 -5
  215. package/src/lib/theme/runtime/useTheme.ts +18 -18
  216. package/src/lib/theme/runtime/useThemeTokens.ts +22 -22
  217. package/src/lib/theme/test/testTheme.ts +15 -16
  218. package/src/lib/theme/tokens/index.ts +2 -7
  219. package/src/lib/theme/tokens/tokens.ts +25 -24
  220. package/src/lib/theme/types.ts +428 -411
  221. package/src/lib/theme/utils/__tests__/themeValidation.test.ts +3 -3
  222. package/src/lib/theme/utils/componentTheming.ts +18 -18
  223. package/src/lib/theme/utils/domUtils.ts +277 -289
  224. package/src/lib/theme/utils/index.ts +1 -2
  225. package/src/lib/theme/utils/injectCSS.ts +10 -14
  226. package/src/lib/theme/utils/naming.ts +20 -16
  227. package/src/lib/theme/utils/themeHelpers.ts +10 -12
  228. package/src/lib/theme/utils/themeUtils.ts +85 -86
  229. package/src/lib/theme/utils/themeValidation.ts +82 -33
  230. package/src/lib/theme-tools.ts +8 -6
  231. package/src/lib/types/components.ts +180 -73
  232. package/src/lib/types/partProps.ts +1 -1
  233. package/src/lib/utils/__tests__/componentUtils.test.ts +57 -2
  234. package/src/lib/utils/__tests__/csv.test.ts +1 -1
  235. package/src/lib/utils/__tests__/themeNaming.test.ts +117 -0
  236. package/src/lib/utils/componentUtils.ts +8 -12
  237. package/src/lib/utils/csv.ts +3 -1
  238. package/src/lib/utils/dataTableExport.ts +1 -5
  239. package/src/lib/utils/fontPreloader.ts +10 -19
  240. package/src/lib/utils/icons.ts +4 -1
  241. package/src/lib/utils/index.ts +2 -6
  242. package/src/lib/utils/memoryMonitor.ts +10 -8
  243. package/src/lib/utils/themeNaming.ts +3 -3
  244. package/src/styles/01-settings/_index.scss +0 -1
  245. package/src/styles/01-settings/_settings.colors.scss +8 -8
  246. package/src/styles/01-settings/_settings.design-tokens.scss +61 -50
  247. package/src/styles/01-settings/_settings.navbar.scss +1 -1
  248. package/src/styles/01-settings/_settings.spacing.scss +3 -4
  249. package/src/styles/01-settings/_settings.tooltip.scss +1 -1
  250. package/src/styles/01-settings/_settings.typography.scss +1 -1
  251. package/src/styles/02-tools/_tools.breakpoints.scss +1 -1
  252. package/src/styles/02-tools/_tools.button.scss +51 -21
  253. package/src/styles/02-tools/_tools.utility-api.scss +36 -24
  254. package/src/styles/03-generic/_generic.root.scss +4 -3
  255. package/src/styles/06-components/_components.atomix-glass.scss +13 -9
  256. package/src/styles/06-components/_components.button.scss +16 -4
  257. package/src/styles/06-components/_components.callout.scss +27 -21
  258. package/src/styles/06-components/_components.card.scss +5 -14
  259. package/src/styles/06-components/_components.chart.scss +22 -19
  260. package/src/styles/06-components/_components.checkbox.scss +3 -1
  261. package/src/styles/06-components/_components.color-mode-toggle.scss +3 -1
  262. package/src/styles/06-components/_components.edge-panel.scss +9 -2
  263. package/src/styles/06-components/_components.footer.scss +1 -1
  264. package/src/styles/06-components/_components.side-menu.scss +5 -5
  265. package/src/styles/06-components/_components.toggle.scss +18 -0
  266. package/src/styles/06-components/_index.scss +1 -1
  267. package/src/styles/06-components/old.chart.styles.scss +0 -2
  268. package/src/styles/99-utilities/_utilities.border.scss +69 -27
  269. package/src/styles/99-utilities/_utilities.display.scss +1 -1
  270. package/src/styles/99-utilities/_utilities.opacity.scss +10 -0
  271. package/src/styles/99-utilities/_utilities.position.scss +16 -9
  272. package/src/styles/99-utilities/_utilities.scss +1 -1
  273. package/src/styles/99-utilities/_utilities.sizes.scss +47 -18
  274. package/src/styles/99-utilities/_utilities.spacing.scss +118 -66
  275. package/src/styles/99-utilities/_utilities.text-gradient.scss +30 -30
  276. package/src/styles/99-utilities/_utilities.text.scss +67 -47
@@ -1,213 +1,266 @@
1
- import React, { useRef, useEffect, useState, memo } from 'react';
2
- import { SelectProps } from '../../lib/types/components';
1
+ import React, { useRef, useEffect, useState, memo, useCallback } from 'react';
2
+ import { SelectProps, SelectOption as SelectOptionType } from '../../lib/types/components';
3
3
  import { useSelect } from '../../lib/composables';
4
4
  import { SELECT } from '../../lib/constants/components';
5
5
  import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
6
+ import { SelectContext, SelectOption } from './SelectOption';
7
+
8
+ export type SelectComponent = React.FC<SelectProps> & {
9
+ Option: typeof SelectOption;
10
+ };
6
11
 
7
12
  /**
8
13
  * Select - A component for dropdown selection
9
14
  */
10
- export const Select: React.FC<SelectProps> = memo(({
11
- options = [],
12
- value,
13
- onChange,
14
- onBlur,
15
- onFocus,
16
- placeholder = 'Select an option',
17
- className = '',
18
- style,
19
- disabled = false,
20
- required = false,
21
- id,
22
- name,
23
- size = 'md',
24
- invalid = false,
25
- valid = false,
26
- multiple = false,
27
- 'aria-label': ariaLabel,
28
- 'aria-describedby': ariaDescribedBy,
29
- glass,
30
- }) => {
31
- const { generateSelectClass } = useSelect({
32
- size,
33
- disabled,
34
- invalid,
35
- valid,
36
- });
37
-
38
- const selectClass = generateSelectClass({
39
- className: `${className} ${glass ? 'c-select--glass' : ''}`.trim(),
40
- size,
41
- disabled,
42
- invalid,
43
- valid,
44
- });
45
-
46
- const [isOpen, setIsOpen] = useState(false);
47
- const [selectedLabel, setSelectedLabel] = useState(placeholder);
48
- const dropdownRef = useRef<HTMLDivElement>(null);
49
- const panelRef = useRef<HTMLDivElement>(null);
50
- const bodyRef = useRef<HTMLDivElement>(null);
51
- const nativeSelectRef = useRef<HTMLSelectElement>(null);
52
-
53
- // Update selected label when value changes
54
- useEffect(() => {
55
- if (value) {
56
- const selectedOption = options.find(opt => opt.value === value);
57
- if (selectedOption) {
58
- setSelectedLabel(selectedOption.label);
15
+ export const Select: SelectComponent = memo(
16
+ ({
17
+ options,
18
+ value,
19
+ onChange,
20
+ onBlur,
21
+ onFocus,
22
+ placeholder = 'Select an option',
23
+ className = '',
24
+ style,
25
+ disabled = false,
26
+ required = false,
27
+ id,
28
+ name,
29
+ size = 'md',
30
+ invalid = false,
31
+ valid = false,
32
+ multiple = false,
33
+ 'aria-label': ariaLabel,
34
+ 'aria-describedby': ariaDescribedBy,
35
+ glass,
36
+ children,
37
+ }: SelectProps) => {
38
+ const { generateSelectClass } = useSelect({
39
+ size,
40
+ disabled,
41
+ invalid,
42
+ valid,
43
+ });
44
+
45
+ const selectClass = generateSelectClass({
46
+ className: `${className} ${glass ? 'c-select--glass' : ''}`.trim(),
47
+ size,
48
+ disabled,
49
+ invalid,
50
+ valid,
51
+ });
52
+
53
+ const [isOpen, setIsOpen] = useState(false);
54
+ const [selectedLabel, setSelectedLabel] = useState(placeholder);
55
+ const dropdownRef = useRef<HTMLDivElement>(null);
56
+ const panelRef = useRef<HTMLDivElement>(null);
57
+ const bodyRef = useRef<HTMLDivElement>(null);
58
+ const nativeSelectRef = useRef<HTMLSelectElement>(null);
59
+
60
+ // State for registered options (Compound mode)
61
+ const [registeredOptions, setRegisteredOptions] = useState<SelectOptionType[]>([]);
62
+
63
+ const registerOption = useCallback((option: SelectOptionType) => {
64
+ setRegisteredOptions((prev) => {
65
+ if (prev.some(o => o.value === option.value)) return prev;
66
+ return [...prev, option];
67
+ });
68
+ }, []);
69
+
70
+ const unregisterOption = useCallback((value: string) => {
71
+ setRegisteredOptions((prev) => prev.filter(o => o.value !== value));
72
+ }, []);
73
+
74
+ // Determine active options
75
+ const hasOptionsProp = options && options.length > 0;
76
+ const activeOptions = hasOptionsProp ? options : registeredOptions;
77
+
78
+ // Update selected label when value changes
79
+ useEffect(() => {
80
+ if (value) {
81
+ const selectedOption = activeOptions.find(opt => opt.value === value);
82
+ if (selectedOption) {
83
+ setSelectedLabel(selectedOption.label);
84
+ }
85
+ } else {
86
+ setSelectedLabel(placeholder);
59
87
  }
60
- } else {
61
- setSelectedLabel(placeholder);
62
- }
63
- }, [value, options, placeholder]);
88
+ }, [value, activeOptions, placeholder]);
64
89
 
65
- // Handle click outside to close dropdown
66
- useEffect(() => {
67
- const handleClickOutside = (event: MouseEvent) => {
68
- if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
69
- setIsOpen(false);
70
- if (bodyRef.current) {
90
+ // Handle click outside to close dropdown
91
+ useEffect(() => {
92
+ const handleClickOutside = (event: MouseEvent) => {
93
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
94
+ setIsOpen(false);
95
+ if (bodyRef.current) {
96
+ bodyRef.current.style.height = '0px';
97
+ }
98
+ }
99
+ };
100
+
101
+ document.addEventListener('mousedown', handleClickOutside);
102
+ return () => {
103
+ document.removeEventListener('mousedown', handleClickOutside);
104
+ };
105
+ }, []);
106
+
107
+ // Toggle dropdown
108
+ const handleToggle = () => {
109
+ if (!disabled) {
110
+ if (!isOpen && bodyRef.current && panelRef.current) {
111
+ bodyRef.current.style.height = `${panelRef.current.clientHeight}px`;
112
+ } else if (bodyRef.current) {
71
113
  bodyRef.current.style.height = '0px';
72
114
  }
115
+ setIsOpen(!isOpen);
73
116
  }
74
117
  };
75
118
 
76
- document.addEventListener('mousedown', handleClickOutside);
77
- return () => {
78
- document.removeEventListener('mousedown', handleClickOutside);
79
- };
80
- }, []);
81
-
82
- // Toggle dropdown
83
- const handleToggle = () => {
84
- if (!disabled) {
85
- if (!isOpen && bodyRef.current && panelRef.current) {
86
- bodyRef.current.style.height = `${panelRef.current.clientHeight}px`;
87
- } else if (bodyRef.current) {
88
- bodyRef.current.style.height = '0px';
89
- }
90
- setIsOpen(!isOpen);
91
- }
92
- };
93
-
94
- // Handle item selection
95
- const handleItemClick = (option: { value: string; label: string }) => {
96
- setSelectedLabel(option.label);
97
- setIsOpen(false);
98
- if (bodyRef.current) {
99
- bodyRef.current.style.height = '0px';
100
- }
119
+ // Handle item selection
120
+ const handleItemClick = useCallback(
121
+ (option: { value: string; label: string }) => {
122
+ setSelectedLabel(option.label);
123
+ setIsOpen(false);
124
+ if (bodyRef.current) {
125
+ bodyRef.current.style.height = '0px';
126
+ }
101
127
 
102
- if (nativeSelectRef.current) {
103
- nativeSelectRef.current.value = option.value;
104
- }
128
+ if (nativeSelectRef.current) {
129
+ nativeSelectRef.current.value = option.value;
130
+ }
105
131
 
106
- if (onChange) {
107
- // Create a synthetic event
108
- const event = {
109
- target: {
110
- name,
111
- value: option.value,
112
- },
113
- } as React.ChangeEvent<HTMLSelectElement>;
114
- onChange(event);
115
- }
116
- };
117
-
118
- const selectContent = (
119
- <div
120
- className={`${selectClass} ${isOpen ? SELECT.CLASSES.IS_OPEN : ''}`}
121
- ref={dropdownRef}
122
- style={style}
123
- aria-expanded={isOpen}
124
- >
125
- {/* Native select for accessibility and form submission */}
126
- <select
127
- ref={nativeSelectRef}
128
- value={value}
129
- onChange={onChange}
130
- onBlur={onBlur}
131
- onFocus={onFocus}
132
- disabled={disabled}
133
- required={required}
134
- id={id}
135
- name={name}
136
- multiple={multiple}
137
- aria-label={ariaLabel}
138
- aria-describedby={ariaDescribedBy}
139
- aria-invalid={invalid}
140
- style={{ display: 'none' }}
141
- >
142
- {placeholder && (
143
- <option value="" disabled>
144
- {placeholder}
145
- </option>
146
- )}
147
- {options.map(option => (
148
- <option key={option.value} value={option.value} disabled={option.disabled}>
149
- {option.label}
150
- </option>
151
- ))}
152
- </select>
153
-
154
- {/* Custom Select UI */}
155
- <div className={SELECT.CLASSES.SELECTED} onClick={handleToggle} aria-disabled={disabled}>
156
- {selectedLabel}
157
- </div>
158
-
159
- <i className={`${SELECT.CLASSES.ICON_CARET} ${SELECT.CLASSES.TOGGLE_ICON}`} />
160
-
161
- <div className={SELECT.CLASSES.SELECT_BODY} ref={bodyRef} style={{ height: 0 }}>
162
- <div className={SELECT.CLASSES.SELECT_PANEL} ref={panelRef}>
163
- <ul className={SELECT.CLASSES.SELECT_ITEMS}>
164
- {options.map((option, index) => (
165
- <li
166
- key={option.value}
167
- className={SELECT.CLASSES.SELECT_ITEM}
168
- data-value={option.value}
169
- onClick={() => !option.disabled && handleItemClick(option)}
170
- >
171
- <label htmlFor={`SelectItem${index}`} className="c-checkbox">
172
- <input
173
- type="checkbox"
174
- id={`SelectItem${index}`}
175
- className="c-checkbox__input c-select__item-input"
176
- checked={value === option.value}
177
- readOnly
178
- disabled={option.disabled}
179
- />
180
- <div className="c-select__item-label">{option.label}</div>
181
- </label>
182
- </li>
132
+ if (onChange) {
133
+ // Create a synthetic event
134
+ const event = {
135
+ target: {
136
+ name,
137
+ value: option.value,
138
+ },
139
+ } as React.ChangeEvent<HTMLSelectElement>;
140
+ onChange(event);
141
+ }
142
+ },
143
+ [onChange, name]
144
+ );
145
+
146
+ const onSelect = useCallback(
147
+ (val: string, label: string) => {
148
+ handleItemClick({ value: val, label });
149
+ },
150
+ [handleItemClick]
151
+ );
152
+
153
+ const contextValue = React.useMemo(
154
+ () => ({
155
+ registerOption,
156
+ unregisterOption,
157
+ selectedValue: value,
158
+ onSelect,
159
+ }),
160
+ [registerOption, unregisterOption, value, onSelect]
161
+ );
162
+
163
+ const selectContent = (
164
+ <SelectContext.Provider value={contextValue}>
165
+ <div
166
+ className={`${selectClass} ${isOpen ? SELECT.CLASSES.IS_OPEN : ''}`}
167
+ ref={dropdownRef}
168
+ style={style}
169
+ aria-expanded={isOpen}
170
+ >
171
+ {/* Native select for accessibility and form submission */}
172
+ <select
173
+ ref={nativeSelectRef}
174
+ value={value}
175
+ onChange={onChange}
176
+ onBlur={onBlur}
177
+ onFocus={onFocus}
178
+ disabled={disabled}
179
+ required={required}
180
+ id={id}
181
+ name={name}
182
+ multiple={multiple}
183
+ aria-label={ariaLabel}
184
+ aria-describedby={ariaDescribedBy}
185
+ aria-invalid={invalid}
186
+ style={{ display: 'none' }}
187
+ >
188
+ {placeholder && (
189
+ <option value="" disabled>
190
+ {placeholder}
191
+ </option>
192
+ )}
193
+ {activeOptions.map(option => (
194
+ <option key={option.value} value={option.value} disabled={option.disabled}>
195
+ {option.label}
196
+ </option>
183
197
  ))}
184
- </ul>
198
+ </select>
199
+
200
+ {/* Custom Select UI */}
201
+ <div className={SELECT.CLASSES.SELECTED} onClick={handleToggle} aria-disabled={disabled}>
202
+ {selectedLabel}
203
+ </div>
204
+
205
+ <i className={`${SELECT.CLASSES.ICON_CARET} ${SELECT.CLASSES.TOGGLE_ICON}`} />
206
+
207
+ <div className={SELECT.CLASSES.SELECT_BODY} ref={bodyRef} style={{ height: 0 }}>
208
+ <div className={SELECT.CLASSES.SELECT_PANEL} ref={panelRef}>
209
+ <ul className={SELECT.CLASSES.SELECT_ITEMS}>
210
+ {hasOptionsProp ? (
211
+ options.map((option, index) => (
212
+ <li
213
+ key={option.value}
214
+ className={SELECT.CLASSES.SELECT_ITEM}
215
+ data-value={option.value}
216
+ onClick={() => !option.disabled && handleItemClick(option)}
217
+ >
218
+ <label htmlFor={`SelectItem${index}`} className="c-checkbox">
219
+ <input
220
+ type="checkbox"
221
+ id={`SelectItem${index}`}
222
+ className="c-checkbox__input c-select__item-input"
223
+ checked={value === option.value}
224
+ readOnly
225
+ disabled={option.disabled}
226
+ />
227
+ <div className="c-select__item-label">{option.label}</div>
228
+ </label>
229
+ </li>
230
+ ))
231
+ ) : (
232
+ children
233
+ )}
234
+ </ul>
235
+ </div>
236
+ </div>
185
237
  </div>
186
- </div>
187
- </div>
188
- );
189
-
190
- if (glass) {
191
- // Default glass settings for select components
192
- const defaultGlassProps = {
193
- displacementScale: 60,
194
- blurAmount: 1,
195
- saturation: 180,
196
- aberrationIntensity: 0.2,
197
- cornerRadius: 12,
198
- mode: 'shader' as const,
199
- };
238
+ </SelectContext.Provider>
239
+ );
200
240
 
201
- const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
241
+ if (glass) {
242
+ // Default glass settings for select components
243
+ const defaultGlassProps = {
244
+ displacementScale: 60,
245
+ blurAmount: 1,
246
+ saturation: 180,
247
+ aberrationIntensity: 0.2,
248
+ cornerRadius: 12,
249
+ mode: 'shader' as const,
250
+ };
202
251
 
203
- return <AtomixGlass {...glassProps}>{selectContent}</AtomixGlass>;
204
- }
252
+ const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
253
+
254
+ return <AtomixGlass {...glassProps}>{selectContent}</AtomixGlass>;
255
+ }
205
256
 
206
- return selectContent;
207
- });
257
+ return selectContent;
258
+ }
259
+ ) as unknown as SelectComponent;
208
260
 
209
261
  export type { SelectProps };
210
262
 
211
263
  Select.displayName = 'Select';
264
+ Select.Option = SelectOption;
212
265
 
213
266
  export default Select;
@@ -0,0 +1,88 @@
1
+ import React, { createContext, useContext, useEffect, memo, ReactNode } from 'react';
2
+ import { SelectOption as SelectOptionType } from '../../lib/types/components';
3
+ import { SELECT } from '../../lib/constants/components';
4
+
5
+ // Context for managing options registration and selection
6
+ export interface SelectContextType {
7
+ registerOption: (option: SelectOptionType) => void;
8
+ unregisterOption: (value: string) => void;
9
+ selectedValue?: string | string[];
10
+ onSelect: (value: string, label: string) => void;
11
+ }
12
+
13
+ export const SelectContext = createContext<SelectContextType | null>(null);
14
+
15
+ export interface SelectOptionProps {
16
+ value: string;
17
+ children?: ReactNode;
18
+ disabled?: boolean;
19
+ className?: string;
20
+ style?: React.CSSProperties;
21
+ }
22
+
23
+ export const SelectOption: React.FC<SelectOptionProps> = memo(
24
+ ({ value, children, disabled = false, className = '', style }) => {
25
+ const context = useContext(SelectContext);
26
+
27
+ // We assume children is the label if it's a string, or we need a way to get label.
28
+ // For simplicity, we use children as label for registration if it's a string.
29
+ const label = typeof children === 'string' ? children : value;
30
+
31
+ useEffect(() => {
32
+ if (context) {
33
+ context.registerOption({ value, label, disabled });
34
+ return () => {
35
+ context.unregisterOption(value);
36
+ };
37
+ }
38
+ return undefined;
39
+ }, [context, value, label, disabled]);
40
+
41
+ if (!context) {
42
+ console.warn('SelectOption must be used within a Select component');
43
+ return null;
44
+ }
45
+
46
+ const { selectedValue, onSelect } = context;
47
+
48
+ const isSelected = Array.isArray(selectedValue)
49
+ ? selectedValue.includes(value)
50
+ : selectedValue === value;
51
+
52
+ const handleClick = (e: React.MouseEvent) => {
53
+ e.preventDefault();
54
+ e.stopPropagation();
55
+ if (!disabled) {
56
+ onSelect(value, label);
57
+ }
58
+ };
59
+
60
+ return (
61
+ <li
62
+ className={`${SELECT.CLASSES.SELECT_ITEM} ${className}`.trim()}
63
+ data-value={value}
64
+ onClick={handleClick}
65
+ style={style}
66
+ role="option"
67
+ aria-selected={isSelected}
68
+ aria-disabled={disabled}
69
+ >
70
+ <label className="c-checkbox" style={{ pointerEvents: 'none' }}>
71
+ <input
72
+ type="checkbox"
73
+ className="c-checkbox__input c-select__item-input"
74
+ checked={isSelected}
75
+ readOnly
76
+ disabled={disabled}
77
+ tabIndex={-1}
78
+ />
79
+ <div className="c-select__item-label">{children}</div>
80
+ </label>
81
+ </li>
82
+ );
83
+ }
84
+ );
85
+
86
+ SelectOption.displayName = 'SelectOption';
87
+
88
+ export default SelectOption;
@@ -4,42 +4,37 @@ import { Textarea } from './Textarea';
4
4
 
5
5
  // Mock AtomixGlass component
6
6
  vi.mock('../AtomixGlass/AtomixGlass', () => ({
7
- AtomixGlass: ({ children, ...props }: any) => (
8
- <div data-testid="atomix-glass" data-glass-props={JSON.stringify(props)}>
9
- {children}
10
- </div>
11
- ),
7
+ AtomixGlass: ({ children, ...props }: any) => (
8
+ <div data-testid="atomix-glass" data-glass-props={JSON.stringify(props)}>
9
+ {children}
10
+ </div>
11
+ ),
12
12
  }));
13
13
 
14
14
  describe('Textarea Component', () => {
15
- it('renders correctly', () => {
16
- render(<Textarea placeholder="Test Textarea" />);
17
- expect(screen.getByPlaceholderText('Test Textarea')).toBeInTheDocument();
18
- });
15
+ it('renders correctly', () => {
16
+ render(<Textarea placeholder="Test Textarea" />);
17
+ expect(screen.getByPlaceholderText('Test Textarea')).toBeInTheDocument();
18
+ });
19
19
 
20
- it('handles uncontrolled defaultValue', () => {
21
- render(<Textarea defaultValue="Default Textarea Value" />);
22
- const textarea = screen.getByDisplayValue('Default Textarea Value');
23
- expect(textarea).toBeInTheDocument();
24
- expect(textarea).toHaveValue('Default Textarea Value');
25
- });
20
+ it('handles uncontrolled defaultValue', () => {
21
+ render(<Textarea defaultValue="Default Textarea Value" />);
22
+ const textarea = screen.getByDisplayValue('Default Textarea Value');
23
+ expect(textarea).toBeInTheDocument();
24
+ expect(textarea).toHaveValue('Default Textarea Value');
25
+ });
26
26
 
27
- it('calls onChange when typing', () => {
28
- const handleChange = vi.fn();
29
- render(<Textarea onChange={handleChange} />);
30
- const textarea = screen.getByRole('textbox');
31
- fireEvent.change(textarea, { target: { value: 'New Textarea Value' } });
32
- expect(handleChange).toHaveBeenCalledTimes(1);
33
- });
27
+ it('calls onChange when typing', () => {
28
+ const handleChange = vi.fn();
29
+ render(<Textarea onChange={handleChange} />);
30
+ const textarea = screen.getByRole('textbox');
31
+ fireEvent.change(textarea, { target: { value: 'New Textarea Value' } });
32
+ expect(handleChange).toHaveBeenCalledTimes(1);
33
+ });
34
34
 
35
- it('applies accessibility attributes', () => {
36
- render(
37
- <Textarea
38
- aria-label="Accessible Textarea"
39
- invalid
40
- />
41
- );
42
- const textarea = screen.getByLabelText('Accessible Textarea');
43
- expect(textarea).toHaveAttribute('aria-invalid', 'true');
44
- });
35
+ it('applies accessibility attributes', () => {
36
+ render(<Textarea aria-label="Accessible Textarea" invalid />);
37
+ const textarea = screen.getByLabelText('Accessible Textarea');
38
+ expect(textarea).toHaveAttribute('aria-invalid', 'true');
39
+ });
45
40
  });