@shohojdhara/atomix 0.2.4 → 0.2.6

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 (214) hide show
  1. package/README.md +19 -0
  2. package/dist/atomix.css +1300 -1418
  3. package/dist/atomix.min.css +3 -3
  4. package/dist/index.d.ts +1259 -874
  5. package/dist/index.esm.js +16256 -26366
  6. package/dist/index.esm.js.map +1 -1
  7. package/dist/index.js +15691 -22295
  8. package/dist/index.js.map +1 -1
  9. package/dist/index.min.js +1 -1
  10. package/dist/index.min.js.map +1 -1
  11. package/dist/themes/applemix.css +15036 -0
  12. package/dist/themes/applemix.min.css +72 -0
  13. package/dist/themes/boomdevs.css +1300 -1419
  14. package/dist/themes/boomdevs.min.css +3 -3
  15. package/dist/themes/esrar.css +1301 -1419
  16. package/dist/themes/esrar.min.css +3 -3
  17. package/dist/themes/flashtrade.css +15187 -0
  18. package/dist/themes/flashtrade.min.css +86 -0
  19. package/dist/themes/mashroom.css +1299 -1417
  20. package/dist/themes/mashroom.min.css +5 -5
  21. package/dist/themes/shaj-default.css +1300 -1418
  22. package/dist/themes/shaj-default.min.css +3 -3
  23. package/package.json +6 -17
  24. package/src/components/Accordion/Accordion.stories.tsx +4 -26
  25. package/src/components/Accordion/Accordion.tsx +21 -12
  26. package/src/components/AtomixGlass/AtomixGlass.test.tsx +106 -72
  27. package/src/components/AtomixGlass/AtomixGlass.tsx +485 -1215
  28. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +399 -0
  29. package/src/components/AtomixGlass/GlassFilter.tsx +156 -0
  30. package/src/components/AtomixGlass/README.md +124 -2
  31. package/src/components/AtomixGlass/atomixGLass.old.tsx +1266 -0
  32. package/src/components/AtomixGlass/glass-utils.ts +263 -0
  33. package/src/components/AtomixGlass/shader-utils.ts +404 -236
  34. package/src/components/AtomixGlass/{AtomixGlass.stories.tsx → stories/AtomixGlass.stories.tsx} +55 -35
  35. package/src/components/AtomixGlass/stories/Examples.stories.tsx +57 -89
  36. package/src/components/AtomixGlass/stories/Modes.stories.tsx +149 -149
  37. package/src/components/AtomixGlass/stories/Playground.stories.tsx +95 -32
  38. package/src/components/AtomixGlass/stories/ShaderVariants.stories.tsx +0 -2
  39. package/src/components/AtomixGlass/stories/shared-components.tsx +9 -18
  40. package/src/components/AtomixGlass/utils.ts +3 -3
  41. package/src/components/Avatar/Avatar.tsx +3 -0
  42. package/src/components/Avatar/AvatarGroup.tsx +2 -1
  43. package/src/components/Badge/Badge.stories.tsx +74 -54
  44. package/src/components/Badge/Badge.tsx +8 -12
  45. package/src/components/Breadcrumb/Breadcrumb.tsx +23 -4
  46. package/src/components/Button/Button.tsx +3 -5
  47. package/src/components/Callout/Callout.stories.tsx +86 -35
  48. package/src/components/Callout/Callout.tsx +4 -0
  49. package/src/components/Card/Card.stories.tsx +89 -85
  50. package/src/components/Card/Card.tsx +15 -4
  51. package/src/components/Card/ElevationCard.tsx +2 -0
  52. package/src/components/Chart/AnimatedChart.tsx +179 -156
  53. package/src/components/Chart/AreaChart.tsx +123 -12
  54. package/src/components/Chart/BarChart.tsx +91 -100
  55. package/src/components/Chart/BaseChart.tsx +80 -0
  56. package/src/components/Chart/BubbleChart.tsx +114 -290
  57. package/src/components/Chart/CandlestickChart.tsx +282 -622
  58. package/src/components/Chart/Chart.stories.tsx +576 -179
  59. package/src/components/Chart/Chart.tsx +374 -75
  60. package/src/components/Chart/ChartRenderer.tsx +371 -220
  61. package/src/components/Chart/ChartToolbar.tsx +372 -61
  62. package/src/components/Chart/ChartTooltip.tsx +33 -18
  63. package/src/components/Chart/DonutChart.tsx +172 -254
  64. package/src/components/Chart/FunnelChart.tsx +169 -240
  65. package/src/components/Chart/GaugeChart.tsx +224 -392
  66. package/src/components/Chart/HeatmapChart.tsx +302 -440
  67. package/src/components/Chart/LineChart.tsx +148 -103
  68. package/src/components/Chart/MultiAxisChart.tsx +267 -395
  69. package/src/components/Chart/PieChart.tsx +114 -64
  70. package/src/components/Chart/RadarChart.tsx +202 -218
  71. package/src/components/Chart/ScatterChart.tsx +111 -97
  72. package/src/components/Chart/TreemapChart.tsx +147 -222
  73. package/src/components/Chart/WaterfallChart.tsx +253 -291
  74. package/src/components/Chart/index.ts +11 -9
  75. package/src/components/Chart/types.ts +85 -9
  76. package/src/components/Chart/utils.ts +66 -0
  77. package/src/components/ColorModeToggle/ColorModeToggle.stories.tsx +121 -11
  78. package/src/components/ColorModeToggle/ColorModeToggle.tsx +149 -45
  79. package/src/components/ColorModeToggle/index.ts +1 -1
  80. package/src/components/Countdown/Countdown.tsx +4 -0
  81. package/src/components/DataTable/DataTable.tsx +2 -1
  82. package/src/components/DatePicker/DatePicker.stories.tsx +0 -11
  83. package/src/components/DatePicker/DatePicker.tsx +3 -9
  84. package/src/components/DatePicker/types.ts +5 -0
  85. package/src/components/Dropdown/Dropdown.stories.tsx +32 -25
  86. package/src/components/Dropdown/Dropdown.tsx +26 -28
  87. package/src/components/EdgePanel/EdgePanel.stories.tsx +13 -15
  88. package/src/components/EdgePanel/EdgePanel.tsx +20 -5
  89. package/src/components/Footer/Footer.stories.tsx +187 -60
  90. package/src/components/Footer/Footer.test.tsx +134 -0
  91. package/src/components/Footer/Footer.tsx +133 -34
  92. package/src/components/Footer/FooterLink.tsx +1 -1
  93. package/src/components/Footer/FooterSection.tsx +53 -36
  94. package/src/components/Footer/FooterSocialLink.tsx +32 -29
  95. package/src/components/Footer/README.md +82 -3
  96. package/src/components/Footer/index.ts +1 -1
  97. package/src/components/Form/Checkbox.stories.tsx +13 -5
  98. package/src/components/Form/Checkbox.tsx +3 -6
  99. package/src/components/Form/Form.stories.tsx +10 -3
  100. package/src/components/Form/Form.tsx +2 -0
  101. package/src/components/Form/FormGroup.tsx +2 -1
  102. package/src/components/Form/Input.stories.tsx +12 -11
  103. package/src/components/Form/Input.tsx +97 -95
  104. package/src/components/Form/Radio.stories.tsx +22 -7
  105. package/src/components/Form/Radio.tsx +3 -6
  106. package/src/components/Form/Select.stories.tsx +21 -6
  107. package/src/components/Form/Select.tsx +3 -5
  108. package/src/components/Form/Textarea.stories.tsx +13 -11
  109. package/src/components/Form/Textarea.tsx +88 -86
  110. package/src/components/Hero/Hero.stories.tsx +2 -3
  111. package/src/components/Hero/Hero.tsx +5 -6
  112. package/src/components/Icon/Icon.tsx +12 -1
  113. package/src/components/List/List.tsx +2 -1
  114. package/src/components/List/ListGroup.tsx +2 -1
  115. package/src/components/Messages/Messages.tsx +3 -2
  116. package/src/components/Modal/Modal.stories.tsx +48 -34
  117. package/src/components/Modal/Modal.tsx +19 -23
  118. package/src/components/Navigation/Menu/MegaMenu.tsx +2 -2
  119. package/src/components/Navigation/Menu/Menu.tsx +2 -2
  120. package/src/components/Navigation/Nav/Nav.tsx +6 -1
  121. package/src/components/Navigation/Nav/NavDropdown.tsx +10 -1
  122. package/src/components/Navigation/Navbar/Navbar.tsx +4 -1
  123. package/src/components/Navigation/SideMenu/SideMenu.tsx +3 -2
  124. package/src/components/Pagination/Pagination.stories.tsx +13 -6
  125. package/src/components/Pagination/Pagination.tsx +7 -6
  126. package/src/components/PhotoViewer/PhotoViewer.tsx +2 -1
  127. package/src/components/Popover/Popover.stories.tsx +32 -24
  128. package/src/components/Popover/Popover.tsx +4 -1
  129. package/src/components/ProductReview/ProductReview.tsx +8 -2
  130. package/src/components/Progress/Progress.tsx +2 -1
  131. package/src/components/Rating/Rating.stories.tsx +11 -6
  132. package/src/components/Rating/Rating.tsx +3 -5
  133. package/src/components/River/River.tsx +5 -5
  134. package/src/components/SectionIntro/SectionIntro.tsx +8 -2
  135. package/src/components/Slider/Slider.stories.tsx +4 -4
  136. package/src/components/Slider/Slider.tsx +4 -3
  137. package/src/components/Spinner/Spinner.tsx +2 -1
  138. package/src/components/Steps/Steps.stories.tsx +5 -4
  139. package/src/components/Steps/Steps.tsx +8 -5
  140. package/src/components/Tab/Tab.stories.tsx +4 -3
  141. package/src/components/Tab/Tab.tsx +8 -6
  142. package/src/components/Testimonial/Testimonial.tsx +8 -2
  143. package/src/components/Todo/Todo.tsx +2 -1
  144. package/src/components/Toggle/Toggle.stories.tsx +5 -4
  145. package/src/components/Toggle/Toggle.tsx +8 -5
  146. package/src/components/Tooltip/Tooltip.stories.tsx +40 -30
  147. package/src/components/Tooltip/Tooltip.tsx +9 -2
  148. package/src/components/Upload/Upload.stories.tsx +252 -0
  149. package/src/components/Upload/Upload.tsx +92 -53
  150. package/src/components/VideoPlayer/VideoPlayer.tsx +3 -1
  151. package/src/components/index.ts +0 -4
  152. package/src/layouts/Grid/Grid.stories.tsx +10 -23
  153. package/src/layouts/Grid/Grid.tsx +20 -1
  154. package/src/layouts/Grid/GridCol.tsx +76 -48
  155. package/src/lib/composables/useAtomixGlass.ts +861 -44
  156. package/src/lib/composables/useBarChart.ts +13 -6
  157. package/src/lib/composables/useChart.ts +17 -13
  158. package/src/lib/composables/useChartExport.ts +19 -78
  159. package/src/lib/composables/useChartToolbar.ts +0 -1
  160. package/src/lib/composables/useEdgePanel.ts +111 -103
  161. package/src/lib/composables/useFooter.ts +3 -3
  162. package/src/lib/composables/useGlassContainer.ts +16 -7
  163. package/src/lib/composables/useLineChart.ts +8 -1
  164. package/src/lib/composables/useRiver.ts +5 -0
  165. package/src/lib/composables/useSlider.ts +62 -24
  166. package/src/lib/composables/useVideoPlayer.ts +60 -63
  167. package/src/lib/constants/components.ts +146 -32
  168. package/src/lib/types/components.ts +258 -10
  169. package/src/lib/utils/displacement-generator.ts +55 -49
  170. package/src/lib/utils/icons.ts +1 -1
  171. package/src/lib/utils/index.ts +16 -10
  172. package/src/styles/01-settings/_settings.accordion.scss +19 -19
  173. package/src/styles/01-settings/_settings.animations.scss +5 -5
  174. package/src/styles/01-settings/_settings.avatar-group.scss +1 -1
  175. package/src/styles/01-settings/_settings.avatar.scss +17 -17
  176. package/src/styles/01-settings/_settings.background.scss +1 -4
  177. package/src/styles/01-settings/_settings.badge.scss +1 -1
  178. package/src/styles/01-settings/_settings.breadcrumb.scss +1 -1
  179. package/src/styles/01-settings/_settings.card.scss +1 -1
  180. package/src/styles/01-settings/_settings.chart.scss +65 -2
  181. package/src/styles/01-settings/_settings.dropdown.scss +1 -1
  182. package/src/styles/01-settings/_settings.footer.scss +35 -42
  183. package/src/styles/01-settings/_settings.input.scss +1 -1
  184. package/src/styles/01-settings/_settings.list.scss +1 -1
  185. package/src/styles/01-settings/_settings.rating.scss +1 -1
  186. package/src/styles/01-settings/_settings.tabs.scss +1 -1
  187. package/src/styles/01-settings/_settings.upload.scss +6 -5
  188. package/src/styles/02-tools/_tools.animations.scss +4 -5
  189. package/src/styles/02-tools/_tools.background.scss +1 -13
  190. package/src/styles/02-tools/_tools.glass.scss +0 -1
  191. package/src/styles/02-tools/_tools.utility-api.scss +42 -34
  192. package/src/styles/03-generic/_generic.root.scss +1 -4
  193. package/src/styles/04-elements/_elements.body.scss +0 -1
  194. package/src/styles/06-components/_components.atomix-glass.scss +217 -39
  195. package/src/styles/06-components/_components.badge.scss +6 -8
  196. package/src/styles/06-components/_components.button.scss +8 -3
  197. package/src/styles/06-components/_components.card.scss +2 -14
  198. package/src/styles/06-components/_components.chart.scss +969 -1449
  199. package/src/styles/06-components/_components.color-mode-toggle.scss +43 -6
  200. package/src/styles/06-components/_components.dropdown.scss +19 -7
  201. package/src/styles/06-components/_components.edge-panel.scss +4 -2
  202. package/src/styles/06-components/_components.footer.scss +166 -85
  203. package/src/styles/06-components/_components.input.scss +8 -9
  204. package/src/styles/06-components/_components.list.scss +1 -0
  205. package/src/styles/06-components/_components.modal.scss +5 -3
  206. package/src/styles/06-components/_components.skeleton.scss +8 -6
  207. package/src/styles/06-components/_components.upload.scss +219 -4
  208. package/src/styles/06-components/old.chart.styles.scss +1 -30
  209. package/src/styles/99-utilities/_utilities.opacity.scss +1 -1
  210. package/src/styles/99-utilities/_utilities.scss +1 -1
  211. package/src/components/Chart/AdvancedChart.tsx +0 -624
  212. package/src/components/Chart/LineChartNew.tsx +0 -167
  213. package/src/components/Chart/RealTimeChart.tsx +0 -436
  214. package/src/components/DatePicker/DatePicker copy.tsx +0 -551
