@resolve-components/theme 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,341 @@
1
+ // =============================================================================
2
+ // Resolve Components — Default Theme (Microsoft Fluent-Inspired)
3
+ // =============================================================================
4
+ // This is the base light theme. All other themes inherit from it.
5
+ //
6
+ // The palette is inspired by Microsoft Fluent Design System:
7
+ // Primary: #0078D4 (Microsoft Blue)
8
+ // Success: #107C10 (Microsoft Green)
9
+ // Warning: #FFB900 (Microsoft Yellow)
10
+ // Danger: #D13438 (Microsoft Red)
11
+ // Info: #00B7C3 (Microsoft Teal)
12
+ //
13
+ // Each color has a 100–900 ramp. Semantic aliases (e.g. color-primary-default)
14
+ // point to specific ramp steps. Users override these to re-theme.
15
+ // =============================================================================
16
+
17
+ @use 'sass:string';
18
+ @use '../theming/theming-variables' as vars;
19
+ @use '../theming/register' as reg;
20
+
21
+ $theme: (
22
+ // Direct CSS properties (emitted without -- prefix)
23
+ _color-scheme: light,
24
+
25
+ // ═══════════════════════════════════════════════════════════════════════════
26
+ // COLOR PALETTE
27
+ // ═══════════════════════════════════════════════════════════════════════════
28
+ // ── Primary (Microsoft Blue) ────────────────────────────────────────────
29
+ color-primary-100: #eff6fc,
30
+ color-primary-200: #deecf9,
31
+ color-primary-300: #b4d6fa,
32
+ color-primary-400: #5ba3e6,
33
+ color-primary-500: #0078d4,
34
+ color-primary-600: #106ebe,
35
+ color-primary-700: #005a9e,
36
+ color-primary-800: #004578,
37
+ color-primary-900: #003356,
38
+
39
+ // Semantic primary aliases
40
+ color-primary-default: color-primary-500,
41
+ color-primary-hover: color-primary-600,
42
+ color-primary-active: color-primary-700,
43
+ color-primary-focus: color-primary-500,
44
+ color-primary-disabled: color-primary-300,
45
+ color-primary-transparent-default: rgba(0, 120, 212, 0.08),
46
+ color-primary-transparent-hover: rgba(0, 120, 212, 0.12),
47
+ color-primary-transparent-active: rgba(0, 120, 212, 0.16),
48
+ // ── Success (Microsoft Green) ───────────────────────────────────────────
49
+ color-success-100: #dff6dd,
50
+ color-success-200: #c8f0c2,
51
+ color-success-300: #9ae08f,
52
+ color-success-400: #54b054,
53
+ color-success-500: #107c10,
54
+ color-success-600: #0e6b0e,
55
+ color-success-700: #094509,
56
+ color-success-800: #063006,
57
+ color-success-900: #042004,
58
+
59
+ color-success-default: color-success-500,
60
+ color-success-hover: color-success-600,
61
+ color-success-active: color-success-700,
62
+
63
+ // ── Warning (Microsoft Yellow/Amber) ────────────────────────────────────
64
+ color-warning-100: #fff8e1,
65
+ color-warning-200: #ffecb3,
66
+ color-warning-300: #ffe082,
67
+ color-warning-400: #ffd54f,
68
+ color-warning-500: #ffb900,
69
+ color-warning-600: #e6a700,
70
+ color-warning-700: #cc9500,
71
+ color-warning-800: #997000,
72
+ color-warning-900: #664b00,
73
+
74
+ color-warning-default: color-warning-500,
75
+ color-warning-hover: color-warning-600,
76
+ color-warning-active: color-warning-700,
77
+
78
+ // ── Danger (Microsoft Red) ──────────────────────────────────────────────
79
+ color-danger-100: #fde7e9,
80
+ color-danger-200: #f9c6ca,
81
+ color-danger-300: #f3a3a6,
82
+ color-danger-400: #e74856,
83
+ color-danger-500: #d13438,
84
+ color-danger-600: #bc2f32,
85
+ color-danger-700: #a4262c,
86
+ color-danger-800: #8b2025,
87
+ color-danger-900: #6e191d,
88
+
89
+ color-danger-default: color-danger-500,
90
+ color-danger-hover: color-danger-600,
91
+ color-danger-active: color-danger-700,
92
+
93
+ // ── Info (Microsoft Teal) ───────────────────────────────────────────────
94
+ color-info-100: #e0f7fa,
95
+ color-info-200: #b2ebf2,
96
+ color-info-300: #80deea,
97
+ color-info-400: #26c6da,
98
+ color-info-500: #00b7c3,
99
+ color-info-600: #009da5,
100
+ color-info-700: #008387,
101
+ color-info-800: #006b6b,
102
+ color-info-900: #00524f,
103
+
104
+ color-info-default: color-info-500,
105
+ color-info-hover: color-info-600,
106
+ color-info-active: color-info-700,
107
+
108
+ // ── Basic / Neutral (Microsoft Grays) ───────────────────────────────────
109
+ color-basic-100: #ffffff,
110
+ color-basic-200: #faf9f8,
111
+ color-basic-300: #f3f2f1,
112
+ color-basic-400: #edebe9,
113
+ color-basic-500: #d2d0ce,
114
+ color-basic-600: #a19f9d,
115
+ color-basic-700: #605e5c,
116
+ color-basic-800: #3b3a39,
117
+ color-basic-900: #323130,
118
+ color-basic-1000: #201f1e,
119
+ color-basic-1100: #11100f,
120
+
121
+ // ── Control (text on filled backgrounds) ────────────────────────────────
122
+ color-control: #ffffff,
123
+ color-control-muted: rgba(255, 255, 255, 0.72),
124
+ // ═══════════════════════════════════════════════════════════════════════════
125
+ // SEMANTIC BACKGROUND COLORS
126
+ // ═══════════════════════════════════════════════════════════════════════════
127
+ background-basic-color-1: color-basic-100,
128
+ background-basic-color-2: color-basic-200,
129
+ background-basic-color-3: color-basic-300,
130
+ background-basic-color-4: color-basic-400,
131
+ background-alternative-color-1: color-basic-900,
132
+ background-alternative-color-2: color-basic-1000,
133
+
134
+ // ═══════════════════════════════════════════════════════════════════════════
135
+ // SEMANTIC TEXT COLORS
136
+ // ═══════════════════════════════════════════════════════════════════════════
137
+ text-basic-color: color-basic-1000,
138
+ text-alternate-color: color-basic-100,
139
+ text-control-color: color-control,
140
+ text-hint-color: color-basic-600,
141
+ text-disabled-color: color-basic-500,
142
+
143
+ // ═══════════════════════════════════════════════════════════════════════════
144
+ // BORDERS
145
+ // ═══════════════════════════════════════════════════════════════════════════
146
+ border-basic-color: color-basic-400,
147
+ border-basic-color-2: color-basic-500,
148
+ border-basic-color-3: color-basic-300,
149
+ border-primary-color: color-primary-default,
150
+
151
+ // ═══════════════════════════════════════════════════════════════════════════
152
+ // TYPOGRAPHY
153
+ // ═══════════════════════════════════════════════════════════════════════════
154
+ font-family-primary: string.unquote(
155
+ "'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif"
156
+ ),
157
+ font-family-secondary: string.unquote(
158
+ "'Segoe UI', -apple-system, BlinkMacSystemFont, 'Roboto', 'Helvetica Neue', sans-serif"
159
+ ),
160
+ font-family-mono: string.unquote(
161
+ "'Cascadia Code', 'Fira Code', 'Consolas', monospace"
162
+ ),
163
+ font-size-xs: 0.75rem,
164
+ font-size-sm: 0.875rem,
165
+ font-size-md: 0.9375rem,
166
+ font-size-lg: 1.125rem,
167
+ font-size-xl: 1.25rem,
168
+ font-size-2xl: 1.5rem,
169
+ font-size-3xl: 2rem,
170
+
171
+ font-weight-normal: 400,
172
+ font-weight-medium: 500,
173
+ font-weight-semibold: 600,
174
+ font-weight-bold: 700,
175
+
176
+ line-height-sm: 1.25,
177
+ line-height-md: 1.5,
178
+ line-height-lg: 1.75,
179
+
180
+ // Heading sizes
181
+ text-heading-1-font-size: 2rem,
182
+ text-heading-1-font-weight: font-weight-semibold,
183
+ text-heading-2-font-size: 1.5rem,
184
+ text-heading-2-font-weight: font-weight-semibold,
185
+ text-heading-3-font-size: 1.25rem,
186
+ text-heading-3-font-weight: font-weight-semibold,
187
+ text-heading-4-font-size: 1.125rem,
188
+ text-heading-4-font-weight: font-weight-semibold,
189
+
190
+ text-body-font-size: font-size-md,
191
+ text-body-font-weight: font-weight-normal,
192
+ text-caption-font-size: font-size-xs,
193
+ text-caption-font-weight: font-weight-normal,
194
+
195
+ // ═══════════════════════════════════════════════════════════════════════════
196
+ // SPACING
197
+ // ═══════════════════════════════════════════════════════════════════════════
198
+ spacing-xs: 0.25rem,
199
+ spacing-sm: 0.5rem,
200
+ spacing-md: 1rem,
201
+ spacing-lg: 1.5rem,
202
+ spacing-xl: 2rem,
203
+ spacing-2xl: 3rem,
204
+
205
+ // ═══════════════════════════════════════════════════════════════════════════
206
+ // BORDER RADIUS
207
+ // ═══════════════════════════════════════════════════════════════════════════
208
+ border-radius: 4px,
209
+ radius-sm: 2px,
210
+ radius-md: 4px,
211
+ radius-lg: 8px,
212
+ radius-xl: 12px,
213
+ radius-full: 9999px,
214
+
215
+ // ═══════════════════════════════════════════════════════════════════════════
216
+ // SHADOWS (Fluent elevation)
217
+ // ═══════════════════════════════════════════════════════════════════════════
218
+ shadow: string.unquote(
219
+ '0 1.6px 3.6px 0 rgba(0, 0, 0, 0.132), 0 0.3px 0.9px 0 rgba(0, 0, 0, 0.108)'
220
+ ),
221
+ shadow-sm: string.unquote(
222
+ '0 0.8px 1.8px 0 rgba(0, 0, 0, 0.108), 0 0.15px 0.45px 0 rgba(0, 0, 0, 0.084)'
223
+ ),
224
+ shadow-md: string.unquote(
225
+ '0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)'
226
+ ),
227
+ shadow-lg: string.unquote(
228
+ '0 6.4px 14.4px 0 rgba(0, 0, 0, 0.132), 0 1.2px 3.6px 0 rgba(0, 0, 0, 0.108)'
229
+ ),
230
+ // ═══════════════════════════════════════════════════════════════════════════
231
+ // TRANSITIONS
232
+ // ═══════════════════════════════════════════════════════════════════════════
233
+ transition-duration-fast: 100ms,
234
+ transition-duration-medium: 150ms,
235
+ transition-duration-normal: 200ms,
236
+ transition-duration-slow: 300ms,
237
+ transition-timing: cubic-bezier(0.33, 0, 0.67, 1),
238
+ // ═══════════════════════════════════════════════════════════════════════════
239
+ // OUTLINE / FOCUS
240
+ // ═══════════════════════════════════════════════════════════════════════════
241
+ outline-color: color-basic-1000,
242
+ outline-width: 2px,
243
+ focus-ring-color: color-primary-focus,
244
+ focus-ring-width: 2px,
245
+ focus-ring-offset: 2px,
246
+
247
+ // ═══════════════════════════════════════════════════════════════════════════
248
+ // DIVIDER
249
+ // ═══════════════════════════════════════════════════════════════════════════
250
+ divider-color: border-basic-color-3,
251
+ divider-width: 1px,
252
+
253
+ // ═══════════════════════════════════════════════════════════════════════════
254
+ // ALERT COMPONENT
255
+ // ═══════════════════════════════════════════════════════════════════════════
256
+ alert-border-radius: radius-md,
257
+ alert-padding: string.unquote('0.875rem 1rem'),
258
+ alert-gap: spacing-sm,
259
+ alert-font-size: font-size-sm,
260
+ alert-title-font-weight: font-weight-semibold,
261
+ alert-title-font-size: font-size-sm,
262
+ alert-close-size: 1rem,
263
+
264
+ // Alert — info (subtle)
265
+ alert-info-subtle-background: color-info-100,
266
+ alert-info-subtle-color: color-info-700,
267
+ alert-info-subtle-border: color-info-300,
268
+ // Alert — info (filled)
269
+ alert-info-filled-background: color-info-default,
270
+ alert-info-filled-color: color-control,
271
+ alert-info-filled-border: color-info-default,
272
+ // Alert — info (outline)
273
+ alert-info-outline-background: transparent,
274
+ alert-info-outline-color: color-info-700,
275
+ alert-info-outline-border: color-info-default,
276
+
277
+ // Alert — success (subtle)
278
+ alert-success-subtle-background: color-success-100,
279
+ alert-success-subtle-color: color-success-700,
280
+ alert-success-subtle-border: color-success-300,
281
+ // Alert — success (filled)
282
+ alert-success-filled-background: color-success-default,
283
+ alert-success-filled-color: color-control,
284
+ alert-success-filled-border: color-success-default,
285
+ // Alert — success (outline)
286
+ alert-success-outline-background: transparent,
287
+ alert-success-outline-color: color-success-700,
288
+ alert-success-outline-border: color-success-default,
289
+
290
+ // Alert — warning (subtle)
291
+ alert-warning-subtle-background: color-warning-100,
292
+ alert-warning-subtle-color: color-warning-800,
293
+ alert-warning-subtle-border: color-warning-400,
294
+ // Alert — warning (filled)
295
+ alert-warning-filled-background: color-warning-default,
296
+ alert-warning-filled-color: color-basic-1100,
297
+ alert-warning-filled-border: color-warning-default,
298
+ // Alert — warning (outline)
299
+ alert-warning-outline-background: transparent,
300
+ alert-warning-outline-color: color-warning-700,
301
+ alert-warning-outline-border: color-warning-default,
302
+
303
+ // Alert — danger (subtle)
304
+ alert-danger-subtle-background: color-danger-100,
305
+ alert-danger-subtle-color: color-danger-700,
306
+ alert-danger-subtle-border: color-danger-300,
307
+ // Alert — danger (filled)
308
+ alert-danger-filled-background: color-danger-default,
309
+ alert-danger-filled-color: color-control,
310
+ alert-danger-filled-border: color-danger-default,
311
+ // Alert — danger (outline)
312
+ alert-danger-outline-background: transparent,
313
+ alert-danger-outline-color: color-danger-700,
314
+ alert-danger-outline-border: color-danger-default,
315
+
316
+ // ═══════════════════════════════════════════════════════════════════════════
317
+ // TOAST COMPONENT
318
+ // ═══════════════════════════════════════════════════════════════════════════
319
+ toast-border-radius: radius-lg,
320
+ toast-padding: string.unquote('0.875rem 1rem'),
321
+ toast-gap: spacing-sm,
322
+ toast-min-width: 20rem,
323
+ toast-max-width: 24rem,
324
+ toast-font-size: font-size-sm,
325
+ toast-title-font-weight: font-weight-semibold,
326
+ toast-title-font-size: font-size-sm,
327
+ toast-shadow: shadow-md,
328
+ toast-background: background-basic-color-1,
329
+ toast-color: text-basic-color,
330
+ toast-border-color: border-basic-color,
331
+ toast-close-size: 1rem,
332
+ toast-progress-height: 3px,
333
+
334
+ toast-info-accent: color-info-default,
335
+ toast-success-accent: color-success-default,
336
+ toast-warning-accent: color-warning-default,
337
+ toast-danger-accent: color-danger-default
338
+ );
339
+
340
+ // Register as the default theme (no parent)
341
+ vars.$rc-themes: reg.rc-register-theme($theme, default);
@@ -0,0 +1,92 @@
1
+ // =============================================================================
2
+ // Resolve Components — rc-component-animation mixin
3
+ // =============================================================================
4
+ // Wraps CSS `transition` declarations in `@media (prefers-reduced-motion: no-preference)`
5
+ // so that users who opt out of motion receive no transitions whatsoever.
6
+ //
7
+ // Usage:
8
+ // @use '../styles/theming/animation' as *;
9
+ //
10
+ // .my-element {
11
+ // @include rc-component-animation(background color);
12
+ // @include rc-component-animation(transform, $speed: 'normal');
13
+ // @include rc-component-animation(border-color, $speed: 'medium');
14
+ // @include rc-component-animation(
15
+ // background,
16
+ // $duration: rc-theme('accordion-transition-duration'),
17
+ // $timing: rc-theme('accordion-transition-timing')
18
+ // );
19
+ // }
20
+ // =============================================================================
21
+
22
+ @use 'sass:list';
23
+ @use 'get-value' as *;
24
+
25
+ /// Emits a CSS `transition` declaration wrapped in
26
+ /// `@media (prefers-reduced-motion: no-preference)`.
27
+ ///
28
+ /// @param {List} $properties Space-separated CSS properties to animate.
29
+ /// E.g. `background color` or just `transform`.
30
+ ///
31
+ /// @param {String} $speed Pre-defined duration tier.
32
+ /// `'fast'` → 100ms · `'medium'` → 150ms ·
33
+ /// `'normal'` → 200ms · `'slow'` → 300ms.
34
+ /// Default: `'fast'`. Ignored when `$duration` is given.
35
+ ///
36
+ /// @param {*} $timing Custom CSS timing function.
37
+ /// Defaults to the `transition-timing` theme token.
38
+ ///
39
+ /// @param {*} $duration Raw duration value (e.g. `rc-theme('accordion-transition-duration')`).
40
+ /// When provided, `$speed` is ignored for the duration.
41
+ ///
42
+ /// @example scss — simple, multiple props
43
+ /// .rc-button { @include rc-component-animation(background color); }
44
+ ///
45
+ /// @example scss — speed tier
46
+ /// .rc-toggle-thumb { @include rc-component-animation(transform, $speed: 'normal'); }
47
+ ///
48
+ /// @example scss — component token
49
+ /// .rc-accordion-header {
50
+ /// @include rc-component-animation(
51
+ /// background,
52
+ /// $duration: rc-theme('accordion-transition-duration'),
53
+ /// $timing: rc-theme('accordion-transition-timing')
54
+ /// );
55
+ /// }
56
+ @mixin rc-component-animation(
57
+ $properties,
58
+ $speed: 'fast',
59
+ $timing: null,
60
+ $duration: null
61
+ ) {
62
+ $d: $duration;
63
+
64
+ @if $d == null {
65
+ @if $speed == 'fast' {
66
+ $d: rc-theme('transition-duration-fast');
67
+ } @else if $speed == 'medium' {
68
+ $d: rc-theme('transition-duration-medium');
69
+ } @else if $speed == 'normal' {
70
+ $d: rc-theme('transition-duration-normal');
71
+ } @else {
72
+ $d: rc-theme('transition-duration-slow');
73
+ }
74
+ }
75
+
76
+ $t: null;
77
+ @if $timing != null {
78
+ $t: $timing;
79
+ } @else {
80
+ $t: rc-theme('transition-timing');
81
+ }
82
+
83
+ $result: ();
84
+
85
+ @each $prop in $properties {
86
+ $result: list.append($result, $prop $d $t, $separator: comma);
87
+ }
88
+
89
+ @media (prefers-reduced-motion: no-preference) {
90
+ transition: $result;
91
+ }
92
+ }
@@ -0,0 +1,34 @@
1
+ // =============================================================================
2
+ // Resolve Components — Helper Functions
3
+ // =============================================================================
4
+
5
+ @use 'sass:map';
6
+ @use 'sass:meta';
7
+ @use 'sass:list';
8
+
9
+ /// Deeply merge two maps recursively.
10
+ /// @param {Map} $map1 - Base map.
11
+ /// @param {Map} $map2 - Override map.
12
+ /// @return {Map} Merged map.
13
+ @function rc-map-deep-merge($map1, $map2) {
14
+ $result: $map1;
15
+
16
+ @each $key, $value in $map2 {
17
+ @if meta.type-of($value) == 'map' and map.has-key($result, $key) and meta.type-of(map.get($result, $key)) == 'map' {
18
+ $result: map.set($result, $key, rc-map-deep-merge(map.get($result, $key), $value));
19
+ } @else {
20
+ $result: map.set($result, $key, $value);
21
+ }
22
+ }
23
+
24
+ @return $result;
25
+ }
26
+
27
+ /// Set a key in a map, returning the new map.
28
+ /// @param {Map} $map
29
+ /// @param {String} $key
30
+ /// @param {*} $value
31
+ /// @return {Map}
32
+ @function rc-map-set($map, $key, $value) {
33
+ @return map.merge($map, ($key: $value));
34
+ }
@@ -0,0 +1,70 @@
1
+ // =============================================================================
2
+ // Resolve Components — Theme Value Resolution
3
+ // =============================================================================
4
+
5
+ @use 'sass:map';
6
+ @use 'sass:meta';
7
+ @use 'theming-variables' as vars;
8
+ @use 'register' as reg;
9
+
10
+ /// Recursively resolve an alias chain in a theme map.
11
+ ///
12
+ /// If the value of a key is itself a key in the same map, follow the chain
13
+ /// until a terminal (non-key) value is found.
14
+ ///
15
+ /// Example: card-bg → background-basic-color-1 → color-basic-100 → #ffffff
16
+ ///
17
+ /// @param {Map} $theme - The theme map.
18
+ /// @param {String} $key - Original key (for error reporting).
19
+ /// @param {*} $value - Current value to resolve.
20
+ /// @return {*} Terminal value.
21
+ @function rc-deep-find-value($theme, $key, $value) {
22
+ @if $value == null {
23
+ @return null;
24
+ }
25
+
26
+ $parent-value: map.get($theme, $value);
27
+
28
+ @if $parent-value != null and $parent-value != $value {
29
+ @return rc-deep-find-value($theme, $value, $parent-value);
30
+ }
31
+
32
+ @return $value;
33
+ }
34
+
35
+ /// Retrieve a themed value by variable name.
36
+ ///
37
+ /// In CSS custom properties mode (default), returns `var(--variable-name)`.
38
+ /// In pre-process mode, returns the resolved SCSS value.
39
+ /// In lazy-process mode, resolves at compile time by walking the alias chain.
40
+ ///
41
+ /// @param {String} $key - The theme variable name.
42
+ /// @return {*} The resolved value or CSS var() reference.
43
+ ///
44
+ /// @example scss
45
+ /// .my-component {
46
+ /// background: rc-theme('card-background-color');
47
+ /// // → var(--card-background-color) in CSS custom properties mode
48
+ /// }
49
+ @function rc-theme($key) {
50
+ $value: null;
51
+
52
+ @if vars.$rc-enable-css-custom-properties == true {
53
+ // CSS custom properties mode → emit var(--key)
54
+ $value: var(--#{$key});
55
+ } @else if vars.$rc-theme-process-mode == 'pre-process' {
56
+ // Pre-processed: value already resolved
57
+ $value: map.get(vars.$rc-processed-theme, $key);
58
+ } @else {
59
+ // Lazy mode: resolve at call time
60
+ $theme: reg.rc-get-registered-theme(vars.$rc-theme-name);
61
+ $raw: map.get($theme, $key);
62
+ $value: rc-deep-find-value($theme, $key, $raw);
63
+ }
64
+
65
+ @if $value == null {
66
+ @warn 'rc-theme: Variable "#{$key}" not found in theme "#{vars.$rc-theme-name}".';
67
+ }
68
+
69
+ @return $value;
70
+ }
@@ -0,0 +1,141 @@
1
+ // =============================================================================
2
+ // Resolve Components — Theme Installation
3
+ // =============================================================================
4
+
5
+ @use 'sass:map';
6
+ @use 'sass:meta';
7
+ @use 'sass:string';
8
+ @use 'theming-variables' as vars;
9
+ @use 'register' as reg;
10
+
11
+ // =============================================================================
12
+ // Public API
13
+ // =============================================================================
14
+
15
+ /// Main install mixin. Call this once in your application's global stylesheet.
16
+ ///
17
+ /// In CSS custom properties mode (default), it:
18
+ /// 1. Emits the @content block (global styles) once.
19
+ /// 2. Emits `.rc-theme-{name} { --var: value; }` blocks for each theme.
20
+ ///
21
+ /// @example scss
22
+ /// // In your styles.scss:
23
+ /// @use '@resolve-components/styles/theming' as *;
24
+ ///
25
+ /// @include rc-install() {
26
+ /// // Optional global styles
27
+ /// body { margin: 0; font-family: var(--font-family-primary); }
28
+ /// }
29
+ @mixin rc-install() {
30
+ @if vars.$rc-enable-css-custom-properties {
31
+ @include _rc-install-global-with-css-props() {
32
+ @content;
33
+ }
34
+ } @else {
35
+ @include _rc-install-global-without-css-props() {
36
+ @content;
37
+ }
38
+ }
39
+ }
40
+
41
+ /// Wraps component styles. Use this in every component's SCSS file.
42
+ ///
43
+ /// In CSS custom properties mode, simply emits the content.
44
+ /// In legacy mode, duplicates styles for each `.rc-theme-{name} :host { ... }`.
45
+ ///
46
+ /// @example scss
47
+ /// @use '@resolve-components/styles/theming' as *;
48
+ ///
49
+ /// @include rc-install-component() {
50
+ /// :host {
51
+ /// background: rc-theme('card-background-color');
52
+ /// }
53
+ /// }
54
+ @mixin rc-install-component() {
55
+ @if vars.$rc-enable-css-custom-properties {
56
+ @content;
57
+ } @else {
58
+ @each $theme-name in reg.rc-get-enabled-themes() {
59
+ .rc-theme-#{$theme-name} {
60
+ @content;
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ // =============================================================================
67
+ // Conditional Theme Mixins
68
+ // =============================================================================
69
+
70
+ /// Only emit styles for a specific theme.
71
+ /// @param {String} $name - Theme name.
72
+ @mixin rc-for-theme($name) {
73
+ @if vars.$rc-theme-name == $name {
74
+ @content;
75
+ }
76
+ }
77
+
78
+ /// Emit styles for all themes except the named one.
79
+ /// @param {String} $name - Theme name to exclude.
80
+ @mixin rc-except-theme($name) {
81
+ @if vars.$rc-theme-name != $name {
82
+ @content;
83
+ }
84
+ }
85
+
86
+ // =============================================================================
87
+ // Private Implementation
88
+ // =============================================================================
89
+
90
+ /// CSS Custom Properties installation (modern mode).
91
+ @mixin _rc-install-global-with-css-props() {
92
+ // Emit global styles once
93
+ @content;
94
+
95
+ // Emit CSS custom property blocks per theme
96
+ @each $theme-name in reg.rc-get-enabled-themes() {
97
+ $theme: reg.rc-get-registered-theme($theme-name);
98
+ @include _rc-install-css-properties($theme-name, $theme);
99
+ }
100
+ }
101
+
102
+ /// Emit CSS custom properties for a single theme.
103
+ /// Keys prefixed with `_` are emitted as direct CSS properties (e.g. `_color-scheme` → `color-scheme: dark`).
104
+ /// Aliases become `var(--other-var)` references, terminal values are emitted directly.
105
+ @mixin _rc-install-css-properties($theme-name, $theme) {
106
+ .rc-theme-#{$theme-name} {
107
+ @each $var, $value in $theme {
108
+ @if string.slice('#{$var}', 1, 1) == '_' {
109
+ // Direct CSS property — strip the leading underscore
110
+ #{string.slice('#{$var}', 2)}: #{$value};
111
+ } @else if
112
+ meta.type-of($value) ==
113
+ 'string' and
114
+ map.has-key($theme, $value)
115
+ {
116
+ // Value is an alias to another theme variable → CSS var reference
117
+ --#{$var}: var(--#{$value});
118
+ } @else {
119
+ // Terminal value → emit directly
120
+ --#{$var}: #{$value};
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ /// Legacy (non-CSS-props) installation.
127
+ @mixin _rc-install-global-without-css-props() {
128
+ @each $theme-name in reg.rc-get-enabled-themes() {
129
+ $theme: reg.rc-get-registered-theme($theme-name);
130
+
131
+ vars.$rc-theme-name: $theme-name;
132
+ vars.$rc-theme: $theme;
133
+
134
+ .rc-theme-#{$theme-name} {
135
+ @content;
136
+ }
137
+ }
138
+
139
+ vars.$rc-theme-name: null;
140
+ vars.$rc-theme: ();
141
+ }