@shohojdhara/atomix 0.3.6 → 0.3.8

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 (74) hide show
  1. package/README.md +3 -3
  2. package/dist/atomix.css +77 -0
  3. package/dist/atomix.css.map +1 -1
  4. package/dist/atomix.min.css +77 -0
  5. package/dist/atomix.min.css.map +1 -1
  6. package/dist/charts.js +50 -142
  7. package/dist/charts.js.map +1 -1
  8. package/dist/core.d.ts +2 -2
  9. package/dist/core.js +179 -274
  10. package/dist/core.js.map +1 -1
  11. package/dist/forms.js +50 -142
  12. package/dist/forms.js.map +1 -1
  13. package/dist/heavy.js +179 -274
  14. package/dist/heavy.js.map +1 -1
  15. package/dist/index.d.ts +1255 -1226
  16. package/dist/index.esm.js +2806 -2958
  17. package/dist/index.esm.js.map +1 -1
  18. package/dist/index.js +3113 -3269
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.min.js +1 -1
  21. package/dist/index.min.js.map +1 -1
  22. package/dist/theme.d.ts +313 -667
  23. package/dist/theme.js +1818 -2589
  24. package/dist/theme.js.map +1 -1
  25. package/package.json +1 -1
  26. package/src/components/AtomixGlass/AtomixGlass.tsx +128 -356
  27. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +1 -1
  28. package/src/components/Button/Button.tsx +85 -167
  29. package/src/components/DataTable/DataTable.stories.tsx +238 -0
  30. package/src/components/DataTable/DataTable.test.tsx +450 -0
  31. package/src/components/DataTable/DataTable.tsx +384 -61
  32. package/src/components/DatePicker/DatePicker.tsx +29 -38
  33. package/src/components/Upload/Upload.tsx +539 -40
  34. package/src/lib/composables/useAtomixGlass.ts +7 -7
  35. package/src/lib/composables/useDataTable.ts +355 -15
  36. package/src/lib/composables/useDatePicker.ts +19 -0
  37. package/src/lib/config/loader.ts +2 -3
  38. package/src/lib/constants/components.ts +17 -0
  39. package/src/lib/hooks/usePerformanceMonitor.ts +1 -1
  40. package/src/lib/theme/adapters/cssVariableMapper.ts +29 -14
  41. package/src/lib/theme/adapters/index.ts +1 -4
  42. package/src/lib/theme/config/configLoader.ts +82 -223
  43. package/src/lib/theme/config/loader.ts +15 -21
  44. package/src/lib/theme/constants/constants.ts +1 -1
  45. package/src/lib/theme/core/ThemeRegistry.ts +75 -279
  46. package/src/lib/theme/core/composeTheme.ts +30 -88
  47. package/src/lib/theme/core/createTheme.ts +88 -51
  48. package/src/lib/theme/core/createThemeObject.ts +2 -2
  49. package/src/lib/theme/core/index.ts +15 -2
  50. package/src/lib/theme/errors/errors.ts +1 -1
  51. package/src/lib/theme/generators/generateCSSNested.ts +131 -0
  52. package/src/lib/theme/generators/generateCSSVariables.ts +24 -16
  53. package/src/lib/theme/generators/index.ts +6 -0
  54. package/src/lib/theme/index.ts +45 -27
  55. package/src/lib/theme/runtime/ThemeApplicator.ts +6 -109
  56. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +1 -1
  57. package/src/lib/theme/runtime/ThemeProvider.tsx +393 -544
  58. package/src/lib/theme/runtime/index.ts +1 -0
  59. package/src/lib/theme/runtime/useTheme.ts +1 -1
  60. package/src/lib/theme/runtime/useThemeTokens.ts +122 -0
  61. package/src/lib/theme/test/testTheme.ts +2 -1
  62. package/src/lib/theme/types.ts +14 -14
  63. package/src/lib/theme/utils/componentTheming.ts +140 -0
  64. package/src/lib/theme/utils/domUtils.ts +57 -15
  65. package/src/lib/theme/utils/injectCSS.ts +0 -1
  66. package/src/lib/theme/utils/naming.ts +100 -0
  67. package/src/lib/theme/utils/themeHelpers.ts +1 -39
  68. package/src/lib/theme/utils/themeUtils.ts +1 -170
  69. package/src/lib/types/components.ts +145 -0
  70. package/src/lib/utils/componentUtils.ts +1 -1
  71. package/src/lib/utils/dataTableExport.ts +143 -0
  72. package/src/lib/utils/memoryMonitor.ts +3 -3
  73. package/src/lib/utils/themeNaming.ts +135 -0
  74. package/src/styles/06-components/_components.data-table.scss +95 -0
