@shohojdhara/atomix 0.6.5 → 0.6.7

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 (43) hide show
  1. package/dist/atomix.css +660 -658
  2. package/dist/atomix.css.map +1 -1
  3. package/dist/atomix.min.css +45 -45
  4. package/dist/atomix.min.css.map +1 -1
  5. package/dist/atomix.umd.js +1 -1
  6. package/dist/atomix.umd.js.map +1 -1
  7. package/dist/atomix.umd.min.js +1 -1
  8. package/dist/charts.js +65 -255
  9. package/dist/charts.js.map +1 -1
  10. package/dist/core.js +65 -255
  11. package/dist/core.js.map +1 -1
  12. package/dist/forms.js +65 -255
  13. package/dist/forms.js.map +1 -1
  14. package/dist/heavy.js +65 -255
  15. package/dist/heavy.js.map +1 -1
  16. package/dist/index.d.ts +6 -37
  17. package/dist/index.esm.js +66 -300
  18. package/dist/index.esm.js.map +1 -1
  19. package/dist/index.js +66 -300
  20. package/dist/index.js.map +1 -1
  21. package/dist/index.min.js +1 -1
  22. package/dist/index.min.js.map +1 -1
  23. package/package.json +1 -1
  24. package/src/components/AtomixGlass/AtomixGlass.tsx +0 -9
  25. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +0 -2
  26. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +1 -1
  27. package/src/components/AtomixGlass/glass-utils.ts +82 -53
  28. package/src/components/AtomixGlass/shader-utils.ts +19 -77
  29. package/src/components/Form/Select.test.tsx +6 -6
  30. package/src/components/Form/Textarea.stories.tsx +5 -5
  31. package/src/layouts/Grid/Grid.stories.tsx +54 -2
  32. package/src/layouts/Grid/README.md +6 -2
  33. package/src/lib/composables/useAtomixGlass.ts +2 -134
  34. package/src/lib/composables/useAtomixGlassStyles.ts +3 -3
  35. package/src/lib/composables/usePerformanceMonitor.ts +0 -66
  36. package/src/lib/constants/components.ts +6 -1
  37. package/src/styles/01-settings/_settings.atomix-glass.scss +2 -2
  38. package/src/styles/01-settings/_settings.colors.scss +73 -73
  39. package/src/styles/02-tools/_tools.button.scss +51 -42
  40. package/src/styles/05-objects/_objects.grid.scss +4 -1
  41. package/src/styles/06-components/_components.atomix-glass.scss +2 -2
  42. package/src/components/AtomixGlass/PerformanceDashboard.tsx +0 -171
  43. package/src/components/AtomixGlass/animation-system.ts +0 -578
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shohojdhara/atomix",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "Atomix Design System - A modern component library for web applications",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -180,7 +180,6 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
180
180
  globalMousePosition,
181
181
  mouseOffset,
182
182
  transformStyle,
183
- getShaderTime,
184
183
  handleMouseEnter,
185
184
  handleMouseLeave,
186
185
  handleMouseDown,
@@ -213,13 +212,6 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
213
212
 
214
213
  style,
215
214
  isFixedOrSticky,
216
- withTimeAnimation,
217
- animationSpeed,
218
- withMultiLayerDistortion,
219
- distortionOctaves,
220
- distortionLacunarity,
221
- distortionGain,
222
- distortionQuality,
223
215
  });
224
216
 
225
217
 
