|
7 | 7 | @use 'sass:list'; |
8 | 8 | @use 'sass:string'; |
9 | 9 |
|
10 | | -// Style API v2 authoring utils for shared, component-agnostic style tokens (e.g. --awsui-style-color-text, |
11 | | -// not --awsui-style-button-color-text). The owning component is implied by where the consumer applies the |
12 | | -// class, so one abstract token name is reused across components and shared token groups can be composed. |
| 10 | +// Style API v2 authoring utils for shared, component-agnostic style tokens (e.g. color-text, |
| 11 | +// color-background). Each public token is exposed as a `--awsui-style-<token>` custom property, |
| 12 | +// registered as `@property { syntax:'*'; inherits:false }`. A slot can read a token in one of two layers: |
| 13 | +// - PUBLIC layer: read the public token directly, on the element the consumer themes (single-element slots). |
| 14 | +// - CARRIER layer: mirror the public token into an inherited internal carrier `--awsui-internal-style-<token>` |
| 15 | +// re-anchored on the component root (carriers() mixin), so descendants can read it per instance. |
13 | 16 | // |
14 | | -// Model: each public token `--awsui-style-<prop>` is registered `@property { syntax:'*'; inherits:false }`. |
15 | | -// A component resolves its prop set into a <prop> -> custom-property map for one of two LAYERS, then reads via |
16 | | -// `var(#{read($tokens, <prop>)}, <default>)` (uniform at every read site): |
| 17 | +// The reusable primitive is a per-slot token map produced by resolve() (`token -> custom-property`). Write |
| 18 | +// each slot's token names once in resolve(); then register(), docs() and read() all consume the same maps — |
| 19 | +// so the docs can never drift from the implementation, and a map can be shared by several slots. |
17 | 20 | // |
18 | | -// - PUBLIC layer: read the public token directly, on the element the consumer themes. It doesn't inherit, so |
19 | | -// it can't pick up an ancestor component's value. Use for single-element components (e.g. a button). |
20 | | -// - CARRIER layer: the public token is mirrored into an INHERITED internal carrier `--awsui-internal-style-<prop>` |
21 | | -// re-anchored on the component root (carriers() mixin), so descendants can read it. Each root re-anchors from |
22 | | -// its own (unset) public token, so values scope to the instance and reset at every nested boundary — no |
23 | | -// leakage. Registrations are byte-identical across components, so order-independent. |
24 | | -// |
25 | | -// Example: |
| 21 | +// Flow (public layer): |
26 | 22 | // |
27 | 23 | // @use '@cloudscape-design/component-toolkit/internal/style-api' as style-api; |
28 | 24 | // |
29 | | -// $props: style-api.combine((color-background, color-text), (icon-color)); // 1. union token lists (deduped) |
30 | | -// $tokens: style-api.resolve($props, carrier); // 2. map for the carrier layer -> pass to read() |
31 | | -// @include style-api.register($props); // 3. declare the public @property (top level, once) |
| 25 | +// $root: style-api.resolve((color-background), public); // 1. token names written once, per slot |
| 26 | +// $info: style-api.resolve((color-text), public); |
| 27 | +// @include style-api.register($root, $text); // 2. declare @property for every token (once) |
| 28 | +// |
| 29 | +// @include style-api.docs('root', $root); // 3. declare the slots (docs only, top level) |
| 30 | +// @include style-api.docs('info', $info); |
32 | 31 | // |
33 | | -// .alert-root { |
34 | | -// @include style-api.carriers($props); // 4. re-anchor public -> internal on the boundary |
35 | | -// color: var(#{style-api.read($tokens, color-text)}, #16191f); // 5. apply token (default via awsui.$… in a real component) |
36 | | -// } |
| 32 | +// .root { background: var(#{style-api.read($root, color-background)}, …); } // 4. apply the tokens |
| 33 | +// .info { color: var(#{style-api.read($info, color-text)}, …); } |
37 | 34 | // |
38 | | -// A consumer themes an instance by setting the public token on a class applied to the component: |
39 | | -// .my-alert { --awsui-style-color-text: light-dark(black, white); } |
| 35 | +// Carrier layer: resolve($tokens, carrier) and `@include style-api.carriers($map)` on the boundary element. |
40 | 36 |
|
41 | | -// 1. Deduped union of token lists. |
| 37 | +// Deduped union of token lists — for composing shared token groups before resolve(). |
42 | 38 | @function combine($lists...) { |
43 | 39 | $result: (); |
44 | 40 | @each $list in $lists { |
45 | | - @each $prop in $list { |
46 | | - @if not list.index($result, $prop) { |
47 | | - $result: list.append($result, $prop); |
| 41 | + @each $token in $list { |
| 42 | + @if not list.index($result, $token) { |
| 43 | + $result: list.append($result, $token); |
48 | 44 | } |
49 | 45 | } |
50 | 46 | } |
51 | 47 | @return $result; |
52 | 48 | } |
53 | 49 |
|
54 | | -// 2. Resolves `$props` into a <prop> -> custom-property map for `$layer` (`public` or `carrier`), consumed by |
55 | | -// read() at read sites. `$layer` is required so the layer choice is always explicit at the call site. |
56 | | -@function resolve($props, $layer) { |
| 50 | +// Resolves `$tokens` (a token name or list of token names) into a `token -> custom-property` map for |
| 51 | +// `$layer` (`public` or `carrier`), consumed by docs(), read() and carriers(). `$layer` is required |
| 52 | +// so the layer choice is always explicit at the definition site. |
| 53 | +@function resolve($tokens, $layer) { |
57 | 54 | @if $layer != public and $layer != carrier { |
58 | 55 | @error 'Unknown layer "#{$layer}". Use `public` or `carrier`.'; |
59 | 56 | } |
60 | 57 | $prefix: '--awsui-internal-style-'; |
61 | 58 | @if $layer == public { |
62 | 59 | $prefix: '--awsui-style-'; |
63 | 60 | } |
64 | | - $tokens: (); |
65 | | - @each $prop in $props { |
66 | | - $tokens: map.set($tokens, $prop, string.unquote('#{$prefix}#{$prop}')); |
| 61 | + $map: (); |
| 62 | + @each $token in $tokens { |
| 63 | + $map: map.set($map, $token, string.unquote('#{$prefix}#{$token}')); |
67 | 64 | } |
68 | | - @return $tokens; |
| 65 | + @return $map; |
69 | 66 | } |
70 | 67 |
|
71 | | -// 3. Registers the public tokens as non-inheriting @property. Top level only. |
72 | | -@mixin register($props) { |
73 | | - @each $prop in $props { |
74 | | - @property --awsui-style-#{$prop} { |
75 | | - syntax: '*'; |
76 | | - inherits: false; |
| 68 | +// Registers the public tokens of the given slot maps as non-inheriting @property. Top level, once. |
| 69 | +// Accepts every slot's map so the registered set is exactly the union of the tokens the slots use. |
| 70 | +@mixin register($maps...) { |
| 71 | + $seen: (); |
| 72 | + @each $map in $maps { |
| 73 | + @each $token in map.keys($map) { |
| 74 | + @if not list.index($seen, $token) { |
| 75 | + $seen: list.append($seen, $token); |
| 76 | + @property --awsui-style-#{$token} { |
| 77 | + syntax: '*'; |
| 78 | + inherits: false; |
| 79 | + } |
| 80 | + } |
77 | 81 | } |
78 | 82 | } |
79 | 83 | } |
80 | 84 |
|
81 | | -// 4. Re-anchors each public token into its inherited internal carrier. Include on the component's root; pair |
82 | | -// with a `resolve($props, carrier)` map so descendants read the re-anchored values. |
83 | | -@mixin carriers($props) { |
84 | | - @each $prop in $props { |
85 | | - --awsui-internal-style-#{$prop}: var(--awsui-style-#{$prop}); |
| 85 | +// Re-anchors each token of a carrier map into its inherited internal carrier. Include on the |
| 86 | +// component's boundary element; descendants read the re-anchored values via the same map. |
| 87 | +@mixin carriers($map) { |
| 88 | + @each $token in map.keys($map) { |
| 89 | + --awsui-internal-style-#{$token}: var(--awsui-style-#{$token}); |
86 | 90 | } |
87 | 91 | } |
88 | 92 |
|
89 | | -// 5. The custom-property to read for `$prop`, looked up in the given map. Errors at compile time on an |
90 | | -// undeclared prop (a typo, or a token the component never composed into its set). |
91 | | -@function read($tokens, $prop) { |
92 | | - @if not map.has-key($tokens, $prop) { |
93 | | - @error 'Unknown style token "#{$prop}". Declared: #{map.keys($tokens)}.'; |
| 93 | +// The custom-property to read for `$token`, looked up in a slot map. Errors at compile time on an |
| 94 | +// unknown token (a typo, or a token not in this slot's set). |
| 95 | +@function read($map, $token) { |
| 96 | + @if not map.has-key($map, $token) { |
| 97 | + @error 'Unknown style token "#{$token}". This slot declares: #{map.keys($map)}.'; |
94 | 98 | } |
95 | | - @return map.get($tokens, $prop); |
| 99 | + @return map.get($map, $token); |
| 100 | +} |
| 101 | + |
| 102 | +// Documents a themeable slot (emits docs only — no styling effect), from the slot map. |
| 103 | +// The `$name` must match the component's `classNames` property entry. |
| 104 | +@mixin docs($name, $map) { |
| 105 | + /* awsui:style-api-slot name=#{$name} tokens=#{map.keys($map)} */ |
96 | 106 | } |
0 commit comments