@shohojdhara/atomix 0.4.0 → 0.4.2

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 (150) hide show
  1. package/dist/atomix.css +0 -14
  2. package/dist/atomix.css.map +1 -1
  3. package/dist/atomix.min.css +4 -4
  4. package/dist/atomix.min.css.map +1 -1
  5. package/dist/charts.d.ts +12 -19
  6. package/dist/charts.js +555 -359
  7. package/dist/charts.js.map +1 -1
  8. package/dist/core.d.ts +98 -28
  9. package/dist/core.js +1082 -733
  10. package/dist/core.js.map +1 -1
  11. package/dist/forms.d.ts +26 -21
  12. package/dist/forms.js +937 -350
  13. package/dist/forms.js.map +1 -1
  14. package/dist/heavy.d.ts +14 -21
  15. package/dist/heavy.js +409 -256
  16. package/dist/heavy.js.map +1 -1
  17. package/dist/index.d.ts +518 -284
  18. package/dist/index.esm.js +1993 -1237
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/index.js +1994 -1237
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.min.js +1 -1
  23. package/dist/index.min.js.map +1 -1
  24. package/package.json +2 -2
  25. package/scripts/atomix-cli.js +43 -1
  26. package/scripts/cli/__tests__/utils.test.js +6 -2
  27. package/scripts/cli/migration-tools.js +2 -2
  28. package/scripts/cli/theme-bridge.js +7 -9
  29. package/scripts/cli/utils.js +2 -1
  30. package/src/components/Accordion/Accordion.stories.tsx +40 -0
  31. package/src/components/Accordion/Accordion.tsx +174 -56
  32. package/src/components/Accordion/AccordionCompound.test.tsx +70 -0
  33. package/src/components/AtomixGlass/AtomixGlass.tsx +82 -54
  34. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +17 -18
  35. package/src/components/AtomixGlass/README.md +5 -5
  36. package/src/components/AtomixGlass/stories/Customization.stories.tsx +2 -2
  37. package/src/components/AtomixGlass/stories/Examples.stories.tsx +42 -42
  38. package/src/components/AtomixGlass/stories/Modes.stories.tsx +5 -5
  39. package/src/components/AtomixGlass/stories/Overview.stories.tsx +3 -3
  40. package/src/components/AtomixGlass/stories/Performance.stories.tsx +2 -2
  41. package/src/components/AtomixGlass/stories/Playground.stories.tsx +45 -45
  42. package/src/components/AtomixGlass/stories/Shaders.stories.tsx +3 -3
  43. package/src/components/Badge/Badge.stories.tsx +1 -1
  44. package/src/components/Badge/Badge.tsx +1 -1
  45. package/src/components/Breadcrumb/Breadcrumb.tsx +185 -65
  46. package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +84 -0
  47. package/src/components/Breadcrumb/index.ts +2 -2
  48. package/src/components/Button/Button.stories.tsx +1 -1
  49. package/src/components/Button/README.md +2 -2
  50. package/src/components/Callout/Callout.stories.tsx +166 -1011
  51. package/src/components/Callout/Callout.test.tsx +3 -3
  52. package/src/components/Callout/Callout.tsx +196 -84
  53. package/src/components/Callout/CalloutCompound.test.tsx +72 -0
  54. package/src/components/Callout/README.md +2 -2
  55. package/src/components/Chart/Chart.stories.tsx +1 -1
  56. package/src/components/Chart/Chart.tsx +5 -5
  57. package/src/components/Chart/TreemapChart.tsx +37 -29
  58. package/src/components/DatePicker/readme.md +3 -3
  59. package/src/components/Dropdown/Dropdown.stories.tsx +1 -1
  60. package/src/components/Dropdown/Dropdown.tsx +133 -20
  61. package/src/components/Dropdown/DropdownCompound.test.tsx +64 -0
  62. package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
  63. package/src/components/EdgePanel/EdgePanel.tsx +164 -112
  64. package/src/components/EdgePanel/EdgePanelCompound.test.tsx +53 -0
  65. package/src/components/Form/Checkbox.stories.tsx +1 -1
  66. package/src/components/Form/Checkbox.tsx +1 -1
  67. package/src/components/Form/Input.stories.tsx +1 -1
  68. package/src/components/Form/Input.tsx +1 -1
  69. package/src/components/Form/Radio.stories.tsx +1 -1
  70. package/src/components/Form/Radio.tsx +1 -1
  71. package/src/components/Form/Select.stories.tsx +24 -1
  72. package/src/components/Form/Select.test.tsx +99 -0
  73. package/src/components/Form/Select.tsx +145 -94
  74. package/src/components/Form/SelectOption.tsx +88 -0
  75. package/src/components/Form/Textarea.stories.tsx +1 -1
  76. package/src/components/Form/Textarea.tsx +1 -1
  77. package/src/components/Hero/Hero.stories.tsx +39 -2
  78. package/src/components/Hero/Hero.test.tsx +142 -0
  79. package/src/components/Hero/Hero.tsx +143 -4
  80. package/src/components/List/List.test.tsx +62 -0
  81. package/src/components/List/List.tsx +16 -5
  82. package/src/components/List/ListItem.tsx +20 -0
  83. package/src/components/Messages/Messages.stories.tsx +1 -1
  84. package/src/components/Messages/Messages.tsx +2 -2
  85. package/src/components/Modal/Modal.stories.tsx +66 -2
  86. package/src/components/Modal/Modal.tsx +115 -35
  87. package/src/components/Modal/ModalCompound.test.tsx +94 -0
  88. package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
  89. package/src/components/Navigation/Nav/Nav.tsx +1 -1
  90. package/src/components/Navigation/Navbar/Navbar.stories.tsx +3 -3
  91. package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
  92. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +2 -2
  93. package/src/components/Navigation/SideMenu/SideMenu.tsx +1 -1
  94. package/src/components/Pagination/Pagination.stories.tsx +1 -1
  95. package/src/components/Pagination/Pagination.tsx +1 -1
  96. package/src/components/Popover/Popover.stories.tsx +1 -1
  97. package/src/components/Popover/Popover.tsx +1 -1
  98. package/src/components/Progress/Progress.tsx +1 -1
  99. package/src/components/Rating/Rating.stories.tsx +1 -1
  100. package/src/components/Rating/Rating.test.tsx +73 -0
  101. package/src/components/Rating/Rating.tsx +25 -37
  102. package/src/components/Spinner/Spinner.tsx +1 -1
  103. package/src/components/Steps/Steps.stories.tsx +1 -1
  104. package/src/components/Steps/Steps.tsx +125 -22
  105. package/src/components/Steps/StepsCompound.test.tsx +81 -0
  106. package/src/components/Tabs/Tabs.stories.tsx +1 -1
  107. package/src/components/Tabs/Tabs.tsx +198 -45
  108. package/src/components/Tabs/TabsCompound.test.tsx +64 -0
  109. package/src/components/Todo/Todo.tsx +0 -1
  110. package/src/components/Toggle/Toggle.stories.tsx +1 -1
  111. package/src/components/Toggle/Toggle.tsx +1 -1
  112. package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
  113. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +2 -2
  114. package/src/lib/composables/__tests__/useAtomixGlassPerf.test.tsx +88 -0
  115. package/src/lib/composables/__tests__/useChart.test.ts +50 -0
  116. package/src/lib/composables/__tests__/useChart.test.tsx +139 -0
  117. package/src/lib/composables/__tests__/useHeroBackgroundSlider.test.tsx +59 -0
  118. package/src/lib/composables/__tests__/useSliderAutoplay.test.tsx +68 -0
  119. package/src/lib/composables/atomix-glass/useGlassBackgroundDetection.ts +329 -0
  120. package/src/lib/composables/atomix-glass/useGlassCornerRadius.ts +82 -0
  121. package/src/lib/composables/atomix-glass/useGlassMouseTracking.ts +153 -0
  122. package/src/lib/composables/atomix-glass/useGlassOverLight.ts +198 -0
  123. package/src/lib/composables/atomix-glass/useGlassSize.ts +117 -0
  124. package/src/lib/composables/atomix-glass/useGlassState.ts +112 -0
  125. package/src/lib/composables/atomix-glass/useGlassTransforms.ts +160 -0
  126. package/src/lib/composables/glass-styles.ts +302 -0
  127. package/src/lib/composables/index.ts +0 -8
  128. package/src/lib/composables/useAtomixGlass.ts +331 -537
  129. package/src/lib/composables/useAtomixGlassStyles.ts +307 -0
  130. package/src/lib/composables/useBarChart.ts +1 -1
  131. package/src/lib/composables/useBreadcrumb.ts +6 -6
  132. package/src/lib/composables/useChart.ts +104 -21
  133. package/src/lib/composables/useHeroBackgroundSlider.ts +16 -7
  134. package/src/lib/composables/useSlider.ts +66 -34
  135. package/src/lib/theme/devtools/CLI.ts +2 -10
  136. package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
  137. package/src/lib/types/components.ts +21 -23
  138. package/src/lib/utils/__tests__/componentUtils.test.ts +57 -2
  139. package/src/lib/utils/__tests__/dom.test.ts +100 -0
  140. package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
  141. package/src/lib/utils/__tests__/themeNaming.test.ts +117 -0
  142. package/src/lib/utils/themeNaming.ts +1 -1
  143. package/src/styles/06-components/_components.accordion.scss +0 -2
  144. package/src/styles/06-components/_components.chart.scss +0 -1
  145. package/src/styles/06-components/_components.dropdown.scss +0 -1
  146. package/src/styles/06-components/_components.edge-panel.scss +0 -2
  147. package/src/styles/06-components/_components.photoviewer.scss +0 -1
  148. package/src/styles/06-components/_components.river.scss +0 -1
  149. package/src/styles/06-components/_components.slider.scss +0 -3
  150. package/src/styles/99-utilities/_utilities.glass-fixes.scss +0 -1