@@ -429,7 +421,6 @@ const AtomixGlassInner = forwardRef<HTMLDivElement, AtomixGlassProps>(function A
429
421
  shaderVariant={shaderVariant}
430
422
  withLiquidBlur={withLiquidBlur}
431
423
  isFixedOrSticky={isFixedOrSticky}
432
- shaderTime={getShaderTime()}
433
424
  withTimeAnimation={withTimeAnimation}
434
425
  animationSpeed={animationSpeed}
435
426
  withMultiLayerDistortion={withMultiLayerDistortion}
@@ -77,7 +77,6 @@ interface AtomixGlassContainerProps
77
77
  withLiquidBlur?: boolean;
78
78
  isFixedOrSticky?: boolean;
79
79
  elasticity?: number;
80
- shaderTime?: number;
81
80
 
82
81
  contentRef?: React.RefObject<HTMLDivElement | null>;
83
82
  children?: React.ReactNode;
@@ -117,7 +116,6 @@ export const AtomixGlassContainer = forwardRef<HTMLDivElement, AtomixGlassContai
117
116
  shaderVariant = 'liquidGlass',
118
117
  withLiquidBlur = false,
119
118
  isFixedOrSticky = false,
120
- shaderTime,
121
119
  withTimeAnimation = false,
122
120
  animationSpeed = 1.0,
123
121
  withMultiLayerDistortion = false,
@@ -8,7 +8,7 @@ exports[`AtomixGlass Visual Regression > matches snapshot with default props 1`]
8
8
  >
9
9
  <div
10
10
  class="c-atomix-glass__container"
11
- style="--atomix-glass-container-radius: 16px; --atomix-glass-container-backdrop: blur(20.16px) saturate(250%) contrast(1.02) brightness(1.02); --atomix-glass-container-shadow: inset 0 0.5px 0 rgba(255, 255, 255, 0.32), inset 0 1px 2px rgba(255, 255, 255, 0.06), 0 8px 32px rgba(0, 0, 0, 0.28), 0 2px 8px rgba(0, 0, 0, 0.16); --atomix-glass-container-shadow-opacity: 1; --atomix-glass-container-bg: none; --atomix-glass-container-text-shadow: 0px 2px 12px rgba(0, 0, 0, 0.4); --atomix-glass-container-box-shadow: 0 8px 32px rgba(0, 0, 0, 0.32), 0 2px 8px rgba(0, 0, 0, 0.18);"
11
+ style="--atomix-glass-container-radius: 16px; --atomix-glass-container-backdrop: blur(20.16px) saturate(250%) contrast(1.02) brightness(1.02); --atomix-glass-container-shadow: inset 0 0.5px 0 rgba(255, 255, 255, 0.32), inset 0 1px 2px rgba(255, 255, 255, 0.06), 0 4px 16px rgba(0, 0, 0, 0.12), 0 1px 4px rgba(0, 0, 0, 0.08); --atomix-glass-container-shadow-opacity: 1; --atomix-glass-container-bg: none; --atomix-glass-container-text-shadow: 0px 2px 12px rgba(0, 0, 0, 0.4); --atomix-glass-container-box-shadow: 0 4px 16px rgba(0, 0, 0, 0.14), 0 1px 4px rgba(0, 0, 0, 0.10);"
12
12
  >
13
13
  <div
14
14
  class="c-atomix-glass__inner"
@@ -6,23 +6,15 @@ import { ATOMIX_GLASS } from '../../lib/constants/components';
6
6
  const { CONSTANTS } = ATOMIX_GLASS;
7
7
 
8
8
  /**
9
- * Calculate distance between two points
10
- */
11
- export const calculateDistance = (pos1: MousePosition, pos2: MousePosition): number => {
12
- if (
13
- !pos1 ||
14
- !pos2 ||
15
- typeof pos1.x !== 'number' ||
16
- typeof pos1.y !== 'number' ||
17
- typeof pos2.x !== 'number' ||
18
- typeof pos2.y !== 'number'
19
- ) {
20
- return 0;
21
- }
22
- const deltaX = pos1.x - pos2.x;
23
- const deltaY = pos1.y - pos2.y;
24
- return Math.sqrt(deltaX * deltaX + deltaY * deltaY);
25
- };
9
+ * Canonical 2D vector type shared across the glass math/shader utilities.
10
+ *
11
+ * Structurally identical to {@link MousePosition}; re-exported by `shader-utils`
12
+ * as the public `Vec2`.
13
+ */
14
+ export interface Vec2 {
15
+ x: number;
16
+ y: number;
17
+ }
26
18
 
27
19
  /**
28
20
  * Calculate element center from bounding rect
@@ -251,6 +243,79 @@ export const softClamp = (value: number, max: number): number => {
251
243
  return max * (1 - Math.exp(-value / max));
252
244
  };
253
245
 
246
+ /**
247
+ * Clamp a value to the `[min, max]` range. Returns `min` for non-finite input.
248
+ */
249
+ export const clamp = (value: number, min: number, max: number): number => {
250
+ if (typeof value !== 'number' || isNaN(value)) {
251
+ return min;
252
+ }
253
+ return Math.max(min, Math.min(max, value));
254
+ };
255
+
256
+ /**
257
+ * Two-edge smoothstep (GLSL semantics) — Hermite interpolation between `edge0`
258
+ * and `edge1`. Reuses {@link smoothstep} after normalizing `x` into `[0, 1]`.
259
+ */
260
+ export const smoothstepEdge = (edge0: number, edge1: number, x: number): number => {
261
+ if (typeof edge0 !== 'number' || typeof edge1 !== 'number' || typeof x !== 'number') {
262
+ return 0;
263
+ }
264
+ return smoothstep((x - edge0) / (edge1 - edge0));
265
+ };
266
+
267
+ /**
268
+ * Cubic ease-in-out curve. `t` is clamped to `[0, 1]`.
269
+ */
270
+ export const easeInOutCubic = (t: number): number => {
271
+ if (typeof t !== 'number' || isNaN(t)) {
272
+ return 0;
273
+ }
274
+ const clamped = Math.max(0, Math.min(1, t));
275
+ return clamped < 0.5
276
+ ? 4 * clamped * clamped * clamped
277
+ : 1 - Math.pow(-2 * clamped + 2, 3) / 2;
278
+ };
279
+
280
+ /**
281
+ * Quartic ease-out curve. `t` is clamped to `[0, 1]`.
282
+ */
283
+ export const easeOutQuart = (t: number): number => {
284
+ if (typeof t !== 'number' || isNaN(t)) {
285
+ return 0;
286
+ }
287
+ const clamped = Math.max(0, Math.min(1, t));
288
+ return 1 - Math.pow(1 - clamped, 4);
289
+ };
290
+
291
+ /**
292
+ * Overflow-safe Euclidean length of a 2D vector.
293
+ */
294
+ export const vec2Length = (x: number, y: number): number => {
295
+ if (typeof x !== 'number' || typeof y !== 'number' || isNaN(x) || isNaN(y)) {
296
+ return 0;
297
+ }
298
+ const maxComponent = Math.max(Math.abs(x), Math.abs(y));
299
+ if (maxComponent === 0) return 0;
300
+ const scaledX = x / maxComponent;
301
+ const scaledY = y / maxComponent;
302
+ return maxComponent * Math.sqrt(scaledX * scaledX + scaledY * scaledY);
303
+ };
304
+
305
+ /**
306
+ * Resolves the hover/active intensity multipliers from interaction state.
307
+ *
308
+ * Single source of truth for the `HOVER_INTENSITY` / `ACTIVE_INTENSITY`
309
+ * multipliers shared by the hook and the imperative style updater.
310
+ */
311
+ export const getInteractionIntensity = (
312
+ isHovered: boolean,
313
+ isActive: boolean
314
+ ): { hoverIntensity: number; activeIntensity: number } => ({
315
+ hoverIntensity: isHovered ? CONSTANTS.INTERACTION.HOVER_INTENSITY : 1,
316
+ activeIntensity: isActive ? CONSTANTS.INTERACTION.ACTIVE_INTENSITY : 1,
317
+ });
318
+
254
319
  /**
255
320
  * Spring-damper integration helper
256
321
  * Calculates the next value based on velocity, stiffness, and damping.
@@ -349,42 +414,6 @@ export function pickGlassLayoutStyle(style: CSSProperties): GlassLayoutPosition
349
414
  };
350
415
  }
351
416
 
352
- /**
353
- * Builds inline layout styles for the root wrapper.
354
- *
355
- * Reserved for alternate layout strategies. The default implementation applies
356
- * layout on `.c-atomix-glass__container` to preserve backdrop-filter behavior.
357
- */
358
- export function pickGlassHoistedRootStyle(style: CSSProperties): CSSProperties {
359
- const layout = pickGlassLayoutStyle(style);
360
- const resolvedPosition = (layout.position ?? style.position ?? 'fixed') as CSSProperties['position'];
361
-
362
- return {
363
- position: resolvedPosition,
364
- ...(layout.top !== undefined && { top: layout.top }),
365
- ...(layout.left !== undefined && { left: layout.left }),
366
- ...(layout.right !== undefined && { right: layout.right }),
367
- ...(layout.bottom !== undefined && { bottom: layout.bottom }),
368
- ...(layout.inset !== undefined && { inset: layout.inset }),
369
- };
370
- }
371
-
372
- /**
373
- * Returns `style` without layout properties, preserving visual declarations only.
374
- */
375
- export function omitGlassLayoutStyle(style: CSSProperties): CSSProperties {
376
- const {
377
- position: _p,
378
- top: _t,
379
- left: _l,
380
- right: _r,
381
- bottom: _b,
382
- inset: _inset,
383
- ...visualStyle
384
- } = style;
385
- return visualStyle;
386
- }
387
-
388
417
  /**
389
418
  * Resolves inset custom properties for decorative layers (hover, borders, backgrounds).
390
419
  *
@@ -1,9 +1,15 @@
1
1
  // Adapted from https://github.com/shuding/liquid-glass
2
2
 
3
- export interface Vec2 {
4
- x: number;
5
- y: number;
6
- }
3
+ import {
4
+ clamp,
5
+ easeInOutCubic,
6
+ easeOutQuart,
7
+ smoothstepEdge as smoothStep,
8
+ vec2Length as calculateLength,
9
+ } from './glass-utils';
10
+ import type { Vec2 } from './glass-utils';
11
+
12
+ export type { Vec2 } from './glass-utils';
7
13
 
8
14
  export interface ShaderOptions {
9
15
  width: number;
@@ -34,31 +40,6 @@ const DEFAULT_CANVAS_WIDTH = 256;
34
40
  const DEFAULT_CANVAS_HEIGHT = 256;
35
41
 
36
42
  // Utility functions
37
- const smoothStep = (a: number, b: number, t: number): number => {
38
- // Add input validation
39
- if (typeof a !== 'number' || typeof b !== 'number' || typeof t !== 'number') {
40
- return 0;
41
- }
42
-
43
- const clamped = Math.max(0, Math.min(1, (t - a) / (b - a)));
44
- return clamped * clamped * (3 - 2 * clamped);
45
- };
46
-
47
- const calculateLength = (x: number, y: number): number => {
48
- // Add input validation and error handling
49
- if (typeof x !== 'number' || typeof y !== 'number' || isNaN(x) || isNaN(y)) {
50
- return 0;
51
- }
52
-
53
- // Prevent potential overflow
54
- const maxX = Math.max(Math.abs(x), Math.abs(y));
55
- if (maxX === 0) return 0;
56
-
57
- const scaledX = x / maxX;
58
- const scaledY = y / maxX;
59
- return maxX * Math.sqrt(scaledX * scaledX + scaledY * scaledY);
60
- };
61
-
62
43
  const roundedRectSDF = (
63
44
  x: number,
64
45
  y: number,
@@ -96,42 +77,6 @@ const validateVec2 = (vec: Vec2): boolean => {
96
77
  );
97
78
  };
98
79
 
99
- const clampValue = (value: number, min: number, max: number): number => {
100
- // Add input validation
101
- if (typeof value !== 'number' || typeof min !== 'number' || typeof max !== 'number') {
102
- return min;
103
- }
104
-
105
- if (isNaN(value)) return min;
106
- if (isNaN(min)) return 0;
107
- if (isNaN(max)) return 1;
108
-
109
- return Math.max(min, Math.min(max, value));
110
- };
111
-
112
- // Advanced easing functions for Apple-style smooth animations
113
- const easeInOutCubic = (t: number): number => {
114
- // Add input validation
115
- if (typeof t !== 'number' || isNaN(t)) {
116
- return 0;
117
- }
118
-
119
- const clampedT = Math.max(0, Math.min(1, t));
120
- return clampedT < 0.5
121
- ? 4 * clampedT * clampedT * clampedT
122
- : 1 - Math.pow(-2 * clampedT + 2, 3) / 2;
123
- };
124
-
125
- const easeOutQuart = (t: number): number => {
126
- // Add input validation
127
- if (typeof t !== 'number' || isNaN(t)) {
128
- return 0;
129
- }
130
-
131
- const clampedT = Math.max(0, Math.min(1, t));
132
- return 1 - Math.pow(1 - clampedT, 4);
133
- };
134
-
135
80
  // Perlin-like noise for organic distortion
136
81
  const noise2D = (x: number, y: number): number => {
137
82
  // Add input validation
@@ -503,8 +448,8 @@ export const fragmentShaders = {
503
448
  const finalY = iy + totalDistortionY + chromaticOffset.y * 0.5;
504
449
 
505
450
  return createTexture(
506
- clampValue(finalX * scaled + 0.5, 0, 1),
507
- clampValue(finalY * scaled + 0.5, 0, 1)
451
+ clamp(finalX * scaled + 0.5, 0, 1),
452
+ clamp(finalY * scaled + 0.5, 0, 1)
508
453
  );
509
454
  },
510
455
 
@@ -545,7 +490,7 @@ export const fragmentShaders = {
545
490
  const totalX = ix + (organicX * 0.035 + fluidVelocityX + vortexX) * mask;
546
491
  const totalY = iy + (organicY * 0.035 + fluidVelocityY + vortexY) * mask;
547
492
 
548
- return createTexture(clampValue(totalX + 0.5, 0, 1), clampValue(totalY + 0.5, 0, 1));
493
+ return createTexture(clamp(totalX + 0.5, 0, 1), clamp(totalY + 0.5, 0, 1));
549
494
  },
550
495
 
551
496
  // High-end glass with advanced refraction and depth
@@ -593,7 +538,7 @@ export const fragmentShaders = {
593
538
  const finalX = ix + (refractionX + depthX + organicNoise * 0.015) * edgeMask;
594
539
  const finalY = iy + (refractionY + depthY + organicNoise * 0.015) * edgeMask;
595
540
 
596
- return createTexture(clampValue(finalX + 0.5, 0, 1), clampValue(finalY + 0.5, 0, 1));
541
+ return createTexture(clamp(finalX + 0.5, 0, 1), clamp(finalY + 0.5, 0, 1));
597
542
  },
598
543
 
599
544
  // Metallic liquid effect with shimmer
@@ -630,7 +575,7 @@ export const fragmentShaders = {
630
575
  const totalX = ix + (wave1 + shimmer + Math.cos(flowAngle) * flowEffect) * mask;
631
576
  const totalY = iy + (wave2 + shimmer * 0.8 + Math.sin(flowAngle) * flowEffect) * mask;
632
577
 
633
- return createTexture(clampValue(totalX + 0.5, 0, 1), clampValue(totalY + 0.5, 0, 1));
578
+ return createTexture(clamp(totalX + 0.5, 0, 1), clamp(totalY + 0.5, 0, 1));
634
579
  },
635
580
 
636
581
  // basiBasi - Expert Premium Glass Shader
@@ -784,7 +729,7 @@ export const fragmentShaders = {
784
729
  const finalX = ix + totalDistortionX * 0.85; // Scale down for subtlety
785
730
  const finalY = iy + totalDistortionY * 0.85;
786
731
 
787
- return createTexture(clampValue(finalX + 0.5, 0, 1), clampValue(finalY + 0.5, 0, 1));
732
+ return createTexture(clamp(finalX + 0.5, 0, 1), clamp(finalY + 0.5, 0, 1));
788
733
  },
789
734
 
790
735
  // Aliases for compatibility
@@ -903,13 +848,13 @@ export class ShaderDisplacementGenerator {
903
848
  const g = smoothedDy / maxScale + 0.5;
904
849
 
905
850
  const pixelIndex = (y * w + x) * 4;
906
- data[pixelIndex] = clampValue(r * 255, NORMALIZATION_CLAMP.min, NORMALIZATION_CLAMP.max); // Red channel (X displacement)
907
- data[pixelIndex + 1] = clampValue(
851
+ data[pixelIndex] = clamp(r * 255, NORMALIZATION_CLAMP.min, NORMALIZATION_CLAMP.max); // Red channel (X displacement)
852
+ data[pixelIndex + 1] = clamp(
908
853
  g * 255,
909
854
  NORMALIZATION_CLAMP.min,
910
855
  NORMALIZATION_CLAMP.max
911
856
  ); // Green channel (Y displacement)
912
- data[pixelIndex + 2] = clampValue(
857
+ data[pixelIndex + 2] = clamp(
913
858
  g * 255,
914
859
  NORMALIZATION_CLAMP.min,
915
860
  NORMALIZATION_CLAMP.max
@@ -951,6 +896,3 @@ export class ShaderDisplacementGenerator {
951
896
  return this.canvasDPI;
952
897
  }
953
898
  }
954
-
955
- // Re-export animation system functions for convenience
956
- export { createFBMEngine, liquidGlassWithTime } from './animation-system';
@@ -20,9 +20,9 @@ describe('Select Component', () => {
20
20
  // Check native select options
21
21
  const select = document.querySelector('select');
22
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');
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
26
  });
27
27
 
28
28
  it('renders compound options correctly', async () => {
@@ -43,9 +43,9 @@ describe('Select Component', () => {
43
43
  await waitFor(() => {
44
44
  const select = document.querySelector('select');
45
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');
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
49
  });
50
50
  });
51
51
 
@@ -104,7 +104,7 @@ Textarea component provides a multi-line text input field for longer content. It
104
104
  description: 'Number of visible text lines',
105
105
  table: {
106
106
  type: { summary: 'number' },
107
- defaultValue: { summary: 2 },
107
+ defaultValue: { summary: '2' },
108
108
  },
109
109
  },
110
110
  cols: {
@@ -138,7 +138,7 @@ Textarea component provides a multi-line text input field for longer content. It
138
138
  description: 'Whether the textarea is disabled',
139
139
  table: {
140
140
  type: { summary: 'boolean' },
141
- defaultValue: { summary: false },
141
+ defaultValue: { summary: 'false' },
142
142
  },
143
143
  },
144
144
  invalid: {
@@ -146,7 +146,7 @@ Textarea component provides a multi-line text input field for longer content. It
146
146
  description: 'Whether the textarea is invalid',
147
147
  table: {
148
148
  type: { summary: 'boolean' },
149
- defaultValue: { summary: false },
149
+ defaultValue: { summary: 'false' },
150
150
  },
151
151
  },
152
152
  valid: {
@@ -154,7 +154,7 @@ Textarea component provides a multi-line text input field for longer content. It
154
154
  description: 'Whether the textarea is valid',
155
155
  table: {
156
156
  type: { summary: 'boolean' },
157
- defaultValue: { summary: false },
157
+ defaultValue: { summary: 'false' },
158
158
  },
159
159
  },
160
160
  glass: {
@@ -162,7 +162,7 @@ Textarea component provides a multi-line text input field for longer content. It
162
162
  description: 'Enable glass morphism effect',
163
163
  table: {
164
164
  type: { summary: 'boolean' },
165
- defaultValue: { summary: false },
165
+ defaultValue: { summary: 'false' },
166
166
  },
167
167
  },
168
168
  defaultValue: {
@@ -670,13 +670,47 @@ export const AlignmentAndSpacing: Story = {
670
670
  </div>
671
671
  </div>
672
672
 
673
+ {/* Row Gap */}
674
+ <div className="u-mb-8">
675
+ <h2 className="u-mb-4 u-text-brand-emphasis">Row Gap</h2>
676
+ <p className="u-mb-4 u-text-secondary-emphasis">
677
+ Wrapped rows use vertical spacing from the grid gutter width (`row-gap` via
678
+ `--atomix-gutter-y`):
679
+ </p>
680
+
681
+ <Row className="u-mb-4 u-p-3 u-bg-brand-subtle u-rounded">
682
+ <GridCol xs={4}>
683
+ <DemoCard variant="secondary">Column 1</DemoCard>
684
+ </GridCol>
685
+ <GridCol xs={4}>
686
+ <DemoCard variant="secondary">Column 2</DemoCard>
687
+ </GridCol>
688
+ <GridCol xs={4}>
689
+ <DemoCard variant="secondary">Column 3</DemoCard>
690
+ </GridCol>
691
+ <GridCol xs={4}>
692
+ <DemoCard variant="secondary">Column 4</DemoCard>
693
+ </GridCol>
694
+ <GridCol xs={4}>
695
+ <DemoCard variant="secondary">Column 5</DemoCard>
696
+ </GridCol>
697
+ <GridCol xs={4}>
698
+ <DemoCard variant="secondary">Column 6</DemoCard>
699
+ </GridCol>
700
+ </Row>
701
+ </div>
702
+
673
703
  {/* No Gutters */}
