@ids-group-ltd/ids-design-system 0.2.2 → 0.4.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.
- package/README.md +2 -2
- package/fesm2022/ids-group-ltd-ids-design-system.mjs +3208 -531
- package/fesm2022/ids-group-ltd-ids-design-system.mjs.map +1 -1
- package/package.json +4 -3
- package/schematics/collection.json +10 -0
- package/schematics/ng-add/index.js +47 -0
- package/schematics/ng-add/schema.json +13 -0
- package/styles/_breakpoints.scss +50 -0
- package/styles/_dropdown-overlay.scss +4 -3
- package/styles/_ds-color.scss +36 -0
- package/styles/_fonts.scss +11 -5
- package/styles/_layout-utils.scss +2 -2
- package/styles/_link.scss +3 -2
- package/styles/_overlay-motion.scss +18 -0
- package/styles/_page-grid.scss +6 -3
- package/styles/_reset.scss +6 -1
- package/styles/_scrollbar.scss +2 -1
- package/styles/_theme-activation.scss +49 -0
- package/styles/_tokens-charts.scss +18 -18
- package/styles/_tokens.scss +74 -25
- package/styles/_typography.scss +21 -21
- package/styles/ds.scss +15 -9
- package/themes/README.md +72 -58
- package/themes/{default/_palette.scss → _base-palette.scss} +67 -50
- package/themes/{default/_theme.scss → _semantic.scss} +103 -87
- package/themes/dark/_palette.scss +39 -0
- package/themes/dark/_theme.scss +122 -0
- package/themes/light/_palette.scss +34 -0
- package/themes/light/_theme.scss +15 -0
- package/types/ids-group-ltd-ids-design-system.d.ts +738 -36
package/styles/_tokens.scss
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
Design Tokens — Tier 3 component + scale layer.
|
|
3
3
|
|
|
4
4
|
Tier 1 colour primitives (ramps + HSL channels) live in
|
|
5
|
-
../themes/
|
|
5
|
+
../themes/light/_palette.scss. Tier 2 semantic role assignments
|
|
6
6
|
(surface, border, text, primary/secondary/tertiary, status, focus)
|
|
7
|
-
live in ../themes/
|
|
7
|
+
live in ../themes/light/_theme.scss.
|
|
8
8
|
|
|
9
9
|
THIS file owns the theme-INDEPENDENT layer:
|
|
10
10
|
· scales — spacing, radius, sizes, hit targets, breakpoints
|
|
@@ -43,6 +43,12 @@
|
|
|
43
43
|
--space-24: 96px;
|
|
44
44
|
--space-32: 128px;
|
|
45
45
|
|
|
46
|
+
/* Semantic spacing aliases — default rhythm for the layout primitives
|
|
47
|
+
(ds-stack / ds-inline). Components default their gap to these so the
|
|
48
|
+
vertical/horizontal rhythm retunes in one place. */
|
|
49
|
+
--space-stack: var(--space-4); /* 16px — vertical rhythm between stacked blocks */
|
|
50
|
+
--space-inline: var(--space-2); /* 8px — horizontal cluster gap */
|
|
51
|
+
|
|
46
52
|
/* ============================================================
|
|
47
53
|
Radius — six-stop scale.
|
|
48
54
|
Each step is functionally distinct; adjacent steps are
|
|
@@ -84,17 +90,18 @@
|
|
|
84
90
|
The alpha values stay constant across themes — only the
|
|
85
91
|
hue/saturation/lightness become configurable.
|
|
86
92
|
============================================================ */
|
|
87
|
-
|
|
93
|
+
|
|
94
|
+
/* --shadow-tint-h/s/l live in themes/light/_palette.scss — palette-level
|
|
88
95
|
so dark palettes can override --shadow-tint-l → ~95% for soft light glows. */
|
|
89
96
|
|
|
90
|
-
--shadow-1: 0 1px 2px
|
|
91
|
-
0 1px 3px
|
|
92
|
-
--shadow-2: 0 4px 8px
|
|
93
|
-
0 4px 16px
|
|
94
|
-
--shadow-3: 0 8px 16px
|
|
95
|
-
0 16px 32px
|
|
96
|
-
--shadow-4: 0 16px 32px
|
|
97
|
-
0 24px 48px
|
|
97
|
+
--shadow-1: 0 1px 2px hsl(var(--shadow-tint-h) var(--shadow-tint-s) var(--shadow-tint-l) / 6%),
|
|
98
|
+
0 1px 3px hsl(var(--shadow-tint-h) var(--shadow-tint-s) var(--shadow-tint-l) / 8%);
|
|
99
|
+
--shadow-2: 0 4px 8px hsl(var(--shadow-tint-h) var(--shadow-tint-s) var(--shadow-tint-l) / 6%),
|
|
100
|
+
0 4px 16px hsl(var(--shadow-tint-h) var(--shadow-tint-s) var(--shadow-tint-l) / 10%);
|
|
101
|
+
--shadow-3: 0 8px 16px hsl(var(--shadow-tint-h) var(--shadow-tint-s) var(--shadow-tint-l) / 8%),
|
|
102
|
+
0 16px 32px hsl(var(--shadow-tint-h) var(--shadow-tint-s) var(--shadow-tint-l) / 14%);
|
|
103
|
+
--shadow-4: 0 16px 32px hsl(var(--shadow-tint-h) var(--shadow-tint-s) var(--shadow-tint-l) / 12%),
|
|
104
|
+
0 24px 48px hsl(var(--shadow-tint-h) var(--shadow-tint-s) var(--shadow-tint-l) / 18%);
|
|
98
105
|
--shadow-popover: var(--shadow-2);
|
|
99
106
|
--shadow-modal: var(--shadow-4);
|
|
100
107
|
--shadow-toast: var(--shadow-3);
|
|
@@ -169,16 +176,13 @@
|
|
|
169
176
|
--opacity-scrim: 0.55; /* modal/drawer backdrop overlay (light theme) */
|
|
170
177
|
--opacity-state-hover: 0.08; /* hover layer over surface (overlay vs surface swap) */
|
|
171
178
|
--opacity-state-pressed: 0.16; /* active/pressed layer */
|
|
179
|
+
--opacity-state-active: 0.24; /* deepest pressed layer (e.g. tag close on :active) */
|
|
172
180
|
|
|
173
181
|
/* ============================================================
|
|
174
|
-
Breakpoints
|
|
182
|
+
Breakpoints — moved to _breakpoints.scss, the single source for both
|
|
183
|
+
the --bp-* custom properties (still emitted, now from that partial)
|
|
184
|
+
AND the $bp-* Sass aliases usable inside @media.
|
|
175
185
|
============================================================ */
|
|
176
|
-
--bp-xs: 480px;
|
|
177
|
-
--bp-sm: 640px;
|
|
178
|
-
--bp-md: 768px;
|
|
179
|
-
--bp-lg: 1024px;
|
|
180
|
-
--bp-xl: 1441px; /* Page-grid wide-tier threshold (12 cols, container caps at --col-cap-content). */
|
|
181
|
-
--bp-2xl:1536px;
|
|
182
186
|
|
|
183
187
|
/* ============================================================
|
|
184
188
|
Page grid — tier-specific gutters + margins.
|
|
@@ -202,13 +206,13 @@
|
|
|
202
206
|
--scrollbar-thumb: var(--neutral-300);
|
|
203
207
|
--scrollbar-thumb-hover: var(--neutral-400);
|
|
204
208
|
--scrollbar-thumb-active: var(--neutral-500);
|
|
205
|
-
--select-bg: color-mix(in srgb, var(--primary) 22%, transparent);
|
|
209
|
+
--select-bg-color: color-mix(in srgb, var(--primary) 22%, transparent);
|
|
206
210
|
--col-cap-text: 720px;
|
|
207
211
|
--col-cap-content: 1280px;
|
|
208
212
|
|
|
209
213
|
/* ============================================================
|
|
210
214
|
Typography scale (theme-independent). Font FAMILIES are a theme
|
|
211
|
-
choice and live in themes/
|
|
215
|
+
choice and live in themes/light/_theme.scss (--font-sans/mono/
|
|
212
216
|
display/heading); the size/weight/leading/tracking scale below is
|
|
213
217
|
constant across themes.
|
|
214
218
|
============================================================ */
|
|
@@ -282,6 +286,15 @@
|
|
|
282
286
|
default their own --ds-*-pad to this, so a consumer retunes all at once. */
|
|
283
287
|
--ds-container-pad: var(--space-6);
|
|
284
288
|
|
|
289
|
+
/* Shared focus indicators — components default their own --ds-*-focus-shadow
|
|
290
|
+
to these, so a consumer retunes focus per-group at once (field vs control)
|
|
291
|
+
or per-component. Two families: fields get the soft halo, interactive
|
|
292
|
+
controls/nav get the crisp ring (see themes/light/_theme.scss). */
|
|
293
|
+
--ds-field-focus-shadow: var(--focus-field);
|
|
294
|
+
--ds-field-focus-shadow-error: var(--focus-field-error);
|
|
295
|
+
--ds-control-focus-shadow: var(--focus-ring);
|
|
296
|
+
--ds-control-focus-shadow-error: var(--focus-ring-error);
|
|
297
|
+
|
|
285
298
|
/* Table cell padding — density-aware, consumed by .tbl td/th. */
|
|
286
299
|
--table-cell-pad-x: var(--space-4);
|
|
287
300
|
--table-cell-pad-y: var(--space-3);
|
|
@@ -367,7 +380,7 @@
|
|
|
367
380
|
--popover-maxh: 360px;
|
|
368
381
|
|
|
369
382
|
/* Field */
|
|
370
|
-
--field-gap
|
|
383
|
+
--field-label-gap: var(--space-1-5); /* field vertical rhythm: label↔control & control↔hint */
|
|
371
384
|
--field-gap-req: var(--space-1); /* label↔asterisk */
|
|
372
385
|
--field-h-sm: var(--hit-min);
|
|
373
386
|
--field-h-md: var(--hit-cozy);
|
|
@@ -383,6 +396,19 @@
|
|
|
383
396
|
--search-h: var(--hit-md);
|
|
384
397
|
--search-maxw: 320px;
|
|
385
398
|
|
|
399
|
+
/* OTP / PIN input — one box per digit. */
|
|
400
|
+
--otp-cell-w: var(--space-12); /* 48px */
|
|
401
|
+
--otp-cell-h: var(--space-14); /* 56px */
|
|
402
|
+
--otp-gap: var(--space-2); /* 8px between cells */
|
|
403
|
+
|
|
404
|
+
/* Color picker */
|
|
405
|
+
--color-picker-swatch: var(--space-6); /* 24px preset swatch */
|
|
406
|
+
--color-picker-preview: var(--space-10); /* 40px panel preview */
|
|
407
|
+
--color-picker-panel-w: 240px; /* picker overlay width */
|
|
408
|
+
--color-picker-sv-h: 160px; /* saturation/value plane height */
|
|
409
|
+
--color-picker-hue-h: var(--space-3); /* 12px hue slider height */
|
|
410
|
+
--color-picker-thumb: 14px; /* draggable thumb diameter */
|
|
411
|
+
|
|
386
412
|
/* ============================================================
|
|
387
413
|
3b · COMPONENT-SCOPED dimensions (Phase 4 additions)
|
|
388
414
|
Tokens for atom geometries that previously lived as literals
|
|
@@ -433,6 +459,11 @@
|
|
|
433
459
|
--menu-minw: 200px; /* dropdown menu min width */
|
|
434
460
|
--menu-sep-h: var(--border-width-default);
|
|
435
461
|
|
|
462
|
+
/* List / list-item — density-aware row padding + leading/trailing gap. */
|
|
463
|
+
--list-item-pad-y: var(--space-3); /* 12px */
|
|
464
|
+
--list-item-pad-x: var(--space-4); /* 16px */
|
|
465
|
+
--list-item-gap: var(--space-3); /* 12px leading↔body↔trailing */
|
|
466
|
+
|
|
436
467
|
/* Tooltip */
|
|
437
468
|
--tooltip-maxw: 240px;
|
|
438
469
|
--tooltip-arrow: var(--space-2); /* 8px — caret edge length, halved for offset */
|
|
@@ -502,6 +533,16 @@
|
|
|
502
533
|
--calendar-bar-h: var(--space-8); /* 32px */
|
|
503
534
|
--calendar-bar-inset-y: var(--space-2); /* 8px from top of cell */
|
|
504
535
|
|
|
536
|
+
/* Timeline / activity feed */
|
|
537
|
+
--timeline-node-size: var(--hit-sm); /* 28px event node */
|
|
538
|
+
--timeline-spine-w: var(--border-width-strong); /* 2px connector spine */
|
|
539
|
+
--timeline-gap: var(--space-5); /* vertical gap between events */
|
|
540
|
+
|
|
541
|
+
/* Tree-view */
|
|
542
|
+
--tree-indent: var(--space-5); /* 20px per nesting level */
|
|
543
|
+
--tree-row-pad-y: var(--space-1-5); /* 6px */
|
|
544
|
+
--tree-row-pad-x: var(--space-3); /* 12px base inset */
|
|
545
|
+
|
|
505
546
|
/* Bin pack visualisation */
|
|
506
547
|
--binpack-bay-h: var(--hit-touch); /* 48px */
|
|
507
548
|
|
|
@@ -549,13 +590,20 @@
|
|
|
549
590
|
--duration-base: 0ms;
|
|
550
591
|
--duration-slow: 0ms;
|
|
551
592
|
--duration-slower: 0ms;
|
|
552
|
-
|
|
553
|
-
|
|
593
|
+
|
|
594
|
+
/* Looping status indicators (spinners, indeterminate progress) stay
|
|
595
|
+
visible but calmer — slowed, never frozen: a stopped spinner reads as
|
|
596
|
+
"hung", not "loading". The one large motion we DO halt is the page-wide
|
|
597
|
+
skeleton sweep, which stops itself in _skeleton-shimmer.scss. */
|
|
598
|
+
--duration-loop-fast: 1400ms;
|
|
599
|
+
--duration-loop-slow: 2800ms;
|
|
554
600
|
}
|
|
601
|
+
|
|
602
|
+
/* Kill one-shot motion only. Every entrance/transition animation is
|
|
603
|
+
token-driven, so zeroing the one-shot durations above already makes them
|
|
604
|
+
instant — loops must NOT be blanket-zeroed here or they freeze. */
|
|
555
605
|
*, *::before, *::after {
|
|
556
606
|
transition-duration: 0ms !important;
|
|
557
|
-
animation-duration: 0ms !important;
|
|
558
|
-
animation-iteration-count: 1 !important;
|
|
559
607
|
}
|
|
560
608
|
}
|
|
561
609
|
|
|
@@ -597,6 +645,7 @@
|
|
|
597
645
|
}
|
|
598
646
|
[data-density="cozy"] {
|
|
599
647
|
--hit-cozy: 40px;
|
|
648
|
+
|
|
600
649
|
/* defaults */
|
|
601
650
|
}
|
|
602
651
|
[data-density="comfy"] {
|
package/styles/_typography.scss
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
// Type utility classes — ported from CLAUDE_DESIGN_DS/tokens.css §Type utilities.
|
|
2
2
|
// These are GLOBAL by design — consumers apply them via class.
|
|
3
3
|
|
|
4
|
-
.t-display { font: var(--font-weight-extrabold) var(--font-size-display)/var(--line-height-tight) var(--font-display); letter-spacing: var(--letter-spacing-tight); }
|
|
5
|
-
.t-h1 { font: var(--font-weight-extrabold) var(--font-size-h1)/var(--line-height-tight) var(--font-heading); letter-spacing: var(--letter-spacing-tight); }
|
|
6
|
-
.t-h2 { font: var(--font-weight-extrabold) var(--font-size-h2)/var(--line-height-snug) var(--font-heading); letter-spacing: var(--letter-spacing-snug); }
|
|
7
|
-
.t-h3 { font: var(--font-weight-extrabold) var(--font-size-h3)/var(--line-height-snug) var(--font-heading); letter-spacing: var(--letter-spacing-snug); }
|
|
8
|
-
.t-h4 { font: var(--font-weight-bold) var(--font-size-h4)/var(--line-height-snug) var(--font-heading); }
|
|
9
|
-
.t-h5 { font: var(--font-weight-bold) var(--font-size-h5)/var(--line-height-snug) var(--font-heading); }
|
|
10
|
-
.t-card-title { font: var(--font-weight-bold) var(--card-title-fs)/var(--line-height-snug) var(--font-heading); margin: 0; color: var(--text-primary); }
|
|
11
|
-
.t-l { font: var(--font-weight-regular) var(--font-size-l)/var(--line-height-base) var(--font-sans); }
|
|
12
|
-
.t-l-bold { font: var(--font-weight-bold) var(--font-size-l)/var(--line-height-base) var(--font-sans); }
|
|
13
|
-
.t-m { font: var(--font-weight-regular) var(--font-size-m)/var(--line-height-base) var(--font-sans); }
|
|
14
|
-
.t-m-bold { font: var(--font-weight-bold) var(--font-size-m)/var(--line-height-base) var(--font-sans); }
|
|
15
|
-
.t-s { font: var(--font-weight-regular) var(--font-size-s)/var(--line-height-base) var(--font-sans); }
|
|
16
|
-
.t-s-bold { font: var(--font-weight-bold) var(--font-size-s)/var(--line-height-base) var(--font-sans); }
|
|
17
|
-
.t-xs { font: var(--font-weight-regular) var(--font-size-xs)/var(--line-height-base) var(--font-sans); }
|
|
18
|
-
.t-xs-bold { font: var(--font-weight-bold) var(--font-size-xs)/var(--line-height-base) var(--font-sans); }
|
|
19
|
-
.t-overline { font: var(--font-weight-bold) var(--font-size-2xs)/var(--line-height-base) var(--font-sans); letter-spacing: var(--letter-spacing-wider); text-transform: uppercase; color: var(--text-tertiary); }
|
|
20
|
-
.t-caption { font: var(--font-weight-regular) var(--font-size-xs)/var(--line-height-base) var(--font-sans); color: var(--text-tertiary); }
|
|
21
|
-
.t-mono { font: var(--font-weight-medium) var(--font-size-mono)/var(--line-height-base) var(--font-mono); }
|
|
22
|
-
.t-mono-inline { font: var(--font-weight-regular) var(--font-size-mono)/1 var(--font-mono); background: var(--surface-secondary); color: var(--text-primary); padding: 1px var(--space-1); border-radius: var(--radius-sm); }
|
|
23
|
-
.t-mono-block { font: var(--font-weight-regular) var(--font-size-mono)/var(--line-height-loose) var(--font-mono); background: var(--surface-secondary); border: var(--border-width-default) solid var(--border-divider); border-radius: var(--radius-md); padding: var(--space-3) var(--space-4); }
|
|
4
|
+
.t-display { font: var(--font-weight-extrabold) var(--font-size-display) / var(--line-height-tight) var(--font-display); letter-spacing: var(--letter-spacing-tight); }
|
|
5
|
+
.t-h1 { font: var(--font-weight-extrabold) var(--font-size-h1) / var(--line-height-tight) var(--font-heading); letter-spacing: var(--letter-spacing-tight); }
|
|
6
|
+
.t-h2 { font: var(--font-weight-extrabold) var(--font-size-h2) / var(--line-height-snug) var(--font-heading); letter-spacing: var(--letter-spacing-snug); }
|
|
7
|
+
.t-h3 { font: var(--font-weight-extrabold) var(--font-size-h3) / var(--line-height-snug) var(--font-heading); letter-spacing: var(--letter-spacing-snug); }
|
|
8
|
+
.t-h4 { font: var(--font-weight-bold) var(--font-size-h4) / var(--line-height-snug) var(--font-heading); }
|
|
9
|
+
.t-h5 { font: var(--font-weight-bold) var(--font-size-h5) / var(--line-height-snug) var(--font-heading); }
|
|
10
|
+
.t-card-title { font: var(--font-weight-bold) var(--card-title-fs) / var(--line-height-snug) var(--font-heading); margin: 0; color: var(--text-primary); }
|
|
11
|
+
.t-l { font: var(--font-weight-regular) var(--font-size-l) / var(--line-height-base) var(--font-sans); }
|
|
12
|
+
.t-l-bold { font: var(--font-weight-bold) var(--font-size-l) / var(--line-height-base) var(--font-sans); }
|
|
13
|
+
.t-m { font: var(--font-weight-regular) var(--font-size-m) / var(--line-height-base) var(--font-sans); }
|
|
14
|
+
.t-m-bold { font: var(--font-weight-bold) var(--font-size-m) / var(--line-height-base) var(--font-sans); }
|
|
15
|
+
.t-s { font: var(--font-weight-regular) var(--font-size-s) / var(--line-height-base) var(--font-sans); }
|
|
16
|
+
.t-s-bold { font: var(--font-weight-bold) var(--font-size-s) / var(--line-height-base) var(--font-sans); }
|
|
17
|
+
.t-xs { font: var(--font-weight-regular) var(--font-size-xs) / var(--line-height-base) var(--font-sans); }
|
|
18
|
+
.t-xs-bold { font: var(--font-weight-bold) var(--font-size-xs) / var(--line-height-base) var(--font-sans); }
|
|
19
|
+
.t-overline { font: var(--font-weight-bold) var(--font-size-2xs) / var(--line-height-base) var(--font-sans); letter-spacing: var(--letter-spacing-wider); text-transform: uppercase; color: var(--text-tertiary); }
|
|
20
|
+
.t-caption { font: var(--font-weight-regular) var(--font-size-xs) / var(--line-height-base) var(--font-sans); color: var(--text-tertiary); }
|
|
21
|
+
.t-mono { font: var(--font-weight-medium) var(--font-size-mono) / var(--line-height-base) var(--font-mono); }
|
|
22
|
+
.t-mono-inline { font: var(--font-weight-regular) var(--font-size-mono) / 1 var(--font-mono); background: var(--surface-secondary); color: var(--text-primary); padding: 1px var(--space-1); border-radius: var(--radius-sm); }
|
|
23
|
+
.t-mono-block { font: var(--font-weight-regular) var(--font-size-mono) / var(--line-height-loose) var(--font-mono); background: var(--surface-secondary); border: var(--border-width-default) solid var(--border-divider); border-radius: var(--radius-md); padding: var(--space-3) var(--space-4); }
|
|
24
24
|
|
|
25
25
|
.sr-only {
|
|
26
26
|
position: absolute !important;
|
|
27
27
|
width: 1px; height: 1px;
|
|
28
28
|
padding: 0; margin: -1px;
|
|
29
|
-
overflow: hidden; clip:
|
|
29
|
+
overflow: hidden; clip-path: inset(50%);
|
|
30
30
|
white-space: nowrap; border: 0;
|
|
31
31
|
}
|
package/styles/ds.scss
CHANGED
|
@@ -1,27 +1,33 @@
|
|
|
1
1
|
// Public DS stylesheet entry. Consumers import once at the top of their styles.scss:
|
|
2
2
|
// @use 'styles/ds';
|
|
3
3
|
//
|
|
4
|
-
// Bundles the
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
4
|
+
// Bundles the base palette (theme-independent ramps + channels + statics + fonts)
|
|
5
|
+
// and the theme activation (light + dark, sibling themes + OS-default media rule).
|
|
6
|
+
// Layers:
|
|
7
|
+
// base-palette — Tier 1a theme-independent primitives (ramps, channels,
|
|
8
|
+
// statics, shadow h/s, font families) emitted at :root
|
|
9
|
+
// theme-activation — Tier 1b + Tier 2: per-theme palette + role assignments,
|
|
10
|
+
// scoped to mutually-exclusive :root selectors
|
|
11
|
+
// tokens — Tier 3 geometry / typography scale / motion / component tokens
|
|
12
|
+
// Advanced consumers can compose themes individually instead of using ds:
|
|
13
|
+
// @use 'themes/base-palette'; @use 'styles/theme-activation'; @use 'styles/tokens';
|
|
11
14
|
// @use 'styles/fonts'; @use 'styles/link'; …
|
|
12
15
|
|
|
13
|
-
@forward '../themes/
|
|
14
|
-
@forward '
|
|
16
|
+
@forward '../themes/base-palette';
|
|
17
|
+
@forward 'theme-activation';
|
|
15
18
|
@forward 'tokens';
|
|
19
|
+
@forward 'breakpoints';
|
|
16
20
|
@forward 'tokens-charts';
|
|
17
21
|
@forward 'fonts';
|
|
18
22
|
@forward 'reset';
|
|
19
23
|
@forward 'typography';
|
|
20
24
|
@forward 'link';
|
|
25
|
+
@forward 'ds-color';
|
|
21
26
|
@forward 'layout-utils';
|
|
22
27
|
@forward 'page-grid';
|
|
23
28
|
@forward 'scrollbar';
|
|
24
29
|
@forward 'icon-base';
|
|
25
30
|
@forward 'dropdown-overlay';
|
|
26
31
|
@forward 'toast-overlay';
|
|
32
|
+
@forward 'overlay-motion';
|
|
27
33
|
@forward 'skeleton-shimmer';
|
package/themes/README.md
CHANGED
|
@@ -1,96 +1,110 @@
|
|
|
1
|
-
# Themes — palette
|
|
1
|
+
# Themes — base palette · sibling themes · brands
|
|
2
2
|
|
|
3
|
-
Colour
|
|
3
|
+
Colour is layered in three concerns: a **base palette** (theme-independent primitives), **themes** (light / dark — sibling role maps; light is the default, dark is opt-in), and **brands** (optional overlays authored on a theme). The geometry/component layer is separate (`../styles/_tokens.scss`).
|
|
4
4
|
|
|
5
|
-
## Architecture: three
|
|
5
|
+
## Architecture: three layers
|
|
6
6
|
|
|
7
|
-
|
|
|
8
|
-
|
|
9
|
-
| **
|
|
10
|
-
| **
|
|
11
|
-
| **
|
|
7
|
+
| Layer | Lives in | Examples | Applied at |
|
|
8
|
+
| ------------------------------ | ---------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ----------------------------------- |
|
|
9
|
+
| **Base palette** (primitives) | `themes/_base-palette.scss` | `--blue-600`, `--cool-gray-*`, `--brand-gray-*`, `--red-h/s/l`, `--static-white`, fonts, `--shadow-tint-h/s` | `:root` (theme-independent) |
|
|
10
|
+
| **Theme** (role map) | `themes/_semantic.scss` + `themes/<t>/_*.scss` | `--primary`, `--surface-canvas`, `--text-primary`, the `--neutral-*` slot, `--focus-ring` | `:root` (light) / `:root[data-theme='dark']` |
|
|
11
|
+
| **Component / scale** | `styles/_tokens.scss`, `*.component.scss` | `--space-*`, `--radius-*`, `--ds-button-bg-color`, `--duration-fast` | `:root` (theme-independent) |
|
|
12
12
|
|
|
13
|
-
**Hard rule:** components NEVER consume
|
|
13
|
+
**Hard rule:** components NEVER consume base-palette primitives directly. `var(--blue-600)` inside `button.component.scss` is a bug — use a Tier-2 role (`--primary`, `--text-secondary`) or a component token.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Files
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
- **`_base-palette.scss`** — theme-independent primitives at `:root`: hue ramps (`--blue/red/green/yellow/sky/orange-*`) + HSL channels, `--cool-gray-*` / `--brand-gray-*` ramps, `--static-white` / `--static-ink` (never flip), `--shadow-tint-h/s`, font families. Shared by every theme.
|
|
18
|
+
- **`_semantic.scss`** — `@mixin roles`: the semantic role map (surfaces, text, icons, borders, primary/secondary/tertiary families, status, status-pills, focus). Structure is identical across themes; values resolve through each theme's own `--neutral-*` ramp. Holds the **light-default** values.
|
|
19
|
+
- **`light/_palette.scss` + `light/_theme.scss`** — `@mixin palette` (the `--neutral-*` slot = cool-gray + `--shadow-tint-l: 7%`) and `@mixin theme` (`palette` + `semantic.roles`). Light is the **default** skin and needs no extra overrides.
|
|
20
|
+
- **`dark/_palette.scss` + `dark/_theme.scss`** — `@mixin palette` (dark neutral ramp + `--shadow-tint-l: 95%`) and `@mixin theme` (`palette` + `semantic.roles` + the value overrides that genuinely differ on dark: tint surfaces, on-tint text, hover/pressed mixed toward white, scrim, focus-halo alpha, chart tokens).
|
|
21
|
+
- **`../styles/_theme-activation.scss`** — applies light at `:root` and dark at `:root[data-theme='dark']`, plus the light-context `data-neutrals='brand'` switch.
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
- **`default/_theme.scss`** — Light role map onto the default palette. Both are bundled into `styles/ds.scss` (palette → theme → tokens) so `@use 'styles/ds';` works out of the box.
|
|
23
|
+
`styles/ds.scss` forwards `_base-palette` then `theme-activation` (before `tokens`), so `@use 'styles/ds';` renders light out of the box.
|
|
21
24
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
**Why sibling themes (not a dark "patch"):** the dark block declares the COMPLETE token set (`@include palette` + `@include semantic.roles` + its own overrides), so `:root[data-theme='dark']` fully replaces light rather than delta-patching it. The `@mixin roles` in `_semantic.scss` is an authoring-DRY device (the light-default structure both themes share), not a runtime cross-theme dependency.
|
|
26
|
+
|
|
27
|
+
## Theme switching
|
|
28
|
+
|
|
29
|
+
**Light is the default — no attribute needed.** Dark is opt-in:
|
|
30
|
+
|
|
31
|
+
- (nothing) — light
|
|
32
|
+
- `<html data-theme="dark">` — dark
|
|
33
|
+
- `<html data-theme="light">` — also light (explicit; same as the default)
|
|
34
|
+
|
|
35
|
+
```scss
|
|
36
|
+
:root { @include light.theme; } // default
|
|
37
|
+
:root[data-theme='dark'] { @include dark.theme; } // opt-in (complete set → fully wins)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
There is **no automatic `prefers-color-scheme` switch** — light is deterministic by default. To follow the OS, opt in at the app root with `provideDsTheme`:
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// app.config.ts
|
|
44
|
+
import { provideDsTheme } from '@ids-group-ltd/ids-design-system';
|
|
45
|
+
|
|
46
|
+
export const appConfig: ApplicationConfig = {
|
|
47
|
+
providers: [provideDsTheme({ followSystem: true })],
|
|
48
|
+
};
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
It sets `data-theme` from `prefers-color-scheme` at startup and keeps it in sync as the OS changes; a manual `data-theme="dark"` still wins.
|
|
52
|
+
|
|
53
|
+
On-fill text uses theme-stable `--static-white` / `--static-ink` so it doesn't flip with the neutrals.
|
|
54
|
+
|
|
55
|
+
**Known limitation — `data-neutrals="brand"` + dark:** the brand-gray (hue-derived) neutral switch is a **light-theme feature**. The dark block ships its own neutral ramp and is declared last, so on `data-theme="dark"` the dark neutrals win and the brand-gray tint is ignored. (Benign — renders correct dark, just not brand-tinted neutrals.)
|
|
25
56
|
|
|
26
57
|
## How a consumer picks a theme
|
|
27
58
|
|
|
28
|
-
### Option A — out
|
|
59
|
+
### Option A — out of the box (light)
|
|
29
60
|
|
|
30
61
|
```scss
|
|
31
|
-
//
|
|
32
|
-
@use 'styles/ds'; // bundles default palette + theme + tokens + reset + ...
|
|
62
|
+
@use 'styles/ds'; // base palette + light/dark activation + tokens + reset + …
|
|
33
63
|
```
|
|
34
64
|
|
|
35
|
-
### Option B —
|
|
65
|
+
### Option B — à la carte (advanced, opt-in global side-effects)
|
|
36
66
|
|
|
37
67
|
```scss
|
|
38
|
-
|
|
39
|
-
@use '
|
|
40
|
-
@use 'themes/dark/theme'; // … then its role map
|
|
68
|
+
@use 'themes/base-palette';
|
|
69
|
+
@use 'styles/theme-activation'; // light at :root + dark at [data-theme='dark']
|
|
41
70
|
@use 'styles/tokens';
|
|
71
|
+
@use 'styles/fonts';
|
|
42
72
|
@use 'styles/reset';
|
|
43
|
-
|
|
44
|
-
@use 'styles/scrollbar';
|
|
45
|
-
@use 'styles/icon-base';
|
|
46
|
-
@use 'styles/dropdown-overlay';
|
|
73
|
+
// …
|
|
47
74
|
```
|
|
48
75
|
|
|
49
|
-
|
|
76
|
+
`base-palette` + `theme-activation` must come BEFORE `tokens` so role aliases resolve. The theme `_palette` / `_theme` files expose **mixins** — don't `@use` them directly; `theme-activation` applies them.
|
|
50
77
|
|
|
51
|
-
### Option C — brand layer on top
|
|
78
|
+
### Option C — brand layer on top
|
|
52
79
|
|
|
53
80
|
```scss
|
|
54
|
-
|
|
55
|
-
@use 'styles/
|
|
56
|
-
@use './app/styles/brand-manage-my'; // overrides palette/roles via [data-brand]
|
|
81
|
+
@use 'styles/ds';
|
|
82
|
+
@use './app/styles/brand-manage-my'; // [data-brand="manage-my"] overrides (light-authored)
|
|
57
83
|
```
|
|
58
84
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
## Brand vs theme — what's the difference?
|
|
62
|
-
|
|
63
|
-
- **Theme** swaps the entire Tier 1 palette. Affects EVERY semantic role downstream.
|
|
64
|
-
- **Brand** is a thin overlay scoped via attribute selector (`[data-brand="X"]`). Usually overrides a smaller subset (key palette stops + maybe a few roles).
|
|
85
|
+
## Brand vs theme
|
|
65
86
|
|
|
66
|
-
|
|
87
|
+
- **Theme** swaps the whole role map (light ↔ dark). Light is the default; dark is opt-in and, when active, fully replaces light.
|
|
88
|
+
- **Brand** is a thin overlay scoped via attribute (`[data-brand="X"]`), authored on a theme. `manage-my` is authored on **light** — pair it with the default (light) theme. A brand that wants dark adds a separate `[data-theme='dark'][data-brand="X"]` layer.
|
|
67
89
|
|
|
68
90
|
## Adding a new theme
|
|
69
91
|
|
|
70
|
-
1. Create
|
|
71
|
-
2.
|
|
72
|
-
3. Or let consumers `@use 'themes/<name>/palette'; @use 'themes/<name>/theme';` directly.
|
|
92
|
+
1. Create `themes/<name>/_palette.scss` (`@mixin palette` — its `--neutral-*` ramp + `--shadow-tint-l`) and `themes/<name>/_theme.scss` (`@mixin theme` — `@include palette` + `@include semantic.roles` + only the role values that differ for this theme).
|
|
93
|
+
2. Add a selector block in `styles/_theme-activation.scss` (`:root[data-theme='<name>'] { @include <name>.theme; }`), after the light default.
|
|
73
94
|
|
|
74
|
-
Components
|
|
95
|
+
Components never change — they consume semantic roles, which the active theme block defines.
|
|
75
96
|
|
|
76
97
|
## Adding a new brand override
|
|
77
98
|
|
|
78
99
|
1. Create `<consumer-app>/src/app/styles/_brand-X.scss`.
|
|
79
|
-
2. Open with `[data-brand="X"] {
|
|
80
|
-
3. Override
|
|
81
|
-
|
|
82
|
-
- **Blue ramp** (`--blue-50..900`) — the simplest re-skin lever. All brand-derived roles (`--primary`, `--primary-strong`, `--primary-subtitle`, `--primary-muted-strong`, `--text-link`, `--surface-tint`) retint automatically from it. For a fully calibrated brand you MAY also set the `--primary-*` roles directly to hand-picked hex (when ramp derivation doesn't hit the exact stop) — see `_brand-manage-my.scss`. Leave `--primary-hover`/`--primary-pressed` to the DS `color-mix()` derivation.
|
|
83
|
-
- Other roles you want to retint independently (surfaces, borders, text, success/danger ramps, secondary, etc.).
|
|
84
|
-
4. Apply via `<html data-brand="X">` on the host app.
|
|
100
|
+
2. Open with `[data-brand="X"] { … }` (authored on light — the default theme).
|
|
101
|
+
3. Override the brand hue (`--blue-*` ramp and/or `--primary` + channels), and any roles you want to retint (surfaces, text, secondary, status). Leave `--primary-hover`/`--primary-pressed` to the DS `color-mix()` derivation.
|
|
102
|
+
4. Apply via `<html data-brand="X">`.
|
|
85
103
|
|
|
86
104
|
## Rules summary
|
|
87
105
|
|
|
88
|
-
- Components
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
```bash
|
|
94
|
-
grep -nE 'var\(--(violet|neutral|red|green|yellow|blue|orange|cyan|magenta)-[0-9]+\)' \
|
|
95
|
-
prototype/ds/src/lib/components/**/*.scss && exit 1
|
|
96
|
-
```
|
|
106
|
+
- Components never reference base-palette primitives (`--blue-600`, `--neutral-200`) directly — use a Tier-2 role.
|
|
107
|
+
- Light is the default; dark is opt-in (`data-theme="dark"`); OS-follow is a consumer opt-in. No `prefers-color-scheme` by default.
|
|
108
|
+
- Themes are sibling and self-contained; `_semantic.scss`'s `@mixin roles` holds the light-default values, dark overrides the deltas.
|
|
109
|
+
- Brands are theme-authored overlays. `manage-my` is light.
|
|
110
|
+
- `--primary-hover`/`--primary-pressed` are auto-derived via `color-mix()` (toward black in light, toward white in dark).
|