@@ -0,0 +1,53 @@
1
+ import { render, screen, fireEvent } from '@testing-library/react';
2
+ import { describe, it, expect, vi } from 'vitest';
3
+ import { EdgePanel } from './EdgePanel';
4
+ import React from 'react';
5
+
6
+ describe('EdgePanel Component', () => {
7
+ it('renders correctly with legacy props', () => {
8
+ render(
9
+ <EdgePanel isOpen={true} title="Legacy Title">
10
+ Legacy Content
11
+ </EdgePanel>
12
+ );
13
+
14
+ expect(screen.getByText('Legacy Title')).toBeInTheDocument();
15
+ expect(screen.getByText('Legacy Content')).toBeInTheDocument();
16
+ });
17
+
18
+ it('renders correctly with compound components', () => {
19
+ render(
20
+ <EdgePanel isOpen={true}>
21
+ <EdgePanel.Header>
22
+ <h4>Compound Title</h4>
23
+ </EdgePanel.Header>
24
+ <EdgePanel.Body>
25
+ Compound Content
26
+ </EdgePanel.Body>
27
+ <EdgePanel.Footer>
28
+ Footer
29
+ </EdgePanel.Footer>
30
+ </EdgePanel>
31
+ );
32
+
33
+ expect(screen.getByText('Compound Title')).toBeInTheDocument();
34
+ expect(screen.getByText('Compound Content')).toBeInTheDocument();
35
+ expect(screen.getByText('Footer')).toBeInTheDocument();
36
+ });
37
+
38
+ it('uses close button in compound mode', () => {
39
+ const onClose = vi.fn();
40
+ render(
41
+ <EdgePanel isOpen={true} onOpenChange={onClose}>
42
+ <EdgePanel.Header>
43
+ <EdgePanel.CloseButton onClick={() => onClose(false)} />
44
+ </EdgePanel.Header>
45
+ <EdgePanel.Body>Content</EdgePanel.Body>
46
+ </EdgePanel>
47
+ );
48
+
49
+ const closeBtn = screen.getByLabelText('Close panel');
50
+ fireEvent.click(closeBtn);
51
+ expect(onClose).toHaveBeenCalledWith(false);
52
+ });
53
+ });
@@ -252,7 +252,7 @@ export const GlassCustom: Story = {
252
252
  blurAmount: 2,
253
253
  saturation: 200,
254
254
  aberrationIntensity: 0.8,
255
- cornerRadius: 12,
255
+ borderRadius: 12,
256
256
  },