674
704
  <div className="u-mb-8">
675
705
  <h2 className="u-mb-4 u-text-brand-emphasis">No Gutters</h2>
676
- <p className="u-mb-4 u-text-secondary-emphasis">Remove spacing between columns:</p>
706
+ <p className="u-mb-4 u-text-secondary-emphasis">
707
+ Remove horizontal and vertical gutter spacing between columns:
708
+ </p>
677
709
 
678
710
  <div className="u-mb-6">
679
- <h3 className="u-mb-3 u-text-secondary-emphasis u-font-medium">With gutters (default)</h3>
711
+ <h3 className="u-mb-3 u-text-secondary-emphasis u-font-medium">
712
+ With gutters (default)
713
+ </h3>
680
714
  <Row className="u-mb-4 u-p-3 u-bg-brand-subtle u-rounded">
681
715
  <GridCol xs={4}>
682
716
  <DemoCard variant="secondary">Column 1</DemoCard>
@@ -687,6 +721,15 @@ export const AlignmentAndSpacing: Story = {
687
721
  <GridCol xs={4}>
688
722
  <DemoCard variant="secondary">Column 3</DemoCard>
689
723
  </GridCol>
724
+ <GridCol xs={4}>
725
+ <DemoCard variant="secondary">Column 4</DemoCard>
726
+ </GridCol>
727
+ <GridCol xs={4}>
728
+ <DemoCard variant="secondary">Column 5</DemoCard>
729
+ </GridCol>
730
+ <GridCol xs={4}>
731
+ <DemoCard variant="secondary">Column 6</DemoCard>
732
+ </GridCol>
690
733
  </Row>
691
734
  </div>
692
735
 
@@ -702,6 +745,15 @@ export const AlignmentAndSpacing: Story = {
702
745
  <GridCol xs={4}>
703
746
  <DemoCard variant="accent">Column 3</DemoCard>
704
747
  </GridCol>
748
+ <GridCol xs={4}>
749
+ <DemoCard variant="accent">Column 4</DemoCard>
750
+ </GridCol>
751
+ <GridCol xs={4}>
752
+ <DemoCard variant="accent">Column 5</DemoCard>
753
+ </GridCol>
754
+ <GridCol xs={4}>
755
+ <DemoCard variant="accent">Column 6</DemoCard>
756
+ </GridCol>
705
757
  </Row>
706
758
  </div>
707
759
  </div>
@@ -30,9 +30,11 @@ import { Grid, GridCol } from '@/layouts/Grid';
30
30
  - Adds utility class `u-justify-content-[value]`
31
31
  - `alignItems`: Control vertical alignment ('start', 'end', 'center', 'baseline', 'stretch')
32
32
  - Adds utility class `u-align-items-[value]`
33
- - `noGutters`: Remove gutters between columns
33
+ - `noGutters`: Remove horizontal and vertical gutter spacing
34
34
  - Adds modifier class `o-grid--no-gutters`
35
35
 
36
+ Grids apply `row-gap` from `--atomix-gutter-y` (defaults to `$grid-gutter-width`) when columns wrap to multiple rows. Horizontal gutters use the existing column padding model. Override vertical spacing with `--atomix-gutter-y` on any `.o-grid`.
37
+
36
38
  ### GridCol
37
39
 
38
40
  Column component with responsive sizing options.
@@ -89,9 +91,11 @@ import { Container, Row, GridCol } from '@/layouts/Grid';
89
91
  - Adds utility class `u-justify-content-[value]`
90
92
  - `alignItems`: Control vertical alignment ('start', 'end', 'center', 'baseline', 'stretch')
91
93
  - Adds utility class `u-align-items-[value]`
92
- - `noGutters`: Remove gutters between columns
94
+ - `noGutters`: Remove horizontal and vertical gutter spacing
93
95
  - Adds modifier class `o-grid--no-gutters`
94
96
 
97
+ Grids apply `row-gap` from `--atomix-gutter-y` (defaults to `$grid-gutter-width`) when columns wrap to multiple rows. Horizontal gutters use the existing column padding model. Override vertical spacing with `--atomix-gutter-y` on any `.o-grid`.
98
+
95
99
  ## Breakpoints
96
100
 
97
101
  The grid system uses the following breakpoints: