Skip to content

Commit b1b275e

Browse files
committed
...
1 parent 764f7f0 commit b1b275e

File tree

11 files changed

+541
-396
lines changed

11 files changed

+541
-396
lines changed

homedocs/src/examples/layout/ButtonModsExample.tsx

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,34 @@ import { Button } from "cx/widgets";
22

33
// @index
44
export default (
5-
<div className="flex flex-wrap gap-2 items-center">
6-
<Button mod="primary">Primary</Button>
7-
<Button mod="danger">Danger</Button>
8-
<Button mod="hollow">Hollow</Button>
5+
<div className="flex flex-col gap-4">
6+
<div className="flex flex-wrap gap-2 items-center">
7+
<Button mod="primary">Primary</Button>
8+
<Button mod="primary" pressed>
9+
Pressed
10+
</Button>
11+
<Button mod="primary" disabled>
12+
Disabled
13+
</Button>
14+
</div>
15+
<div className="flex flex-wrap gap-2 items-center">
16+
<Button mod="danger">Danger</Button>
17+
<Button mod="danger" pressed>
18+
Pressed
19+
</Button>
20+
<Button mod="danger" disabled>
21+
Disabled
22+
</Button>
23+
</div>
24+
<div className="flex flex-wrap gap-2 items-center">
25+
<Button mod="hollow">Hollow</Button>
26+
<Button mod="hollow" pressed>
27+
Pressed
28+
</Button>
29+
<Button mod="hollow" disabled>
30+
Disabled
31+
</Button>
32+
</div>
933
</div>
1034
);
1135
// @index-end