@@ -1,7 +1,7 @@
1
- import AdvancedChart from './AdvancedChart';
2
1
  import AnimatedChart from './AnimatedChart';
3
2
  import AreaChart from './AreaChart';
4
3
  import BarChart from './BarChart';
4
+ import BaseChart from './BaseChart';
5
5
  import BubbleChart from './BubbleChart';
6
6
  import CandlestickChart from './CandlestickChart';
7
7
  import Chart from './Chart';
@@ -16,18 +16,18 @@ import LineChart from './LineChart';
16
16
  import MultiAxisChart from './MultiAxisChart';
17
17
  import PieChart from './PieChart';
18
18
  import RadarChart from './RadarChart';
19
- import RealTimeChart from './RealTimeChart';
20
19
  import ScatterChart from './ScatterChart';
21
20
 
22
21
  import TreemapChart from './TreemapChart';
23
22
  import WaterfallChart from './WaterfallChart';
23
+ import * as ChartUtils from './utils';
24
24
 
25
25
  // Export all chart components
26
26
  export {
27
- AdvancedChart,
28
27
  AnimatedChart,
29
28
  AreaChart,
30
29
  BarChart,
30
+ BaseChart,
31
31
  BubbleChart,
32
32
  CandlestickChart,
33
33
  Chart,
@@ -42,12 +42,15 @@ export {
42
42
  MultiAxisChart,
43
43
  PieChart,
44
44
  RadarChart,
45
- RealTimeChart,
46
45
  ScatterChart,
47
46
  TreemapChart,
48
47
  WaterfallChart,
48
+ ChartUtils,
49
49
  };
50
50
 
51
+ // Export chart context
52
+ export { ChartContext, useChartContext } from './Chart';
53
+
51
54
  // Export chart hooks
52
55
  export { useBarChart } from '../../lib/composables/useBarChart';
53
56
  export {
@@ -63,10 +66,9 @@ export { useLineChart } from '../../lib/composables/useLineChart';
63
66
  export { usePieChart } from '../../lib/composables/usePieChart';
64
67
 
65
68
  // Export chart component types
66
- export type { AdvancedChartProps } from './AdvancedChart';
67
- export type { AnimatedChartProps } from './AnimatedChart';
68
69
  export type { AreaChartProps } from './AreaChart';
69
70
  export type { BarChartProps } from './BarChart';
71
+ export type { BaseChartProps } from './BaseChart';
70
72
  export type { BubbleChartProps, BubbleDataPoint } from './BubbleChart';
71
73
  export type { CandlestickChartProps, CandlestickDataPoint } from './CandlestickChart';
72
74
  export type { ChartToolbarAction, ChartToolbarGroup, ChartToolbarProps } from './ChartToolbar';
@@ -76,11 +78,10 @@ export type { FunnelChartProps, FunnelDataPoint } from './FunnelChart';
76
78
  export type { GaugeChartProps } from './GaugeChart';
77
79
  export type { HeatmapChartProps, HeatmapDataPoint } from './HeatmapChart';
78
80
  export type { LineChartProps } from './LineChart';
79
- export type { AxisConfig, MultiAxisChartProps, MultiAxisDataset } from './MultiAxisChart';
81
+ export type { MultiAxisChartProps } from './MultiAxisChart';
80
82
  export type { PieChartProps } from './PieChart';
81
83
  export type { RadarChartProps } from './RadarChart';
82
- export type { RealTimeChartProps } from './RealTimeChart';
83
- export type { ScatterChartProps, ScatterDataPoint } from './ScatterChart';
84
+ export type { ScatterChartProps } from './ScatterChart';
84
85
 
85
86
  export type { TreemapChartProps, TreemapDataPoint, TreemapNode } from './TreemapChart';
86
87
  export type {
@@ -90,6 +91,7 @@ export type {
90
91
  ChartProps,
91
92
  ChartToolbarConfig,
92
93
  ChartType,
94
+ ScatterDataPoint,
93
95
  } from './types';
94
96
  export type { WaterfallChartProps, WaterfallDataPoint } from './WaterfallChart';
95
97
 
@@ -1,5 +1,5 @@
1
- import { ReactNode } from 'react';
2
- import { BaseComponentProps, Size, Variant } from '../../lib/types/components';
1
+ import React, { ReactNode } from 'react';
2
+ import { BaseComponentProps, Size, Variant, AtomixGlassProps } from '../../lib/types/components';
3
3
 
4
4
  /**
5
5
  * Chart types - comprehensive list
@@ -35,6 +35,15 @@ export interface ChartDataPoint {
35
35
  metadata?: Record<string, any>;
36
36
  }
37
37
 
38
+ /**
39
+ * Scatter chart data point interface
40
+ */
41
+ export interface ScatterDataPoint extends ChartDataPoint {
42
+ x: number;
43
+ y: number;
44
+ size?: number;
45
+ }
46
+
38
47
  /**
39
48
  * Chart dataset interface
40
49
  */
@@ -109,6 +118,12 @@ export interface ChartProps extends BaseComponentProps {
109
118
  */
110
119
  variant?: Variant;
111
120
 
121
+ /**
122
+ * Glass morphism effect
123
+ * When true, applies default glass effect. When an object, allows custom glass configuration.
124
+ */
125
+ glass?: boolean | AtomixGlassProps;
126
+
112
127
  /**
113
128
  * Chart title
114
129
  */
@@ -274,14 +289,9 @@ export interface ChartProps extends BaseComponentProps {
274
289
 
275
290
  /**
276
291
  * Chart scales interface
292
+ * Imported from useChart for consistency
277
293
  */
278
- export interface ChartScales {
279
- xScale: (index: number) => number;
280
- yScale: (value: number) => number;
281
- width: number;
282
- height: number;
283
- padding: { top: number; right: number; bottom: number; left: number };
284
- }
294
+ export type { ChartScales } from '../../lib/composables/useChart';
285
295
 
286
296
  /**
287
297
  * Chart interaction state
@@ -291,6 +301,72 @@ export interface ChartInteraction {
291
301
  selectedIndex: number | null;
292
302
  }
293
303
 
304
+ /**
305
+ * Chart hovered point state
306
+ */
307
+ export interface ChartHoveredPoint {
308
+ datasetIndex: number;
309
+ pointIndex: number;
310
+ x: number;
311
+ y: number;
312
+ clientX: number;
313
+ clientY: number;
314
+ }
315
+
316
+ /**
317
+ * Chart event handlers interface
318
+ */
319
+ export interface ChartHandlers {
320
+ onDataPointClick?: (dataPoint: ChartDataPoint, datasetIndex: number, pointIndex: number) => void;
321
+ onPointHover: (
322
+ datasetIndex: number,
323
+ pointIndex: number,
324
+ x: number,
325
+ y: number,
326
+ clientX: number,
327
+ clientY: number
328
+ ) => void;
329
+ onPointLeave: () => void;
330
+ onMouseMove: (event: React.MouseEvent<SVGSVGElement>) => void;
331
+ onMouseDown: (event: React.MouseEvent<SVGSVGElement>) => void;
332
+ onMouseUp: () => void;
333
+ onWheel: (event: React.WheelEvent<SVGSVGElement>) => void;
334
+ }
335
+
336
+ /**
337
+ * Chart accessibility interface
338
+ */
339
+ export interface ChartAccessibility {
340
+ announcement: string;
341
+ focusedPoint: { datasetIndex: number; pointIndex: number };
342
+ getAccessibleDescription: () => string;
343
+ }
344
+
345
+ /**
346
+ * Chart toolbar state
347
+ */
348
+ export interface ChartToolbarState {
349
+ showTooltips?: boolean;
350
+ showLegend?: boolean;
351
+ animationsEnabled?: boolean;
352
+ showGrid?: boolean;
353
+ }
354
+
355
+ /**
356
+ * Chart render content parameters
357
+ */
358
+ export interface ChartRenderContentParams {
359
+ scales: ChartScales;
360
+ colors: string[];
361
+ datasets: ChartDataset[];
362
+ interactionState?: ChartInteraction;
363
+ handlers: ChartHandlers;
364
+ accessibility: ChartAccessibility;
365
+ hoveredPoint: ChartHoveredPoint | null;
366
+ toolbarState?: ChartToolbarState;
367
+ config?: ChartConfig;
368
+ }
369
+
294
370
  /**
295
371
  * Chart toolbar configuration interface
296
372
  */
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Generate a color from a color scheme based on an index
3
+ * @param colorScheme - Array of colors to choose from
4
+ * @param index - Index to select color from
5
+ * @returns Color string
6
+ */
7
+ export const getColorFromScheme = (colorScheme: string[], index: number): string => {
8
+ return colorScheme[index % colorScheme.length];
9
+ };
10
+
11
+ /**
12
+ * Generate a color based on a value within a range
13
+ * @param value - The value to map to a color
14
+ * @param minValue - The minimum value in the range
15
+ * @param maxValue - The maximum value in the range
16
+ * @param colorScheme - Array of colors to interpolate between
17
+ * @returns Color string
18
+ */
19
+ export const getColorFromValue = (
20
+ value: number,
21
+ minValue: number,
22
+ maxValue: number,
23
+ colorScheme: string[]
24
+ ): string => {
25
+ if (minValue === maxValue) {
26
+ return colorScheme[0];
27
+ }
28
+
29
+ const normalized = (value - minValue) / (maxValue - minValue);
30
+ const index = Math.floor(normalized * (colorScheme.length - 1));
31
+ return colorScheme[index];
32
+ };
33
+
34
+ /**
35
+ * Default color schemes for charts
36
+ */
37
+ export const DEFAULT_COLOR_SCHEMES = {
38
+ primary: [
39
+ 'var(--atomix-primary)',
40
+ 'var(--atomix-secondary)',
41
+ 'var(--atomix-success)',
42
+ 'var(--atomix-warning)',
43
+ 'var(--atomix-error)',
44
+ 'var(--atomix-info)',
45
+ ],
46
+ rainbow: [
47
+ 'var(--atomix-error)',
48
+ 'var(--atomix-info)',
49
+ 'var(--atomix-primary)',
50
+ 'var(--atomix-warning)',
51
+ 'var(--atomix-success)',
52
+ 'var(--atomix-primary-5)',
53
+ 'var(--atomix-primary-7)',
54
+ 'var(--atomix-warning-5)',
55
+ ],
56
+ monochrome: [
57
+ 'var(--atomix-gray-10)',
58
+ 'var(--atomix-gray-9)',
59
+ 'var(--atomix-gray-8)',
60
+ 'var(--atomix-gray-7)',
61
+ 'var(--atomix-gray-6)',
62
+ 'var(--atomix-gray-5)',
63
+ 'var(--atomix-gray-4)',
64
+ 'var(--atomix-gray-3)',
65
+ ],
66
+ };
@@ -1,5 +1,7 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react';
2
- import { ColorModeToggle } from './ColorModeToggle';
2
+ import { useState } from 'react';
3
+ import { ColorModeToggle, type ColorMode } from './ColorModeToggle';
4
+ import { Moon, Sun } from '@phosphor-icons/react';
3
5
 
4
6
  const meta = {
5
7
  title: 'Components/ColorModeToggle',
@@ -8,9 +10,26 @@ const meta = {
8
10
  layout: 'centered',
9
11
  },
10
12
  argTypes: {
11
- className: {
12
- control: 'text',
13
- description: 'Additional CSS class names',
13
+ size: {
14
+ control: 'select',
15
+ options: ['sm', 'md', 'lg'],
16
+ description: 'Size variant',
17
+ },
18
+ disabled: {
19
+ control: 'boolean',
20
+ description: 'Disable the toggle',
21
+ },
22
+ showTooltip: {
23
+ control: 'boolean',
24
+ description: 'Show tooltip on hover',
25
+ },
26
+ disableStorage: {
27
+ control: 'boolean',
28
+ description: 'Disable localStorage persistence',
29
+ },
30
+ disableSystemPreference: {
31
+ control: 'boolean',
32
+ description: 'Disable system preference detection',
14
33
  },
15
34
  },
16
35
  } satisfies Meta<typeof ColorModeToggle>;
@@ -23,22 +42,113 @@ export const Default: Story = {
23
42
  args: {},
24
43
  };
25
44
 
26
- // With Custom Class
27
- export const WithCustomClass: Story = {
45
+ // Size Variants
46
+ export const Sizes: Story = {
47
+ render: () => (
48
+ <div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
49
+ <ColorModeToggle size="sm" />
50
+ <ColorModeToggle size="md" />
51
+ <ColorModeToggle size="lg" />
52
+ </div>
53
+ ),
54
+ };
55
+
56
+ // Disabled State
57
+ export const Disabled: Story = {
58
+ args: {
59
+ disabled: true,
60
+ },
61
+ };
62
+
63
+ // Controlled Mode
64
+ export const Controlled: Story = {
65
+ render: () => {
66
+ const [mode, setMode] = useState<ColorMode>('light');
67
+ return (
68
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', alignItems: 'center' }}>
69
+ <ColorModeToggle value={mode} onChange={setMode} />
70
+ <p>Current mode: {mode}</p>
71
+ <button onClick={() => setMode(mode === 'light' ? 'dark' : 'light')}>
72
+ Toggle from outside
73
+ </button>
74
+ </div>
75
+ );
76
+ },
77
+ };
78
+
79
+ // Custom Icons
80
+ export const CustomIcons: Story = {
81
+ render: () => (
82
+ <ColorModeToggle
83
+ lightIcon={<Moon size={24} weight="fill" />}
84
+ darkIcon={<Sun size={24} weight="fill" />}
85
+ />
86
+ ),
87
+ };
88
+
89
+ // With Callback
90
+ export const WithCallback: Story = {
91
+ render: () => {
92
+ const [lastChanged, setLastChanged] = useState<string>('');
93
+ return (
94
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem', alignItems: 'center' }}>
95
+ <ColorModeToggle
96
+ onChange={(mode) => setLastChanged(`Changed to ${mode} at ${new Date().toLocaleTimeString()}`)}
97
+ />
98
+ {lastChanged && <p style={{ fontSize: '0.875rem' }}>{lastChanged}</p>}
99
+ </div>
100
+ );
101
+ },
102
+ };
103
+
104
+ // Custom Storage Key
105
+ export const CustomStorageKey: Story = {
106
+ args: {
107
+ storageKey: 'my-app-theme',
108
+ dataAttribute: 'data-theme',
109
+ },
110
+ };
111
+
112
+ // Without Storage
113
+ export const WithoutStorage: Story = {
28
114
  args: {
29
- className: 'custom-class',
115
+ disableStorage: true,
30
116
  },
31
117
  };
32
118
 
33
- // Example Usage
34
- export const ExampleUsage: Story = {
119
+ // Example Usage in Header
120
+ export const InHeader: Story = {
35
121
  render: () => (
36
122
  <div
37
123
  className="u-p-5 u-shadow u-d-flex u-justify-content-between u-align-items-center"
38
- style={{ width: '300px', borderRadius: '8px' }}
124
+ style={{ width: '400px', borderRadius: '8px' }}
39
125
  >
40
- <span>Toggle Theme</span>
126
+ <span style={{ fontWeight: 600 }}>Toggle Theme</span>
41
127
  <ColorModeToggle />
42
128
  </div>
43
129
  ),
44
130
  };
131
+
132
+ // Multiple Toggles
133
+ export const MultipleToggles: Story = {
134
+ render: () => (
135
+ <div style={{ display: 'flex', gap: '2rem' }}>
136
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', alignItems: 'center' }}>
137
+ <ColorModeToggle size="sm" />
138
+ <span style={{ fontSize: '0.75rem' }}>Small</span>
139
+ </div>
140
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', alignItems: 'center' }}>
141
+ <ColorModeToggle size="md" />
142
+ <span style={{ fontSize: '0.75rem' }}>Medium</span>
143
+ </div>
144
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', alignItems: 'center' }}>
145
+ <ColorModeToggle size="lg" />
146
+ <span style={{ fontSize: '0.75rem' }}>Large</span>
147
+ </div>
148
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '0.5rem', alignItems: 'center' }}>
149
+ <ColorModeToggle disabled />
150
+ <span style={{ fontSize: '0.75rem' }}>Disabled</span>
151
+ </div>
152
+ </div>
153
+ ),
154
+ };
@@ -1,81 +1,185 @@
1
- import React, { useEffect, useState } from 'react';
1
+ import React, { useEffect, useState, useCallback } from 'react';
2
+
3
+ export type ColorMode = 'light' | 'dark';
2
4
 
3
5
  export interface ColorModeToggleProps {
6
+ /** Additional CSS class names */
4
7
  className?: string;
8
+ /** Inline styles */
9
+ style?: React.CSSProperties;
10
+ /** Controlled mode value */
11
+ value?: ColorMode;
12
+ /** Default mode (uncontrolled) */
13
+ defaultValue?: ColorMode;
14
+ /** Callback when mode changes */
15
+ onChange?: (mode: ColorMode) => void;
16
+ /** Custom light mode icon */
17
+ lightIcon?: React.ReactNode;
18
+ /** Custom dark mode icon */
19
+ darkIcon?: React.ReactNode;
20
+ /** Size variant */
21
+ size?: 'sm' | 'md' | 'lg';
22
+ /** Disable the toggle */
23
+ disabled?: boolean;
24
+ /** localStorage key for persistence */
25
+ storageKey?: string;
26
+ /** data attribute name for body element */
27
+ dataAttribute?: string;
28
+ /** Disable localStorage persistence */
29
+ disableStorage?: boolean;
30
+ /** Disable system preference detection */
31
+ disableSystemPreference?: boolean;
32
+ /** Custom aria-label */
33
+ 'aria-label'?: string;
34
+ /** Show tooltip */
35
+ showTooltip?: boolean;
5
36
  }
6
37
 
7
- export const ColorModeToggle: React.FC<ColorModeToggleProps> = ({ className = '' }) => {
8
- const [colorMode, setColorMode] = useState<'light' | 'dark'>('light');
38
+ const DEFAULT_STORAGE_KEY = 'atomix-color-mode';
39
+ const DEFAULT_DATA_ATTRIBUTE = 'data-atomix-color-mode';
40
+
41
+ const SIZE_MAP = {
42
+ sm: 16,
43
+ md: 24,
44
+ lg: 32,
45
+ };
46
+
47
+ export const ColorModeToggle: React.FC<ColorModeToggleProps> = ({
48
+ className = '',
49
+ style,
50
+ value: controlledValue,
51
+ defaultValue = 'light',
52
+ onChange,
53
+ lightIcon,
54
+ darkIcon,
55
+ size = 'md',
56
+ disabled = false,
57
+ storageKey = DEFAULT_STORAGE_KEY,
58
+ dataAttribute = DEFAULT_DATA_ATTRIBUTE,
59
+ disableStorage = false,
60
+ disableSystemPreference = false,
61
+ 'aria-label': ariaLabel,
62
+ showTooltip = true,
63
+ }) => {
64
+ const isControlled = controlledValue !== undefined;
65
+ const [internalMode, setInternalMode] = useState<ColorMode>(defaultValue);
66
+ const colorMode = isControlled ? controlledValue : internalMode;
9
67
 
10
68
  // Initialize color mode from localStorage or system preference
11
69
  useEffect(() => {
70
+ if (isControlled) return;
71
+
72
+ // SSR check
73
+ if (typeof window === 'undefined') return;
74
+
12
75
  // Check if color mode is already set in localStorage
13
- const storedColorMode = localStorage.getItem('atomix-color-mode');
76
+ if (!disableStorage) {
77
+ try {
78
+ const storedColorMode = localStorage.getItem(storageKey);
79
+ if (storedColorMode === 'light' || storedColorMode === 'dark') {
80
+ setInternalMode(storedColorMode);
81
+ return;
82
+ }
83
+ } catch (error) {
84
+ console.warn('ColorModeToggle: Failed to read from localStorage', error);
85
+ }
86
+ }
14
87
 
15
- if (storedColorMode === 'light' || storedColorMode === 'dark') {
16
- setColorMode(storedColorMode);
17
- } else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
18
- // Use system preference if no stored preference
19
- setColorMode('dark');
88
+ // Use system preference if no stored preference
89
+ if (!disableSystemPreference && window.matchMedia) {
90
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
91
+ if (prefersDark) {
92
+ setInternalMode('dark');
93
+ }
20
94
  }
21
- }, []);
95
+ }, [isControlled, disableStorage, disableSystemPreference, storageKey]);
22
96
 
23
97
  // Update the document theme attribute when colorMode changes
24
98
  useEffect(() => {
25
- document.body.setAttribute('data-atomix-color-mode', colorMode);
26
- localStorage.setItem('atomix-color-mode', colorMode);
27
- }, [colorMode]);
99
+ if (typeof window === 'undefined') return;
100
+
101
+ const validColorMode = colorMode === 'dark' ? 'dark' : 'light';
102
+ document.body.setAttribute(dataAttribute, validColorMode);
103
+
104
+ if (!disableStorage) {
105
+ try {
106
+ localStorage.setItem(storageKey, validColorMode);
107
+ } catch (error) {
108
+ console.warn('ColorModeToggle: Failed to write to localStorage', error);
109
+ }
110
+ }
111
+ }, [colorMode, dataAttribute, disableStorage, storageKey]);
28
112
 
29
113
  // Listen for system color scheme changes
30
114
  useEffect(() => {
115
+ if (isControlled || disableSystemPreference || typeof window === 'undefined') return;
116
+
31
117
  const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
32
118
 
33
119
  const handleSystemThemeChange = (event: MediaQueryListEvent) => {
34
120
  // Only update if user hasn't explicitly set a preference
35
- if (!localStorage.getItem('atomix-color-mode')) {
36
- setColorMode(event.matches ? 'dark' : 'light');
121
+ if (disableStorage) {
122
+ setInternalMode(event.matches ? 'dark' : 'light');
123
+ } else {
124
+ try {
125
+ const hasStoredPreference = localStorage.getItem(storageKey);
126
+ if (!hasStoredPreference) {
127
+ setInternalMode(event.matches ? 'dark' : 'light');
128
+ }
129
+ } catch (error) {
130
+ console.warn('ColorModeToggle: Failed to check localStorage', error);
131
+ }
37
132
  }
38
133
  };
39
134
 
40
- // Add event listener for system theme changes
41
- if (darkModeMediaQuery.addEventListener) {
42
- darkModeMediaQuery.addEventListener('change', handleSystemThemeChange);
43
- } else {
44
- // Fallback for older browsers
45
- darkModeMediaQuery.addListener(handleSystemThemeChange);
46
- }
135
+ darkModeMediaQuery.addEventListener('change', handleSystemThemeChange);
47
136
 
48
- // Clean up event listener
49
137
  return () => {
50
- if (darkModeMediaQuery.removeEventListener) {
51
- darkModeMediaQuery.removeEventListener('change', handleSystemThemeChange);
52
- } else {
53
- // Fallback for older browsers
54
- darkModeMediaQuery.removeListener(handleSystemThemeChange);
55
- }
138
+ darkModeMediaQuery.removeEventListener('change', handleSystemThemeChange);
56
139
  };
57
- }, []);
140
+ }, [isControlled, disableSystemPreference, disableStorage, storageKey]);
58
141
 
59
- const toggleColorMode = () => {
60
- setColorMode(prevMode => (prevMode === 'light' ? 'dark' : 'light'));
61
- };
142
+ const toggleColorMode = useCallback(() => {
143
+ if (disabled) return;
144
+
145
+ const newMode: ColorMode = colorMode === 'light' ? 'dark' : 'light';
146
+
147
+ if (!isControlled) {
148
+ setInternalMode(newMode);
149
+ }
150
+
151
+ onChange?.(newMode);
152
+ }, [disabled, colorMode, isControlled, onChange]);
153
+
154
+ const iconSize = SIZE_MAP[size];
155
+ const nextMode = colorMode === 'light' ? 'dark' : 'light';
156
+ const label = ariaLabel || `Switch to ${nextMode} mode`;
157
+ const title = showTooltip ? `Switch to ${nextMode} mode` : undefined;
158
+
159
+ const defaultLightIcon = (
160
+ <svg viewBox="0 0 24 24" width={iconSize} height={iconSize} fill="currentColor" aria-hidden="true">
161
+ <path d="M9.37 5.51c-.18.64-.27 1.31-.27 1.99 0 4.08 3.32 7.4 7.4 7.4.68 0 1.35-.09 1.99-.27C17.45 17.19 14.93 19 12 19c-3.86 0-7-3.14-7-7 0-2.93 1.81-5.45 4.37-6.49zM12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z" />
162
+ </svg>
163
+ );
164
+
165
+ const defaultDarkIcon = (
166
+ <svg viewBox="0 0 24 24" width={iconSize} height={iconSize} fill="currentColor" aria-hidden="true">
167
+ <path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41.39.39 1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41.39.39 1.03.39 1.41 0l1.06-1.06z" />
168
+ </svg>
169
+ );
62
170
 
63
171
  return (
64
172
  <button
65
- className={`c-color-mode-toggle ${className}`}
173
+ type="button"
174
+ className={`c-color-mode-toggle c-color-mode-toggle--${size} ${disabled ? 'c-color-mode-toggle--disabled' : ''} ${className}`}
66
175
  onClick={toggleColorMode}
67
- aria-label={`Switch to ${colorMode === 'light' ? 'dark' : 'light'} mode`}
68
- title={`Switch to ${colorMode === 'light' ? 'dark' : 'light'} mode`}
176
+ disabled={disabled}
177
+ aria-label={label}
178
+ aria-pressed={colorMode === 'dark'}
179
+ title={title}
180
+ style={style}
69
181
  >
70
- {colorMode === 'light' ? (
71
- <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
72
- <path d="M9.37 5.51c-.18.64-.27 1.31-.27 1.99 0 4.08 3.32 7.4 7.4 7.4.68 0 1.35-.09 1.99-.27C17.45 17.19 14.93 19 12 19c-3.86 0-7-3.14-7-7 0-2.93 1.81-5.45 4.37-6.49zM12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.26-4.4 2.26-2.98 0-5.4-2.42-5.4-5.4 0-1.81.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z" />
73
- </svg>
74
- ) : (
75
- <svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
76
- <path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41.39.39 1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41.39.39 1.03.39 1.41 0l1.06-1.06z" />
77
- </svg>
78
- )}
182
+ {colorMode === 'light' ? (lightIcon || defaultLightIcon) : (darkIcon || defaultDarkIcon)}
79
183
  </button>
80
184
  );
81
185
  };
@@ -1,2 +1,2 @@
1
1
  export { default as ColorModeToggle } from './ColorModeToggle';
2
- export type { ColorModeToggleProps } from './ColorModeToggle';
2
+ export type { ColorModeToggleProps, ColorMode } from './ColorModeToggle';