@@ -1,10 +1,11 @@
1
- import React, { ElementType, forwardRef, useCallback, useMemo } from 'react';
1
+ import React, { ElementType, forwardRef, useCallback } from 'react';
2
2
  import { useButton } from '../../lib/composables/useButton';
3
3
  import { ButtonProps } from '../../lib/types/components';
4
4
  import { AtomixGlass } from '../AtomixGlass/AtomixGlass';
5
5
  import { Spinner } from '../Spinner/Spinner';
6
6
  import { Icon, type PhosphorIconsType } from '../Icon/Icon';
7
- import { BUTTON } from '../../lib/constants/components';
7
+ import { BUTTON, THEME_NAMING } from '../../lib/constants/components';
8
+ import { ThemeNaming } from '../../lib/utils/themeNaming';
8
9
 
9
10
  export type ButtonAsProp = {
10
11
  as?: ElementType;
@@ -62,13 +63,7 @@ export const Button = React.memo(
62
63
  const shouldRenderAsLink = Boolean(href && !isDisabled);
63
64
 
64
65
  // Resolve icon element - support both icon (ReactNode) and iconName (string)
65
- const iconElement = useMemo(() => {
66
- if (loading) return null;
67
- if (iconName) {
68
- return <Icon name={iconName as PhosphorIconsType} size={iconSize} />;
69
- }
70
- return icon;
71
- }, [icon, iconName, iconSize, loading]);
66
+ const iconElement = iconName ? <Icon name={iconName as PhosphorIconsType} size={iconSize} /> : icon;
72
67
 
73
68
  const { generateButtonClass, handleClick } = useButton({
74
69
  variant,
@@ -83,24 +78,23 @@ export const Button = React.memo(
83
78
  selected,
84
79
  });
85
80
 
86
- const buttonClass = useMemo(
87
- () =>
88
- generateButtonClass({
89
- variant,
90
- size,
91
- disabled: isDisabled,
92
- rounded,
93
- iconOnly,
94
- glass,
95
- loading,
96
- fullWidth,
97
- block,
98
- active,
99
- selected,
100
- className,
101
- }),
102
- [variant, size, isDisabled, rounded, iconOnly, glass, loading, fullWidth, block, active, selected, className, generateButtonClass]
103
- );
81
+ const buttonClass = [
82
+ BUTTON.BASE_CLASS,
83
+ ThemeNaming.variantClass(THEME_NAMING.BUTTON_PREFIX, variant),
84
+ size !== 'md' ? ThemeNaming.sizeClass(THEME_NAMING.BUTTON_PREFIX, size) : '',
85
+ iconOnly ? ThemeNaming.stateClass(THEME_NAMING.BUTTON_PREFIX, THEME_NAMING.ICON_ELEMENT) : '',
86
+ rounded ? ThemeNaming.stateClass(THEME_NAMING.BUTTON_PREFIX, 'rounded') : '',
87
+ isDisabled ? ThemeNaming.stateClass(THEME_NAMING.BUTTON_PREFIX, 'disabled') : '',
88
+ glass ? ThemeNaming.stateClass(THEME_NAMING.BUTTON_PREFIX, 'glass') : '',
89
+ loading ? BUTTON.CLASSES.LOADING : '',
90
+ fullWidth ? BUTTON.CLASSES.FULL_WIDTH : '',
91
+ block ? BUTTON.CLASSES.BLOCK : '',
92
+ active ? BUTTON.CLASSES.ACTIVE : '',
93
+ selected ? BUTTON.CLASSES.SELECTED : '',
94
+ className,
95
+ ]
96
+ .filter(Boolean)
97
+ .join(' ');
104
98
 
105
99
  // Handle click with loading check
106
100
  const handleClickEvent = useCallback(
@@ -145,117 +139,76 @@ export const Button = React.memo(
145
139
  );
146
140
 
147
141
  // Determine button text
148
- const buttonText = useMemo(() => {
149
- if (loading && loadingText) return loadingText;
150
- if (loading && !loadingText) return label || children;
151
- return label || children;
152
- }, [loading, loadingText, label, children]);
142
+ const buttonText = loading && loadingText ? loadingText : label || children;
153
143
 
154
144
  // Determine spinner size based on button size
155
- const spinnerSize = useMemo(() => {
156
- if (size === 'sm') return 'sm';
157
- if (size === 'lg') return 'md';
158
- return 'sm';
159
- }, [size]);
145
+ const spinnerSize = size === 'sm' ? 'sm' : size === 'lg' ? 'md' : 'sm';
160
146
 
161
147
  // Button content with icon positioning
162
- const buttonContent = useMemo(() => {
163
- const iconSpan = iconElement && (
164
- <span className={BUTTON.ICON_CLASS} aria-hidden="true">
165
- {iconElement}
166
- </span>
167
- );
168
-
169
- const spinnerElement = loading && (
170
- <span className={BUTTON.SPINNER_CLASS} aria-hidden="true">
171
- <Spinner
172
- size={spinnerSize}
173
- variant={
174
- variant === 'link' || (typeof variant === 'string' && variant.startsWith('outline-'))
175
- ? 'primary'
176
- : (variant === 'danger' ? 'error' : (variant as any))
177
- }
178
- />
179
- </span>
180
- );
181
-
182
- const labelElement = !iconOnly && buttonText && (
183
- <span className={BUTTON.LABEL_CLASS}>{buttonText}</span>
184
- );
185
-
186
- if (iconPosition === 'end') {
187
- return (
188
- <>
189
- {labelElement}
190
- {spinnerElement}
191
- {iconSpan}
192
- </>
193
- );
194
- }
195
-
196
- return (
197
- <>
198
- {spinnerElement}
199
- {iconSpan}
200
- {labelElement}
201
- </>
202
- );
203
- }, [iconElement, iconPosition, iconOnly, buttonText, loading, spinnerSize, variant]);
148
+ const buttonContent = (
149
+ <>
150
+ {loading && (
151
+ <span className={ThemeNaming.bemClass(THEME_NAMING.BUTTON_PREFIX, THEME_NAMING.SPINNER_ELEMENT)} aria-hidden="true">
152
+ <Spinner
153
+ size={spinnerSize}
154
+ variant={
155
+ variant === 'link' || (typeof variant === 'string' && variant.startsWith('outline-'))
156
+ ? 'primary'
157
+ : (variant === 'danger' ? 'error' : (variant as any))
158
+ }
159
+ />
160
+ </span>
161
+ )}
162
+ {iconElement && !loading && (
163
+ <span className={ThemeNaming.bemClass(THEME_NAMING.BUTTON_PREFIX, THEME_NAMING.ICON_ELEMENT)} aria-hidden="true">
164
+ {iconElement}
165
+ </span>
166
+ )}
167
+ {!iconOnly && buttonText && (
168
+ <span className={ThemeNaming.bemClass(THEME_NAMING.BUTTON_PREFIX, THEME_NAMING.LABEL_ELEMENT)}>{buttonText}</span>
169
+ )}
170
+ </>
171
+ );
204
172
 
205
173
  // Button props
206
- const buttonProps = useMemo(
207
- () => ({
208
- ref,
209
- className: buttonClass,
210
- type: Component === 'button' && !shouldRenderAsLink ? type : undefined,
211
- onClick: handleClickEvent,
212
- onMouseEnter: onHover ? handleMouseEnter : undefined,
213
- onFocus: onFocus ? handleFocusEvent : undefined,
214
- onBlur: onBlur ? handleBlurEvent : undefined,
215
- disabled: isDisabled && Component === 'button' && !shouldRenderAsLink,
216
- 'aria-disabled': isDisabled,
217
- 'aria-busy': loading,
218
- 'aria-label': ariaLabel || (iconOnly ? label || children : undefined),
219
- 'aria-describedby': ariaDescribedBy,
220
- 'aria-expanded': ariaExpanded,
221
- 'aria-controls': ariaControls,
222
- tabIndex: tabIndex !== undefined ? tabIndex : (isDisabled ? -1 : 0),
223
- style,
224
- ...props,
225
- }),
226
- [
227
- ref,
228
- buttonClass,
229
- Component,
230
- type,
231
- handleClickEvent,
232
- handleMouseEnter,
233
- handleFocusEvent,
234
- handleBlurEvent,
235
- isDisabled,
236
- loading,
237
- ariaLabel,
238
- iconOnly,
239
- label,
240
- children,
241
- ariaDescribedBy,
242
- ariaExpanded,
243
- ariaControls,
244
- tabIndex,
245
- style,
246
- props,
247
- ]
248
- );
174
+ const buttonProps = {
175
+ ref,
176
+ className: buttonClass,
177
+ type: Component === 'button' && !shouldRenderAsLink ? type : undefined,
178
+ onClick: handleClickEvent,
179
+ onMouseEnter: onHover ? handleMouseEnter : undefined,
180
+ onFocus: onFocus ? handleFocusEvent : undefined,
181
+ onBlur: onBlur ? handleBlurEvent : undefined,
182
+ disabled: isDisabled && Component === 'button' && !shouldRenderAsLink,
183
+ 'aria-disabled': isDisabled,
184
+ 'aria-busy': loading,
185
+ 'aria-label': ariaLabel || (iconOnly ? label || children : undefined),
186
+ 'aria-describedby': ariaDescribedBy,
187
+ 'aria-expanded': ariaExpanded,
188
+ 'aria-controls': ariaControls,
189
+ tabIndex: tabIndex !== undefined ? tabIndex : (isDisabled ? -1 : 0),
190
+ style,
191
+ ...props,
192
+ };
193
+
194
+ // Default glass props
195
+ const defaultGlassProps = {
196
+ displacementScale: 20,
197
+ blurAmount: 0,
198
+ saturation: 200,
199
+ elasticity: 0,
200
+ };
201
+ const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
249
202
 
250
203
  // Render as anchor if href is provided
251
204
  if (shouldRenderAsLink) {
252
205
  const { ref: _, ...buttonPropsWithoutRef } = buttonProps;
253
206
  const anchorButtonProps = {
254
207
  ...buttonPropsWithoutRef,
255
- type: undefined,
208
+ type: undefined,
256
209
  disabled: undefined,
257
210
  };
258
-
211
+
259
212
  // Use custom LinkComponent if provided (e.g., Next.js Link)
260
213
  if (LinkComponent) {
261
214
  const LinkComp = LinkComponent as React.ComponentType<any>;
@@ -266,25 +219,14 @@ export const Button = React.memo(
266
219
  target,
267
220
  rel: target === '_blank' ? 'noopener noreferrer' : undefined,
268
221
  };
269
-
222
+
270
223
  const linkElement = (
271
224
  <LinkComp {...linkProps}>
272
225
  {buttonContent}
273
226
  </LinkComp>
274
227
  );
275
228
 
276
- if (glass) {
277
- const defaultGlassProps = {
278
- displacementScale: 20,
279
- blurAmount: 0,
280
- saturation: 200,
281
- elasticity: 0,
282
- };
283
- const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
284
- return <AtomixGlass {...glassProps}>{linkElement}</AtomixGlass>;
285
- }
286
-
287
- return linkElement;
229
+ return glass ? <AtomixGlass {...glassProps}>{linkElement}</AtomixGlass> : linkElement;
288
230
  }
289
231
 
290
232
  // Fallback to regular anchor tag
@@ -294,39 +236,15 @@ export const Button = React.memo(
294
236
  </a>
295
237
  );
296
238
 
297
- if (glass) {
298
- const defaultGlassProps = {
299
- displacementScale: 20,
300
- blurAmount: 0,
301
- saturation: 200,
302
- elasticity: 0,
303
- };
304
- const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
305
- return <AtomixGlass {...glassProps}>{anchorElement}</AtomixGlass>;
306
- }
307
-
308
- return anchorElement;
239
+ return glass ? <AtomixGlass {...glassProps}>{anchorElement}</AtomixGlass> : anchorElement;
309
240
  }
310
241
 
311
242
  // Default button rendering
312
- if (glass) {
313
- const defaultGlassProps = {
314
- displacementScale: 20,
315
- blurAmount: 0,
316
- saturation: 200,
317
- elasticity: 0,
318
- };
319
-
320
- const glassProps = glass === true ? defaultGlassProps : { ...defaultGlassProps, ...glass };
321
-
322
- return (
323
- <AtomixGlass {...glassProps}>
324
- <Component {...buttonProps}>{buttonContent}</Component>
325
- </AtomixGlass>
326
- );
327
- }
243
+ const buttonElement = (
244
+ <Component {...buttonProps}>{buttonContent}</Component>
245
+ );
328
246
 
329
- return <Component {...buttonProps}>{buttonContent}</Component>;
247
+ return glass ? <AtomixGlass {...glassProps}>{buttonElement}</AtomixGlass> : buttonElement;
330
248
  }
331
249
  )
332
250
  );
@@ -335,4 +253,4 @@ Button.displayName = 'Button';
335
253
 
336
254
  export type { ButtonProps };
337
255
 
338
- export default Button;
256
+ export default Button;
@@ -253,3 +253,241 @@ export const Interactive: Story = {
253
253
  sortable: true,
254
254
  },
255
255
  };