homedocs/src/examples/layout/TabsExample.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ export default (
1919
<Tab tab="tab2" value={m.tab}>
2020
Tab 2
2121
</Tab>
22-
<Tab tab="tab3" value={m.tab} disabled>
22+
<Tab tab="tab3" value={m.tab}>
23+
Tab 3
24+
</Tab>
25+
<Tab tab="tab4" value={m.tab} disabled>
2326
Disabled
2427
</Tab>
2528
</div>
@@ -30,7 +33,10 @@ export default (
3033
<Tab tab="tab2" value={m.tab} mod="line">
3134
Tab 2
3235
</Tab>
33-
<Tab tab="tab3" value={m.tab} mod="line" disabled>
36+
<Tab tab="tab3" value={m.tab} mod="line">
37+
Tab 3
38+
</Tab>
39+
<Tab tab="tab4" value={m.tab} mod="line" disabled>
3440
Disabled
3541
</Tab>
3642
</div>
@@ -42,7 +48,10 @@ export default (
4248
<Tab tab="tab2" value={m.tab} mod="classic">
4349
Tab 2
4450
</Tab>
45-
<Tab tab="tab3" value={m.tab} mod="classic" disabled>
51+
<Tab tab="tab3" value={m.tab} mod="classic">
52+
Tab 3
53+
</Tab>
54+
<Tab tab="tab4" value={m.tab} mod="classic" disabled>
4655
Disabled
4756
</Tab>
4857
</div>

homedocs/src/examples/theme-editor/data.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ export const defaultCategories: Category[] = [
77
icon: "star",
88
variables: [
99
{ name: "--cx-theme-primary-color", label: "Main primary color", value: "black", type: "color" },
10-
{ name: "--cx-theme-primary-color-light", label: "Lighter shade of primary", value: "#333", type: "color" },
11-
{ name: "--cx-theme-primary-color-dark", label: "Darker shade of primary", value: "#000", type: "color" },
12-
{ name: "--cx-theme-primary-text-color", label: "Text color on primary backgrounds", value: "black", type: "color" },
1310
],
1411
},
1512
{
@@ -19,7 +16,6 @@ export const defaultCategories: Category[] = [
1916
variables: [
2017
{ name: "--cx-theme-accent-color", label: "Accent color for highlights", value: "lightgray", type: "color" },
2118
{ name: "--cx-theme-danger-color", label: "Danger/error color", value: "#d32f2f", type: "color" },
22-
{ name: "--cx-theme-danger-color-dark", label: "Darker danger color", value: "#b71c1c", type: "color" },
2319
],
2420
},
2521
{
@@ -28,8 +24,6 @@ export const defaultCategories: Category[] = [
2824
icon: "type",
2925
variables: [
3026
{ name: "--cx-theme-color", label: "Default text color", value: "rgba(0, 0, 0, 0.87)", type: "color" },
31-
{ name: "--cx-theme-color-light", label: "Light/muted text color", value: "rgba(0, 0, 0, 0.6)", type: "color" },
32-
{ name: "--cx-theme-secondary-text-color", label: "Secondary text color", value: "#757575", type: "color" },
3327
],
3428
},
3529
{
@@ -72,13 +66,23 @@ export const defaultCategories: Category[] = [
7266
{ name: "--cx-theme-icon-size", label: "Icon size", value: "16px", type: "size" },
7367
],
7468
},
69+
{
70+
id: "active-states",
71+
name: "Active States",
72+
icon: "mouse-pointer",
73+
variables: [
74+
{ name: "--cx-theme-active-state-color", label: "Overlay color for hover/press (black for light themes, white for dark)", value: "black", type: "color" },
75+
{ name: "--cx-theme-active-state-hover-amount", label: "Hover overlay opacity", value: "8%", type: "text" },
76+
{ name: "--cx-theme-active-state-pressed-amount", label: "Pressed overlay opacity", value: "12%", type: "text" },
77+
],
78+
},
7579
{
7680
id: "buttons",
7781
name: "Buttons",
7882
icon: "square",
7983
variables: [
80-
{ name: "--cx-theme-button-background-color", label: "Button background", value: "#f5f5f5", type: "color" },
81-
{ name: "--cx-theme-button-border-color", label: "Button border color", value: "lightgray", type: "color" },
84+
{ name: "--cx-theme-default-button-background-color", label: "Default button background", value: "color-mix(in srgb, var(--cx-theme-surface-color), var(--cx-theme-active-state-color) 4%)", type: "color" },
85+
{ name: "--cx-theme-default-button-border-color", label: "Default button border color", value: "var(--cx-theme-border-color)", type: "color" },
8286
],
8387
},
8488
{

homedocs/src/examples/theme-editor/examples.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ export const examples: ExampleDef[] = [
1616
categories: ["buttons", "primary", "border", "sizing", "shadows", "transitions"],
1717
component: () => import("../layout/ButtonBasicExample"),
1818
},
19+
{
20+
id: "button-mods",
21+
name: "Button Mods",
22+
categories: ["buttons", "primary", "accent", "active-states", "border", "sizing", "shadows", "transitions"],
23+
component: () => import("../layout/ButtonModsExample"),
24+
},
1925

2026
// Inputs
2127
{

meta/CSS_VARIABLES.md

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# CxJS CSS Variables Design Document
2+
3+
This document defines the naming conventions and structure for CSS custom properties in CxJS theming.
4+
5+
## Implementation
6+
7+
The CSS variables theming system is implemented in the `cx-theme-variables` package (`packages/cx-theme-variables/`). This package provides a base theme that can be customized at runtime by overriding CSS variables.
8+
9+
## CSS Custom Properties Limitation
10+
11+
**Important:** CSS custom properties have a fundamental limitation that affects sub-theming. When you define a derived variable on `:root` that references another variable:
12+
13+
```scss
14+
:root {
15+
--cx-theme-primary-color: blue;
16+
--cx-primary-button-hover-background-color: color-mix(in srgb, var(--cx-theme-primary-color), white 20%);
17+
}
18+
```
19+
20+
And then override the base variable in a container:
21+
22+
```scss
23+
.my-container {
24+
--cx-theme-primary-color: red;
25+
}
26+
```
27+
28+
The derived variable (`--cx-primary-button-hover-background-color`) **does NOT recalculate** inside `.my-container`. It still uses the `:root` computed value.
29+
30+
**Consequence:** We cannot use intermediate component-level CSS variables for derived values. Instead, `color-mix()` calculations must be placed directly in the SCSS map overrides so they reference theme variables directly and recalculate properly in sub-themed containers.
31+
32+
## Layers
33+
34+
| Layer | Prefix | Purpose |
35+
|-------|--------|---------|
36+
| Theme | `--cx-theme-*` | High-level design tokens (colors, sizing, effects) |
37+
| Theme Defaults | `--cx-theme-default-*` | Base values for components (optional, for simple references) |
38+
39+
**Note:** Component-level variables (`--cx-{component}-*`) are intentionally avoided for derived values due to the limitation above. Use `color-mix()` directly in SCSS maps instead.
40+
41+
## Layer 1: Theme (`--cx-theme-*`)
42+
43+
Minimal set of design tokens that define the theme's identity.
44+
45+
```scss
46+
:root {
47+
// Primary colors
48+
--cx-theme-primary-color: #1976d2;
49+
50+
// Status colors
51+
--cx-theme-accent-color: #ffc107;
52+
--cx-theme-danger-color: #d32f2f;
53+
54+
// Text colors
55+
--cx-theme-color: rgba(0, 0, 0, 0.87);
56+
57+
// Surface colors
58+
--cx-theme-background-color: white;
59+
--cx-theme-surface-color: white;
60+
61+
// Border
62+
--cx-theme-border-color: lightgray;
63+
64+
// Active state colors (black for light themes, white for dark themes)
65+
--cx-theme-active-state-color: black;
66+
--cx-theme-active-state-hover-amount: 8%;
67+
--cx-theme-active-state-pressed-amount: 12%;
68+
69+
// Effects
70+
--cx-theme-box-shadow: ...;
71+
--cx-theme-focus-box-shadow: ...;
72+
--cx-theme-transition: all 0.2s ease;
73+
74+
// Sizing
75+
--cx-theme-border-radius: 4px;
76+
--cx-theme-box-padding: 5px;
77+
--cx-theme-box-line-height: 24px;
78+
}
79+
```
80+
81+
## Layer 2: Theme Defaults (`--cx-theme-default-*`)
82+
83+
Simple default values for components. Only use for direct references, not for values that will be used in further calculations.
84+
85+
```scss
86+
:root {
87+
--cx-theme-default-button-background-color: color-mix(in srgb, var(--cx-theme-surface-color), var(--cx-theme-active-state-color) 4%);
88+
--cx-theme-default-button-border-color: var(--cx-theme-border-color);
89+
}
90+
```
91+
92+
## Using color-mix() in SCSS Maps
93+
94+
Since we cannot use intermediate CSS variables for derived values, place `color-mix()` calculations directly in the SCSS map overrides:
95+
96+
```scss
97+
$cx-button-state-style-map: cx-deep-map-merge(
98+
$cx-button-state-style-map,
99+
(
100+
default: (
101+
background-color: var(--cx-theme-default-button-background-color),
102+
border-color: var(--cx-theme-default-button-border-color),
103+
),
104+
hover: (
105+
// Calculate directly - references theme variable, will recalculate in sub-themed containers
106+
background-color: color-mix(in srgb, var(--cx-theme-default-button-background-color), var(--cx-theme-active-state-color) var(--cx-theme-active-state-hover-amount)),
107+
),
108+
active: (
109+
background-color: color-mix(in srgb, var(--cx-theme-default-button-background-color), var(--cx-theme-active-state-color) var(--cx-theme-active-state-pressed-amount)),
110+
),
111+
)
112+
);
113+
114+
$cx-button-mods: cx-deep-map-merge(
115+
$cx-button-mods,
116+
(
117+
primary: (
118+
default: (
119+
background-color: var(--cx-theme-primary-color),
120+
color: var(--cx-theme-background-color),
121+
border-color: color-mix(in srgb, var(--cx-theme-primary-color), var(--cx-theme-active-state-color) 20%),
122+
),
123+
hover: (
124+
background-color: color-mix(in srgb, var(--cx-theme-primary-color), var(--cx-theme-background-color) 20%),
125+
),
126+
active: (
127+
background-color: color-mix(in srgb, var(--cx-theme-primary-color), var(--cx-theme-active-state-color) 20%),
128+
),
129+
),
130+
)
131+
);
132+
```
133+
134+
## File Structure
135+
136+
```
137+
packages/cx-theme-variables/src/
138+
├── index.scss # Main entry
139+
├── variables.scss # Theme variables, @forward cx
140+
├── maps.scss # @use component files, map overrides
141+
├── widgets/
142+
│ ├── Button.scss # Button map overrides with color-mix()
143+
│ ├── form/
144+
│ │ └── ...
145+
│ ├── grid/
146+
│ │ └── ...
147+
│ └── ...
148+
```
149+
150+
## Sub-theming
151+
152+
Override theme variables in containers. Because `color-mix()` is in the SCSS maps (not intermediate CSS variables), derived colors recalculate correctly.
153+
154+
**Toolbar with primary-colored buttons:**
155+
```scss
156+
.toolbar {
157+
--cx-theme-default-button-background-color: var(--cx-theme-primary-color);
158+
--cx-theme-default-button-border-color: transparent;
159+
}
160+
```
161+
162+
**Dark mode:**
163+
```scss
164+
.dark-mode {
165+
--cx-theme-primary-color: #90caf9;
166+
--cx-theme-surface-color: #1e1e1e;
167+
--cx-theme-background-color: #121212;
168+
--cx-theme-color: rgba(255, 255, 255, 0.87);
169+
--cx-theme-border-color: #424242;
170+
--cx-theme-active-state-color: white; // Lighten on hover instead of darken
171+
}
172+
```
173+
174+
## SCSS Variable Overrides
175+
176+
Set SCSS `$cx-default-*` variables to `null` in the `@forward` block when they are controlled via CSS variables and maps:
177+
178+
```scss
179+
@forward "cx/src/variables" with (
180+
$cx-default-button-background-color: null !default,
181+
$cx-default-button-border-color: null !default,
182+
...
183+
);
184+
```
185+
186+
## Guidelines
187+
188+
1. **Minimize variables** - Only expose theme-level design tokens
189+
2. **No intermediate derived variables** - Use `color-mix()` directly in SCSS maps
190+
3. **Use active-state-color** - Avoid hardcoding `black` or `white` for state changes
191+
4. **Reference theme variables** - Always reference `--cx-theme-*` variables so sub-theming works
192+
5. **Test sub-theming** - Verify that overriding theme variables in containers produces expected results

packages/cx-theme-variables/src/index.scss

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,22 @@
1212

1313
// 4. NOW load cx/src/index which generates CSS using configured values
1414
@use "cx/src/index" as *;
15+
16+
// 5. Load include utilities for conditional CSS
17+
@use "cx/src/util/scss/include.scss" as *;
18+
19+
$block: map.get($cx-besm, block);
20+
$element: map.get($cx-besm, element);
21+
$state: map.get($cx-besm, state);
22+
$mod: map.get($cx-besm, mod);
23+
24+
// 6. Component-specific overrides
25+
@if (cx-included("cx/widgets/Tab")) {
26+
//TAB
27+
.#{$block}tab.#{$mod}classic {
28+
&:first-child {
29+
border-left-width: 1px;
30+
border-left-color: var(--cx-theme-border-color);
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)