257
257
  },
258
258
  render: (args: any) => (
@@ -117,7 +117,7 @@ export const Checkbox = React.memo(
117
117
  blurAmount: 1,
118
118
  saturation: 160,
119
119
  aberrationIntensity: 0.3,
120
- cornerRadius: 6,
120
+ borderRadius: 6,
121
121
  mode: 'shader' as const,
122
122
  };
123
123
  const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
@@ -432,7 +432,7 @@ export const GlassVariants: Story = {
432
432
  blurAmount: 2,
433
433
  saturation: 200,
434
434
  aberrationIntensity: 2,
435
- cornerRadius: 12,
435
+ borderRadius: 12,
436
436
  }}
437
437
  />
438
438
  </div>
@@ -101,7 +101,7 @@ export const Input = memo(
101
101
  blurAmount: 1,
102
102
  saturation: 180,
103
103
  aberrationIntensity: 0.2,
104
- cornerRadius: 12,
104
+ borderRadius: 12,
105
105
  mode: 'shader' as const,
106
106
  };
107
107
 
@@ -333,7 +333,7 @@ export const GlassCustom: Story = {
333
333
  blurAmount: 2,
334
334
  saturation: 200,
335
335
  aberrationIntensity: 0.8,
336
- cornerRadius: 12,
336
+ borderRadius: 12,
337
337
  } as any,