256
+
257
+ // Row selection - multiple
258
+ export const RowSelectionMultiple: Story = {
259
+ render: args => {
260
+ const [selectedRows, setSelectedRows] = useState<any[]>([]);
261
+
262
+ return (
263
+ <div>
264
+ <DataTable
265
+ {...args}
266
+ selectionMode="multiple"
267
+ onSelectionChange={(rows, ids) => setSelectedRows(rows)}
268
+ />
269
+ {selectedRows.length > 0 && (
270
+ <div style={{ marginTop: '1rem', padding: '1rem', background: '#f5f5f5', borderRadius: '4px' }}>
271
+ <strong>Selected: {selectedRows.length} row(s)</strong>
272
+ <pre style={{ marginTop: '0.5rem', fontSize: '0.875rem' }}>
273
+ {JSON.stringify(selectedRows.map(r => r.name), null, 2)}
274
+ </pre>
275
+ </div>
276
+ )}
277
+ </div>
278
+ );
279
+ },
280
+ args: {
281
+ data: users,
282
+ columns,
283
+ sortable: true,
284
+ },
285
+ parameters: {
286
+ docs: {
287
+ description: {
288
+ story: 'DataTable with multiple row selection enabled. Select rows using checkboxes.',
289
+ },
290
+ },
291
+ },
292
+ };
293
+
294
+ // Row selection - single
295
+ export const RowSelectionSingle: Story = {
296
+ render: args => {
297
+ const [selectedRow, setSelectedRow] = useState<any>(null);
298
+
299
+ return (
300
+ <div>
301
+ <DataTable
302
+ {...args}
303
+ selectionMode="single"
304
+ onSelectionChange={(rows) => setSelectedRow(rows[0] || null)}
305
+ />
306
+ {selectedRow && (
307
+ <div style={{ marginTop: '1rem', padding: '1rem', background: '#f5f5f5', borderRadius: '4px' }}>
308
+ <strong>Selected:</strong>
309
+ <pre style={{ marginTop: '0.5rem', fontSize: '0.875rem' }}>
310
+ {JSON.stringify(selectedRow, null, 2)}
311
+ </pre>
312
+ </div>
313
+ )}
314
+ </div>
315
+ );
316
+ },
317
+ args: {
318
+ data: users,
319
+ columns,
320
+ sortable: true,
321
+ },
322
+ parameters: {
323
+ docs: {
324
+ description: {
325
+ story: 'DataTable with single row selection enabled. Select a row using radio buttons.',
326
+ },
327
+ },
328
+ },
329
+ };
330
+
331
+ // Column-specific filtering
332
+ export const ColumnFilters: Story = {
333
+ args: {
334
+ data: users,
335
+ columns: columns.map(col => ({
336
+ ...col,
337
+ filterable: ['name', 'role', 'email'].includes(col.key),
338
+ })),
339
+ columnFilters: true,
340
+ sortable: true,
341
+ },
342
+ parameters: {
343
+ docs: {
344
+ description: {
345
+ story: 'DataTable with column-specific filters. Each filterable column has its own filter input.',
346
+ },
347
+ },
348
+ },
349
+ };
350
+
351
+ // Column resizing
352
+ export const ResizableColumns: Story = {
353
+ args: {
354
+ data: users,
355
+ columns: columns.map(col => ({
356
+ ...col,
357
+ resizable: true,
358
+ minWidth: '100px',
359
+ })),
360
+ resizable: true,
361
+ sortable: true,
362
+ },
363
+ parameters: {
364
+ docs: {
365
+ description: {
366
+ story: 'DataTable with resizable columns. Drag the right edge of column headers to resize.',
367
+ },
368
+ },
369
+ },
370
+ };
371
+
372
+ // Column reordering
373
+ export const ReorderableColumns: Story = {
374
+ args: {
375
+ data: users,
376
+ columns,
377
+ reorderable: true,
378
+ sortable: true,
379
+ },
380
+ parameters: {
381
+ docs: {
382
+ description: {
383
+ story: 'DataTable with reorderable columns. Drag column headers to reorder them.',
384
+ },
385
+ },
386
+ },
387
+ };
388
+
389
+ // Column visibility toggle
390
+ export const ColumnVisibility: Story = {
391
+ args: {
392
+ data: users,
393
+ columns,
394
+ showColumnVisibility: true,
395
+ sortable: true,
396
+ },
397
+ parameters: {
398
+ docs: {
399
+ description: {
400
+ story: 'DataTable with column visibility toggle. Use the Columns button to show/hide columns.',
401
+ },
402
+ },
403
+ },
404
+ };
405
+
406
+ // Export functionality
407
+ export const Exportable: Story = {
408
+ args: {
409
+ data: users,
410
+ columns,
411
+ exportable: true,
412
+ exportFormats: ['csv', 'excel', 'json'],
413
+ exportFilename: 'users',
414
+ sortable: true,
415
+ },
416
+ parameters: {
417
+ docs: {
418
+ description: {
419
+ story: 'DataTable with export functionality. Export data as CSV, Excel, or JSON.',
420
+ },
421
+ },
422
+ },
423
+ };
424
+
425
+ // Sticky headers
426
+ export const StickyHeaders: Story = {
427
+ args: {
428
+ data: largeDataSet,
429
+ columns,
430
+ stickyHeader: true,
431
+ stickyHeaderOffset: '0px',
432
+ sortable: true,
433
+ paginated: true,
434
+ pageSize: 20,
435
+ },
436
+ parameters: {
437
+ docs: {
438
+ description: {
439
+ story: 'DataTable with sticky headers. Headers remain visible when scrolling.',
440
+ },
441
+ },
442
+ },
443
+ };
444
+
445
+ // All advanced features
446
+ export const AllAdvancedFeatures: Story = {
447
+ render: args => {
448
+ const [selectedRows, setSelectedRows] = useState<any[]>([]);
449
+
450
+ return (
451
+ <div>
452
+ <DataTable
453
+ {...args}
454
+ selectionMode="multiple"
455
+ onSelectionChange={(rows) => setSelectedRows(rows)}
456
+ />
457
+ {selectedRows.length > 0 && (
458
+ <div style={{ marginTop: '1rem', padding: '1rem', background: '#f5f5f5', borderRadius: '4px' }}>
459
+ <strong>Selected: {selectedRows.length} row(s)</strong>
460
+ </div>
461
+ )}
462
+ </div>
463
+ );
464
+ },
465
+ args: {
466
+ data: largeDataSet,
467
+ columns: columns.map(col => ({
468
+ ...col,
469
+ filterable: ['name', 'role'].includes(col.key),
470
+ resizable: true,
471
+ })),
472
+ sortable: true,
473
+ filterable: true,
474
+ columnFilters: true,
475
+ paginated: true,
476
+ pageSize: 10,
477
+ striped: true,
478
+ bordered: true,
479
+ resizable: true,
480
+ reorderable: true,
481
+ showColumnVisibility: true,
482
+ exportable: true,
483
+ exportFormats: ['csv', 'excel', 'json'],
484
+ stickyHeader: true,
485
+ },
486
+ parameters: {
487
+ docs: {
488
+ description: {
489
+ story: 'DataTable with all advanced features enabled: selection, filtering, resizing, reordering, visibility toggle, export, and sticky headers.',
490
+ },
491
+ },
492
+ },
493
+ };