@octaviaflow/grid 1.0.0

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.
@@ -0,0 +1,484 @@
1
+ //
2
+ // Copyright OctaviaFlow. 2025
3
+ //
4
+ // This source code is licensed under the Apache-2.0 license found in the
5
+ // LICENSE file in the root directory of this source tree.
6
+ //
7
+
8
+ @use 'sass:list';
9
+ @use 'sass:map';
10
+ @use 'sass:math';
11
+ @use 'sass:meta';
12
+ @use 'config' as *;
13
+ @use 'breakpoint' as *;
14
+
15
+ /// Emit all the styles related to the CSS Grid, these include:
16
+ /// - The base grid class
17
+ /// - The various gutter modes
18
+ /// - The ability to specifiy column span
19
+ /// - The ability to specifiy column start,end position
20
+ /// - Support for subgrid
21
+ /// - Support for hanging content on a grid column
22
+ /// - Support for controlling grid gutter on a cell basis
23
+ /// - Support for specifying content alignment
24
+ ///
25
+ ///
26
+ /// In general, this mixin is structured in a way to emit the fewest CSS styles
27
+ /// as possible. To do this, we will (as much as possible) not emit a value if
28
+ /// one is already defined at a previous breakpoint.
29
+ ///
30
+ /// In addition, this mixin will break down emitting styles into several stages:
31
+ /// 1. Emit styles for the smallest breakpoint without any breakpoint. By
32
+ /// default, these styles will be on
33
+ /// 2. For every other breakpoint, wrap it in a breakpoint so that it only is
34
+ /// triggered when that breakpoint is applied
35
+ /// 3. In situations where it is appropriate, we also will emit "unconditional"
36
+ /// selectors that will always apply. For example, if you wanted a column to
37
+ /// always span four columns
38
+ @mixin css-grid($breakpoints: $grid-breakpoints) {
39
+ /// The :root selector is responsible for setting several top-level CSS Custom
40
+ /// Properties, including the overall grid gutter, grid column count, and grid
41
+ /// margin
42
+ :root {
43
+ --cds-grid-gutter: #{$grid-gutter};
44
+
45
+ // Iterate through the grid breakpoints and only emit the grid-columns and
46
+ // grid-margin CSS Custom Properties if they've changed from the previous
47
+ // breakpoint. By default, we emit the smallest breakpoint values on the
48
+ // :root selector
49
+ @each $key, $value in $breakpoints {
50
+ @if is-smallest-breakpoint($key, $breakpoints) {
51
+ --cds-grid-columns: #{get-column-count($breakpoints, $key)};
52
+ --cds-grid-margin: #{get-margin($breakpoints, $key)};
53
+ } @else {
54
+ $previous-breakpoint: breakpoint-prev($key, $breakpoints);
55
+ $changes: ();
56
+
57
+ @if get-column-count($breakpoints, $key) !=
58
+ get-column-count($breakpoints, $previous-breakpoint)
59
+ {
60
+ $changes: map.set(
61
+ $changes,
62
+ grid-columns,
63
+ get-column-count($breakpoints, $key)
64
+ );
65
+ }
66
+
67
+ @if get-margin($breakpoints, $key) !=
68
+ get-margin($breakpoints, $previous-breakpoint)
69
+ {
70
+ $changes: map.set(
71
+ $changes,
72
+ grid-margin,
73
+ get-margin($breakpoints, $key)
74
+ );
75
+ }
76
+
77
+ @include breakpoint($key) {
78
+ @each $name, $value in $changes {
79
+ --cds-#{$name}: #{$value};
80
+ }
81
+ }
82
+ }
83
+ }
84
+ }
85
+
86
+ // -----------------------------------------------------------------------------
87
+ // Base CSS Grid
88
+ // -----------------------------------------------------------------------------
89
+ .#{$prefix}--css-grid {
90
+ --cds-grid-gutter-start: calc(var(--cds-grid-gutter) / 2);
91
+ --cds-grid-gutter-end: calc(var(--cds-grid-gutter) / 2);
92
+ // We split out a separate "column hang" since "gutter-start" is set
93
+ // dynamically and could be 0
94
+ --cds-grid-column-hang: calc(var(--cds-grid-gutter) / 2);
95
+
96
+ display: grid;
97
+ grid-template-columns: repeat(var(--cds-grid-columns), minmax(0, 1fr));
98
+ inline-size: 100%;
99
+ margin-inline: auto;
100
+ max-inline-size: get-grid-width(
101
+ $breakpoints,
102
+ largest-breakpoint-name($breakpoints)
103
+ );
104
+ padding-inline: var(--cds-grid-margin);
105
+ }
106
+
107
+ // -----------------------------------------------------------------------------
108
+ // Full width
109
+ // -----------------------------------------------------------------------------
110
+ .#{$prefix}--css-grid--full-width {
111
+ max-inline-size: 100%;
112
+ }
113
+
114
+ // -----------------------------------------------------------------------------
115
+ // Column
116
+ // -----------------------------------------------------------------------------
117
+
118
+ // Add gutter to columns in a CSS Grid. Unfortunately, we cannot use
119
+ // `grid-gap`, `column-gap`, etc. as we need to conditionally remove leading
120
+ // and trailing gutter from a column.
121
+ .#{$prefix}--css-grid-column {
122
+ // grid-mode-start, grid-mode-end are meant to capture the "grid settings"
123
+ // so that subgrids can correctly fit in parent grids by reverting the
124
+ // grid's outer padding
125
+ --cds-grid-mode-start: var(--cds-grid-gutter-start);
126
+ --cds-grid-mode-end: var(--cds-grid-gutter-end);
127
+
128
+ margin-inline: var(--cds-grid-gutter-start) var(--cds-grid-gutter-end);
129
+
130
+ [dir='rtl'] & {
131
+ margin-inline: var(--cds-grid-gutter-end) var(--cds-grid-gutter-start);
132
+ }
133
+ }
134
+
135
+ // -----------------------------------------------------------------------------
136
+ // Gutter modes
137
+ // -----------------------------------------------------------------------------
138
+
139
+ // Narrow
140
+ .#{$prefix}--css-grid--narrow {
141
+ --cds-grid-gutter-start: 0;
142
+ }
143
+
144
+ // Condensed
145
+ .#{$prefix}--css-grid--condensed {
146
+ --cds-grid-gutter: #{$grid-gutter-condensed};
147
+ --cds-grid-column-hang: #{math.div($grid-gutter, 2) - math.div(
148
+ $grid-gutter-condensed,
149
+ 2
150
+ )};
151
+ }
152
+
153
+ // -----------------------------------------------------------------------------
154
+ // Alignment
155
+ // -----------------------------------------------------------------------------
156
+
157
+ // Start
158
+ .#{$prefix}--css-grid--start {
159
+ margin-inline-start: 0;
160
+ }
161
+
162
+ // End
163
+ .#{$prefix}--css-grid--end {
164
+ margin-inline-end: 0;
165
+ }
166
+
167
+ // -----------------------------------------------------------------------------
168
+ // Subgrid
169
+ // -----------------------------------------------------------------------------
170
+ .#{$prefix}--subgrid {
171
+ display: grid;
172
+ grid-template-columns: repeat(var(--cds-grid-columns), minmax(0, 1fr));
173
+ margin-inline: calc(var(--cds-grid-mode-start) * -1)
174
+ calc(var(--cds-grid-mode-end) * -1);
175
+
176
+ [dir='rtl'] & {
177
+ margin-inline: calc(var(--cds-grid-mode-end) * -1)
178
+ calc(var(--cds-grid-mode-start) * -1);
179
+ }
180
+ }
181
+
182
+ // Support the gutter modes in subgrids
183
+ .#{$prefix}--subgrid--wide {
184
+ --cds-grid-gutter-start: #{math.div($grid-gutter, 2)};
185
+ --cds-grid-gutter-end: #{math.div($grid-gutter, 2)};
186
+ --cds-grid-column-hang: 0;
187
+ }
188
+
189
+ .#{$prefix}--subgrid--narrow {
190
+ --cds-grid-gutter-start: 0;
191
+ --cds-grid-gutter-end: #{math.div($grid-gutter, 2)};
192
+ --cds-grid-column-hang: #{math.div($grid-gutter, 2)};
193
+ }
194
+
195
+ .#{$prefix}--subgrid--condensed {
196
+ --cds-grid-gutter-start: #{math.div($grid-gutter-condensed, 2)};
197
+ --cds-grid-gutter-end: #{math.div($grid-gutter-condensed, 2)};
198
+ --cds-grid-column-hang: #{math.div($grid-gutter, 2) - math.div(
199
+ $grid-gutter-condensed,
200
+ 2
201
+ )};
202
+ }
203
+
204
+ // -----------------------------------------------------------------------------
205
+ // Column hang
206
+ // -----------------------------------------------------------------------------
207
+
208
+ // Helper class to allow for text alignment in columns where the leading
209
+ // gutter is missing (like narrow) or is reduced (like in condensed).
210
+ .#{$prefix}--grid-column-hang {
211
+ margin-inline-start: var(--cds-grid-column-hang);
212
+
213
+ [dir='rtl'] & {
214
+ margin-inline: initial var(--cds-grid-column-hang);
215
+ }
216
+ }
217
+
218
+ // -----------------------------------------------------------------------------
219
+ // Column span
220
+ // -----------------------------------------------------------------------------
221
+
222
+ // Generate col-span-{0-16} classes which unconditionally set column span
223
+ // regardless of breakpoint
224
+ @for $i from 0 through get-grid-columns($breakpoints) {
225
+ .#{$prefix}--col-span-#{$i} {
226
+ @include -column-span($i);
227
+ }
228
+ }
229
+
230
+ // Responsive column span
231
+ @each $name, $value in $breakpoints {
232
+ // Column span per breakpoint
233
+ @for $i from 0 through get-column-count($breakpoints, $name) {
234
+ @if is-smallest-breakpoint($name, $breakpoints) {
235
+ .#{$prefix}--#{$name}\:col-span-#{$i} {
236
+ @include -column-span($i);
237
+ }
238
+ } @else {
239
+ @include breakpoint($name) {
240
+ .#{$prefix}--#{$name}\:col-span-#{$i} {
241
+ @include -column-span($i);
242
+ }
243
+ }
244
+ }
245
+ }
246
+
247
+ // Percent column span per breakpoint
248
+ @if is-smallest-breakpoint($name, $breakpoints) {
249
+ .#{$prefix}--#{$name}\:col-span-auto {
250
+ grid-column: auto;
251
+ }
252
+
253
+ .#{$prefix}--#{$name}\:col-span-100 {
254
+ grid-column: 1 / -1;
255
+ }
256
+
257
+ $columns: get-column-count($breakpoints, $name);
258
+
259
+ .#{$prefix}--#{$name}\:col-span-75 {
260
+ $span: $columns * 0.75;
261
+ --cds-grid-columns: #{$span};
262
+
263
+ grid-column: span list.slash($span, span) #{$span};
264
+ }
265
+
266
+ .#{$prefix}--#{$name}\:col-span-50 {
267
+ $span: $columns * 0.5;
268
+ --cds-grid-columns: #{$span};
269
+
270
+ grid-column: span list.slash($span, span) #{$span};
271
+ }
272
+
273
+ .#{$prefix}--#{$name}\:col-span-25 {
274
+ $span: $columns * 0.25;
275
+ --cds-grid-columns: #{$span};
276
+
277
+ grid-column: span list.slash($span, span) #{$span};
278
+ }
279
+ } @else {
280
+ @include breakpoint($name) {
281
+ .#{$prefix}--#{$name}\:col-span-auto {
282
+ grid-column: auto;
283
+ }
284
+
285
+ .#{$prefix}--#{$name}\:col-span-100 {
286
+ grid-column: 1 / -1;
287
+ }
288
+
289
+ $columns: get-column-count($breakpoints, $name);
290
+
291
+ .#{$prefix}--#{$name}\:col-span-75 {
292
+ $span: $columns * 0.75;
293
+ --cds-grid-columns: #{$span};
294
+
295
+ grid-column: span list.slash($span, span) #{$span};
296
+ }
297
+
298
+ .#{$prefix}--#{$name}\:col-span-50 {
299
+ $span: $columns * 0.5;
300
+ --cds-grid-columns: #{$span};
301
+
302
+ grid-column: span list.slash($span, span) #{$span};
303
+ }
304
+
305
+ .#{$prefix}--#{$name}\:col-span-25 {
306
+ $span: $columns * 0.25;
307
+ --cds-grid-columns: #{$span};
308
+
309
+ grid-column: span list.slash($span, span) #{$span};
310
+ }
311
+ }
312
+ }
313
+ }
314
+
315
+ // -----------------------------------------------------------------------------
316
+ // Column percent span
317
+ // -----------------------------------------------------------------------------
318
+ .#{$prefix}--col-span-auto {
319
+ grid-column: auto;
320
+ }
321
+
322
+ .#{$prefix}--col-span-100 {
323
+ grid-column: 1 / -1;
324
+ }
325
+
326
+ .#{$prefix}--col-span-75 {
327
+ @include -percent-column-span($breakpoints, 0.75);
328
+ }
329
+
330
+ .#{$prefix}--col-span-50 {
331
+ @include -percent-column-span($breakpoints, 0.5);
332
+ }
333
+
334
+ .#{$prefix}--col-span-25 {
335
+ @include -percent-column-span($breakpoints, 0.25);
336
+ }
337
+
338
+ // -----------------------------------------------------------------------------
339
+ // Column offset
340
+ // -----------------------------------------------------------------------------
341
+ // Unconditional column start
342
+ // Note: we start at 1 and end at column-count to match grid lines. We do not
343
+ // start at column-count + 1 since starting at the end of the grid would mean
344
+ // a column would have no width available
345
+ @for $i from 1 through get-grid-columns($breakpoints) {
346
+ .#{$prefix}--col-start-#{$i} {
347
+ grid-column-start: $i;
348
+ }
349
+ }
350
+
351
+ // Unconditional column end
352
+ // Note: we start at 2 since a column ending at line 1 would have no width. We
353
+ // end at column-count + 1 since grid lines start at 1
354
+ @for $i from 2 through get-grid-columns($breakpoints) + 1 {
355
+ .#{$prefix}--col-end-#{$i} {
356
+ grid-column-end: $i;
357
+ }
358
+ }
359
+
360
+ .#{$prefix}--col-start-auto {
361
+ grid-column-start: auto;
362
+ }
363
+
364
+ .#{$prefix}--col-end-auto {
365
+ grid-column-end: auto;
366
+ }
367
+
368
+ // Responsive column start, end
369
+ @each $name, $value in $breakpoints {
370
+ @if is-smallest-breakpoint($name, $breakpoints) {
371
+ // Responsive column start
372
+ @for $i from 1 through get-grid-columns($breakpoints) {
373
+ .#{$prefix}--#{$name}\:col-start-#{$i} {
374
+ grid-column-start: $i;
375
+ }
376
+ }
377
+
378
+ // Responsive column end
379
+ @for $i from 2 through get-grid-columns($breakpoints) + 1 {
380
+ .#{$prefix}--#{$name}\:col-end-#{$i} {
381
+ grid-column-end: $i;
382
+ }
383
+ }
384
+
385
+ .#{$prefix}--#{$name}\:col-start-auto {
386
+ grid-column-start: auto;
387
+ }
388
+
389
+ .#{$prefix}--#{$name}\:col-end-auto {
390
+ grid-column-end: auto;
391
+ }
392
+ } @else {
393
+ @include breakpoint($name) {
394
+ // Responsive column start
395
+ @for $i from 1 through get-grid-columns($breakpoints) {
396
+ .#{$prefix}--#{$name}\:col-start-#{$i} {
397
+ grid-column-start: $i;
398
+ }
399
+ }
400
+
401
+ // Responsive column end
402
+ @for $i from 2 through get-grid-columns($breakpoints) + 1 {
403
+ .#{$prefix}--#{$name}\:col-end-#{$i} {
404
+ grid-column-end: $i;
405
+ }
406
+ }
407
+
408
+ .#{$prefix}--#{$name}\:col-start-auto {
409
+ grid-column-start: auto;
410
+ }
411
+
412
+ .#{$prefix}--#{$name}\:col-end-auto {
413
+ grid-column-end: auto;
414
+ }
415
+ }
416
+ }
417
+ }
418
+ }
419
+
420
+ /// Generate the styles for a grid column
421
+ @mixin -column-span($i) {
422
+ @if $i == 0 {
423
+ display: none;
424
+ } @else {
425
+ --cds-grid-columns: #{$i};
426
+
427
+ display: block;
428
+ grid-column: span $i / span $i;
429
+ }
430
+ }
431
+
432
+ /// Generate the styles for an unconditional class that represents a percent
433
+ /// span of a grid
434
+ @mixin -percent-column-span($breakpoints, $percent) {
435
+ @each $key, $value in $breakpoints {
436
+ $columns: get-column-count($breakpoints, $key);
437
+ $span: $columns * $percent;
438
+
439
+ @if is-smallest-breakpoint($key, $breakpoints) {
440
+ --cds-grid-columns: #{$span};
441
+
442
+ grid-column: span list.slash($span, span) #{$span};
443
+ } @else {
444
+ $previous-breakpoint: breakpoint-prev($key, $breakpoints);
445
+ $previous-column-count: get-column-count(
446
+ $breakpoints,
447
+ $previous-breakpoint
448
+ );
449
+ $previous-span: $previous-column-count * $percent;
450
+
451
+ @if $span != $previous-span {
452
+ @include breakpoint($key) {
453
+ --cds-grid-columns: #{$span};
454
+
455
+ grid-column: span list.slash($span, span) #{$span};
456
+ }
457
+ }
458
+ }
459
+ }
460
+ }
461
+
462
+ /// Get the grid width for a specific breakpoint name
463
+ @function get-grid-width($breakpoints, $breakpoint) {
464
+ @return map.get(map.get($breakpoints, $breakpoint), width);
465
+ }
466
+
467
+ /// Get the grid column count for a specific breakpoint name
468
+ @function get-column-count($breakpoints, $breakpoint) {
469
+ @return map.get(map.get($breakpoints, $breakpoint), columns);
470
+ }
471
+
472
+ /// Get the grid margin for a specific breakpoint name
473
+ @function get-margin($breakpoints, $breakpoint) {
474
+ $value: map.get(map.get($breakpoints, $breakpoint), margin);
475
+ @if $value == 0 {
476
+ @return 0;
477
+ }
478
+ @return $value;
479
+ }
480
+
481
+ /// Return the largest column count from a set of breakpoints
482
+ @function get-grid-columns($breakpoints) {
483
+ @return get-column-count($breakpoints, largest-breakpoint-name($breakpoints));
484
+ }