338
338
  onChange: () => {},
339
339
  },
@@ -68,7 +68,7 @@ export const Radio: React.FC<RadioProps> = memo(
68
68
  blurAmount: 1,
69
69
  saturation: 160,
70
70
  aberrationIntensity: 0.3,
71
- cornerRadius: 6,
71
+ borderRadius: 6,
72
72
  mode: 'shader' as const,
73
73
  };
74
74
 
@@ -256,6 +256,29 @@ export const Sizes: Story = {
256
256
  ),
257
257
  };
258
258
 
259
+ // Compound usage
260
+ export const Compound: Story = {
261
+ render: () => (
262
+ <div style={{ width: '300px' }}>
263
+ <Select placeholder="Select a framework">
264
+ <Select.Option value="react">React</Select.Option>
265
+ <Select.Option value="vue">Vue</Select.Option>
266
+ <Select.Option value="angular">Angular</Select.Option>
267
+ <Select.Option value="svelte" disabled>
268
+ Svelte (Disabled)
269
+ </Select.Option>
270
+ </Select>
271
+ </div>
272
+ ),
273
+ parameters: {
274
+ docs: {
275
+ description: {
276
+ story: 'Select component using Compound Component Pattern.',
277
+ },
278
+ },
279
+ },
280
+ };
281
+
259
282
  // Select states
260
283
  export const States: Story = {
261
284
  args: {
@@ -330,7 +353,7 @@ export const GlassCustom: Story = {
330
353
  blurAmount: 2,
331
354
  saturation: 200,
332
355
  aberrationIntensity: 0.8,
333
- cornerRadius: 12,
356
+ borderRadius: 12,
334
357
  },
335
358
  },
336
359
  render: (args: any) => (
@@ -0,0 +1,99 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3
+ import { describe, it, expect, vi } from 'vitest';
4
+ import { Select } from './Select';
5
+
6
+ describe('Select Component', () => {
7
+ it('renders legacy options correctly', () => {
8
+ const options = [
9
+ { value: '1', label: 'Option 1' },
10
+ { value: '2', label: 'Option 2' },
11
+ ];
12
+ render(<Select options={options} value="" onChange={() => {}} />);
13
+
14
+ // Check custom UI items
15
+ const items = document.querySelectorAll('.c-select__item');
16
+ expect(items).toHaveLength(2);
17
+ expect(items[0]).toHaveTextContent('Option 1');
18
+ expect(items[1]).toHaveTextContent('Option 2');
19
+
20
+ // Check native select options
21
+ const select = document.querySelector('select');
22
+ expect(select).not.toBeNull();
23
+ expect(select?.options).toHaveLength(3); // Placeholder + 2
24
+ expect(select?.options[1].value).toBe('1');
25
+ expect(select?.options[2].value).toBe('2');
26
+ });
27
+
28
+ it('renders compound options correctly', async () => {
29
+ render(
30
+ <Select value="" onChange={() => {}}>
31
+ <Select.Option value="1">Compound Option 1</Select.Option>
32
+ <Select.Option value="2">Compound Option 2</Select.Option>
33
+ </Select>
34
+ );
35
+
36
+ // Check custom UI items
37
+ const items = document.querySelectorAll('.c-select__item');
38
+ expect(items).toHaveLength(2);
39
+ expect(items[0]).toHaveTextContent('Compound Option 1');
40
+ expect(items[1]).toHaveTextContent('Compound Option 2');
41
+
42
+ // Check native select options
43
+ await waitFor(() => {
44
+ const select = document.querySelector('select');
45
+ expect(select).not.toBeNull();
46
+ expect(select?.options).toHaveLength(3); // Placeholder + 2
47
+ expect(select?.options[1].value).toBe('1');
48
+ expect(select?.options[2].value).toBe('2');
49
+ });
50
+ });
51
+
52
+ it('handles selection in legacy mode', () => {
53
+ const handleChange = vi.fn();
54
+ const options = [
55
+ { value: '1', label: 'Option 1' },
56
+ { value: '2', label: 'Option 2' },
57
+ ];
58
+ render(<Select options={options} value="" onChange={handleChange} />);
59
+
60
+ // Open dropdown
61
+ const trigger = document.querySelector('.c-select__selected');
62
+ fireEvent.click(trigger!);
63
+
64
+ // Click item
65
+ const item = document.querySelector('.c-select__item[data-value="1"]');
66
+ fireEvent.click(item!);
67
+
68
+ expect(handleChange).toHaveBeenCalled();
69
+ // Check event value
70
+ expect(handleChange.mock.calls[0][0].target.value).toBe('1');
71
+ });
72
+
73
+ it('handles selection in compound mode', async () => {
74
+ const handleChange = vi.fn();
75
+ render(
76
+ <Select value="" onChange={handleChange}>
77
+ <Select.Option value="1">Option 1</Select.Option>
78
+ <Select.Option value="2">Option 2</Select.Option>
79
+ </Select>
80
+ );
81
+
82
+ // Wait for options to be registered
83
+ await waitFor(() => {
84
+ const select = document.querySelector('select');
85
+ expect(select?.options).toHaveLength(3);
86
+ });
87
+
88
+ // Open dropdown
89
+ const trigger = document.querySelector('.c-select__selected');
90
+ fireEvent.click(trigger!);
91
+
92
+ // Click item
93
+ const item = document.querySelector('.c-select__item[data-value="2"]');
94
+ fireEvent.click(item!);
95
+
96
+ expect(handleChange).toHaveBeenCalled();
97
+ expect(handleChange.mock.calls[0][0].target.value).toBe('2');
98
+ });
99
+ });
@@ -1,15 +1,20 @@
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(
15
+ export const Select: SelectComponent = memo(
11
16
  ({
12
- options = [],
17
+ options,
13
18
  value,
14
19
  onChange,
15
20
  onBlur,
@@ -28,7 +33,8 @@ export const Select: React.FC<SelectProps> = memo(
28
33
  'aria-label': ariaLabel,
29
34
  'aria-describedby': ariaDescribedBy,
30
35
  glass,
31
- }) => {
36
+ children,
37
+ }: SelectProps) => {
32
38
  const { generateSelectClass } = useSelect({
33
39
  size,
34
40
  disabled,
@@ -51,17 +57,35 @@ export const Select: React.FC<SelectProps> = memo(
51
57
  const bodyRef = useRef<HTMLDivElement>(null);
52
58
  const nativeSelectRef = useRef<HTMLSelectElement>(null);
53
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
+
54
78
  // Update selected label when value changes
55
79
  useEffect(() => {
56
80
  if (value) {
57
- const selectedOption = options.find(opt => opt.value === value);
81
+ const selectedOption = activeOptions.find(opt => opt.value === value);
58
82
  if (selectedOption) {
59
83
  setSelectedLabel(selectedOption.label);
60
84
  }
61
85
  } else {
62
86
  setSelectedLabel(placeholder);
63
87
  }
64
- }, [value, options, placeholder]);
88
+ }, [value, activeOptions, placeholder]);
65
89
 
66
90
  // Handle click outside to close dropdown
67
91
  useEffect(() => {
@@ -93,99 +117,125 @@ export const Select: React.FC<SelectProps> = memo(
93
117
  };
94
118
 
95
119
  // Handle item selection
96
- const handleItemClick = (option: { value: string; label: string }) => {
97
- setSelectedLabel(option.label);
98
- setIsOpen(false);
99
- if (bodyRef.current) {
100
- bodyRef.current.style.height = '0px';
101
- }
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
+ }
102
127
 
103
- if (nativeSelectRef.current) {
104
- nativeSelectRef.current.value = option.value;
105
- }
128
+ if (nativeSelectRef.current) {
129
+ nativeSelectRef.current.value = option.value;
130
+ }
106
131
 
107
- if (onChange) {
108
- // Create a synthetic event
109
- const event = {
110
- target: {
111
- name,
112
- value: option.value,
113
- },
114
- } as React.ChangeEvent<HTMLSelectElement>;
115
- onChange(event);
116
- }
117
- };
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
+ );
118
162
 
119
163
  const selectContent = (
120
- <div
121
- className={`${selectClass} ${isOpen ? SELECT.CLASSES.IS_OPEN : ''}`}
122
- ref={dropdownRef}
123
- style={style}
124
- aria-expanded={isOpen}
125
- >
126
- {/* Native select for accessibility and form submission */}
127
- <select
128
- ref={nativeSelectRef}
129
- value={value}
130
- onChange={onChange}
131
- onBlur={onBlur}
132
- onFocus={onFocus}
133
- disabled={disabled}
134
- required={required}
135
- id={id}
136
- name={name}
137
- multiple={multiple}
138
- aria-label={ariaLabel}
139
- aria-describedby={ariaDescribedBy}
140
- aria-invalid={invalid}
141
- style={{ display: 'none' }}
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}
142
170
  >
143
- {placeholder && (
144
- <option value="" disabled>
145
- {placeholder}
146
- </option>
147
- )}
148
- {options.map(option => (
149
- <option key={option.value} value={option.value} disabled={option.disabled}>
150
- {option.label}
151
- </option>
152
- ))}
153
- </select>
154
-
155
- {/* Custom Select UI */}
156
- <div className={SELECT.CLASSES.SELECTED} onClick={handleToggle} aria-disabled={disabled}>
157
- {selectedLabel}
158
- </div>
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>
197
+ ))}
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}`} />
159
206
 
160
- <i className={`${SELECT.CLASSES.ICON_CARET} ${SELECT.CLASSES.TOGGLE_ICON}`} />
161
-
162
- <div className={SELECT.CLASSES.SELECT_BODY} ref={bodyRef} style={{ height: 0 }}>
163
- <div className={SELECT.CLASSES.SELECT_PANEL} ref={panelRef}>
164
- <ul className={SELECT.CLASSES.SELECT_ITEMS}>
165
- {options.map((option, index) => (
166
- <li
167
- key={option.value}
168
- className={SELECT.CLASSES.SELECT_ITEM}
169
- data-value={option.value}
170
- onClick={() => !option.disabled && handleItemClick(option)}
171
- >
172
- <label htmlFor={`SelectItem${index}`} className="c-checkbox">
173
- <input
174
- type="checkbox"
175
- id={`SelectItem${index}`}
176
- className="c-checkbox__input c-select__item-input"
177
- checked={value === option.value}
178
- readOnly
179
- disabled={option.disabled}
180
- />
181
- <div className="c-select__item-label">{option.label}</div>
182
- </label>
183
- </li>
184
- ))}
185
- </ul>
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>
186
236
  </div>
187
237
  </div>
188
- </div>
238
+ </SelectContext.Provider>
189
239
  );
190
240
 
191
241
  if (glass) {
@@ -195,7 +245,7 @@ export const Select: React.FC<SelectProps> = memo(
195
245
  blurAmount: 1,
196
246
  saturation: 180,
197
247
  aberrationIntensity: 0.2,
198
- cornerRadius: 12,
248
+ borderRadius: 12,
199
249
  mode: 'shader' as const,
200
250
  };
201
251
 
@@ -206,10 +256,11 @@ export const Select: React.FC<SelectProps> = memo(
206
256
 
207
257
  return selectContent;
208
258
  }
209
- );
259
+ ) as unknown as SelectComponent;
210
260
 
211
261
  export type { SelectProps };
212
262
 
213
263
  Select.displayName = 'Select';
264
+ Select.Option = SelectOption;
214
265
 
215
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;
@@ -343,7 +343,7 @@ export const GlassVariants: Story = {
343
343
  blurAmount: 2,
344
344
  saturation: 200,
345
345
  aberrationIntensity: 2,
346
- cornerRadius: 12,
346
+ borderRadius: 12,
347
347
  }}
348
348
  />
349
349
  </div>
@@ -92,7 +92,7 @@ export const Textarea = memo(
92
92
  blurAmount: 1,
93
93
  saturation: 180,
94
94
  aberrationIntensity: 1,
95
- cornerRadius: 8,
95
+ borderRadius: 8,
96
96
  mode: 'shader' as const,
97
97
  };
98
98