Skip to content

Commit bf5ce26

Browse files
committed
feat(design-system): implement elevation and iconography foundations with style guide integration
- Created basic-elevation plugin for standardized z-index tokens and stacking gallery - Created basic-icons plugin for unified size scale and stroke-weight tokens - Updated all theme presets (Aurora, Midnight Pro, etc.) with tailored foundation scales - Integrated elevation and iconography specs into the consolidated Style Guide - Added click-to-copy functionality for all foundation tokens - Fixed state initialization to ensure new foundations are visible by default - Resolved diagnostic lints for import order and duplicate identifiers
1 parent 4259afb commit bf5ce26

8 files changed

Lines changed: 308 additions & 16 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ All notable changes to this project will be documented in this file. We will log
1616
- **Dev Handoff: Copy to Clipboard**: Added one-click copy functionality to the Style Guide, allowing users to instantly copy design tokens as CSS variables.
1717
- **Accessibility Master Audit**: Implemented a dedicated audit preview module that scans for WCAG contrast violations across all semantic color pairs and provides one-click "Nudge" fixes.
1818
- **Z-Index & Elevation Foundation**: Introduced a global stacking token system (`--z-index-*`) with a dedicated preview spec and interactive controls. All theme presets have been updated with standard elevation foundations.
19+
- **Iconography Scale Foundation**: Created a standardized icon scaling system (`--icon-size-*` and `--icon-stroke-*`). Updated all theme personas with tailored iconography scales and added a visual spec preview.
1920

2021
### Changed
2122

src/app/preview-registry.ts

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import type { ThemeConfig } from '../compiler/types';
2-
import { accessibilityPreviewModule } from '../plugins/basic-accessibility';
31
import { alertPreviewModule } from '../plugins/basic-alert';
42
import { buttonsPreviewModule } from '../plugins/basic-buttons';
53
import { cardPreviewModule } from '../plugins/basic-card';
64
import { colorsPreviewModule } from '../plugins/basic-colors';
5+
import { dashboardPreviewModule } from '../plugins/dashboard';
76
import { elevationPreviewModule } from '../plugins/basic-elevation';
7+
import { iconsPreviewModule } from '../plugins/basic-icons';
88
import { inputsPreviewModule } from '../plugins/basic-inputs';
99
import { layoutPreviewModule } from '../plugins/basic-layout';
1010
import { modalPreviewModule } from '../plugins/basic-modal';
@@ -17,33 +17,31 @@ import { styleguidePreviewModule } from '../plugins/basic-styleguide';
1717
import { surfacePreviewModule } from '../plugins/basic-surface';
1818
import { tablePreviewModule } from '../plugins/basic-table';
1919
import { typographyPreviewModule } from '../plugins/basic-typography';
20-
import { dashboardPreviewModule } from '../plugins/dashboard';
2120

2221
export type PreviewModule = {
2322
id: string;
2423
title: string;
25-
description?: string;
26-
render: (config: ThemeConfig) => string;
24+
render: (config: any) => string;
2725
};
2826

29-
export const previewModules = [
30-
sandboxPreviewModule,
27+
export const previewModules: PreviewModule[] = [
28+
dashboardPreviewModule,
3129
styleguidePreviewModule,
32-
accessibilityPreviewModule,
30+
sandboxPreviewModule,
3331
layoutPreviewModule,
34-
dashboardPreviewModule,
32+
typographyPreviewModule,
3533
colorsPreviewModule,
36-
buttonsPreviewModule,
37-
inputsPreviewModule,
38-
cardPreviewModule,
39-
surfacePreviewModule,
40-
radiusPreviewModule,
4134
spacingPreviewModule,
35+
radiusPreviewModule,
4236
shadowPreviewModule,
4337
elevationPreviewModule,
38+
iconsPreviewModule,
4439
alertPreviewModule,
4540
tablePreviewModule,
46-
typographyPreviewModule,
41+
buttonsPreviewModule,
42+
inputsPreviewModule,
43+
cardPreviewModule,
4744
modalPreviewModule,
4845
motionPreviewModule,
49-
] satisfies PreviewModule[];
46+
surfacePreviewModule,
47+
];

src/app/state.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { alertDefaults } from '../plugins/basic-alert';
33
import { buttonsDefaults } from '../plugins/basic-buttons';
44
import { cardDefaults } from '../plugins/basic-card';
55
import { colorsDefaults } from '../plugins/basic-colors';
6+
import { elevationDefaults } from '../plugins/basic-elevation';
7+
import { iconsDefaults } from '../plugins/basic-icons';
68
import { inputsDefaults } from '../plugins/basic-inputs';
79
import { layoutDefaults } from '../plugins/basic-layout';
810
import { modalDefaults } from '../plugins/basic-modal';
@@ -25,6 +27,8 @@ const defaultFragments: PartialThemeConfig[] = [
2527
typographyDefaults,
2628
spacingDefaults,
2729
radiusDefaults,
30+
elevationDefaults,
31+
iconsDefaults,
2832
sandboxDefaults,
2933
shadowDefaults,
3034
alertDefaults,

src/app/ui.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { buttonsControlModule } from '../plugins/basic-buttons';
44
import { cardControlModule } from '../plugins/basic-card';
55
import { colorsControlModule } from '../plugins/basic-colors';
66
import { elevationControlModule } from '../plugins/basic-elevation';
7+
import { iconsControlModule } from '../plugins/basic-icons';
78
import { inputsControlModule } from '../plugins/basic-inputs';
89
import { layoutControlModule } from '../plugins/basic-layout';
910
import { modalControlModule } from '../plugins/basic-modal';
@@ -36,6 +37,7 @@ export const controlsRegistry: ControlsRegistry = {
3637
radius: radiusControlModule,
3738
shadow: shadowControlModule,
3839
elevation: elevationControlModule,
40+
icons: iconsControlModule,
3941
buttons: buttonsControlModule,
4042
inputs: inputsControlModule,
4143
card: cardControlModule,

src/compiler/registry.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { buttonsCompilerEntry } from '../plugins/basic-buttons';
33
import { cardCompilerEntry } from '../plugins/basic-card';
44
import { colorsCompilerEntry } from '../plugins/basic-colors/index';
55
import { elevationCompilerEntry } from '../plugins/basic-elevation';
6+
import { iconsCompilerEntry } from '../plugins/basic-icons';
67
import { inputsCompilerEntry } from '../plugins/basic-inputs';
78
import { layoutCompilerEntry } from '../plugins/basic-layout';
89
import { modalCompilerEntry } from '../plugins/basic-modal';
@@ -35,6 +36,7 @@ export const compilerRegistry: EmitterEntry[] = [
3536
radiusCompilerEntry,
3637
shadowCompilerEntry,
3738
elevationCompilerEntry,
39+
iconsCompilerEntry,
3840
buttonsCompilerEntry,
3941
inputsCompilerEntry,
4042
cardCompilerEntry,

src/plugins/basic-icons/index.ts

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
import type { ControlModule } from '../../app/registry';
2+
import type { ThemeConfig } from '../../compiler/types';
3+
4+
declare module '../../compiler/types' {
5+
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
6+
interface ThemeModules {
7+
icons: {
8+
sizes: Record<string, string>;
9+
stroke: Record<string, string>;
10+
};
11+
}
12+
}
13+
14+
export const iconsCompilerEntry = {
15+
id: 'icons' as const,
16+
title: 'Iconography',
17+
isEnabled: (config: ThemeConfig) => Boolean(config.icons),
18+
emitTokens: (config: ThemeConfig) => {
19+
if (!config.icons) return '';
20+
const sizeLines = Object.keys(config.icons.sizes)
21+
.map((key) => ` --icon-size-${key}: ${config.icons.sizes[key]};`);
22+
const strokeLines = Object.keys(config.icons.stroke)
23+
.map((key) => ` --icon-stroke-${key}: ${config.icons.stroke[key]};`);
24+
return [...sizeLines, ...strokeLines].join('\n');
25+
},
26+
emitUtilities: (config: ThemeConfig) => {
27+
if (!config.icons) return '';
28+
const sizeUtils = Object.keys(config.icons.sizes)
29+
.map((key) => `.icon-${key} { width: var(--icon-size-${key}); height: var(--icon-size-${key}); }`);
30+
const strokeUtils = Object.keys(config.icons.stroke)
31+
.map((key) => `.icon-stroke-${key} { stroke-width: var(--icon-stroke-${key}); }`);
32+
return [...sizeUtils, ...strokeUtils].join('\n');
33+
},
34+
emitComponents: () => '',
35+
};
36+
37+
export const iconsControlModule: ControlModule = {
38+
id: 'icons',
39+
title: 'Iconography Scale',
40+
mount: (container, api) => {
41+
const renderSizeInput = (key: string, label: string) => `
42+
<div class="control-group">
43+
<label for="icon-size-${key}">${label}</label>
44+
<div class="range-with-value">
45+
<input id="icon-size-${key}" name="icons.sizes.${key}" type="range" min="8" max="64" step="2" />
46+
<span class="range-value" id="icon-size-${key}-val">0px</span>
47+
</div>
48+
</div>
49+
`;
50+
51+
const renderStrokeInput = (key: string, label: string) => `
52+
<div class="control-group">
53+
<label for="icon-stroke-${key}">${label}</label>
54+
<div class="range-with-value">
55+
<input id="icon-stroke-${key}" name="icons.stroke.${key}" type="range" min="0.5" max="4" step="0.5" />
56+
<span class="range-value" id="icon-stroke-${key}-val">0px</span>
57+
</div>
58+
</div>
59+
`;
60+
61+
container.innerHTML = `
62+
<div class="icons-editor" style="display: grid; gap: 16px;">
63+
<div class="control-subgroup">
64+
<h4 style="margin: 0 0 8px 0; font-size: 12px; text-transform: uppercase; opacity: 0.6;">Sizes</h4>
65+
${renderSizeInput('xs', 'Extra Small (xs)')}
66+
${renderSizeInput('sm', 'Small (sm)')}
67+
${renderSizeInput('md', 'Medium (md)')}
68+
${renderSizeInput('lg', 'Large (lg)')}
69+
${renderSizeInput('xl', 'Extra Large (xl)')}
70+
</div>
71+
<div class="control-subgroup" style="padding-top: 8px; border-top: 1px solid rgba(128,128,128,0.1);">
72+
<h4 style="margin: 0 0 8px 0; font-size: 12px; text-transform: uppercase; opacity: 0.6;">Stroke Weight</h4>
73+
${renderStrokeInput('thin', 'Thin')}
74+
${renderStrokeInput('base', 'Base')}
75+
${renderStrokeInput('bold', 'Bold')}
76+
</div>
77+
</div>
78+
`;
79+
80+
const sizeKeys = ['xs', 'sm', 'md', 'lg', 'xl'];
81+
const strokeKeys = ['thin', 'base', 'bold'];
82+
83+
const sync = () => {
84+
const cfg = api.getConfig();
85+
if (!cfg.icons) return;
86+
87+
sizeKeys.forEach((key) => {
88+
const input = container.querySelector<HTMLInputElement>(`#icon-size-${key}`);
89+
const valSpan = container.querySelector<HTMLElement>(`#icon-size-${key}-val`);
90+
if (input && cfg.icons.sizes[key]) {
91+
const val = Number.parseInt(cfg.icons.sizes[key], 10) || 0;
92+
input.value = String(val);
93+
if (valSpan) valSpan.textContent = `${val}px`;
94+
}
95+
});
96+
97+
strokeKeys.forEach((key) => {
98+
const input = container.querySelector<HTMLInputElement>(`#icon-stroke-${key}`);
99+
const valSpan = container.querySelector<HTMLElement>(`#icon-stroke-${key}-val`);
100+
if (input && cfg.icons.stroke[key]) {
101+
const val = Number.parseFloat(cfg.icons.stroke[key]) || 0;
102+
input.value = String(val);
103+
if (valSpan) valSpan.textContent = `${val}`;
104+
}
105+
});
106+
};
107+
108+
const onChange = (e: Event) => {
109+
const target = e.target as HTMLInputElement;
110+
const path = target.name.split('.'); // ['icons', 'sizes'|'stroke', key]
111+
const key = path[2];
112+
const type = path[1] as 'sizes' | 'stroke';
113+
const val = type === 'sizes' ? `${target.value}px` : target.value;
114+
115+
api.updateConfig((cfg) => ({
116+
...cfg,
117+
icons: {
118+
...cfg.icons,
119+
[type]: {
120+
...cfg.icons[type],
121+
[key]: val,
122+
},
123+
},
124+
}));
125+
};
126+
127+
container.addEventListener('input', onChange);
128+
const unsubscribe = api.subscribe(sync);
129+
sync();
130+
131+
return () => {
132+
unsubscribe();
133+
container.removeEventListener('input', onChange);
134+
};
135+
}
136+
};
137+
138+
export const iconsPreviewModule = {
139+
id: 'icons',
140+
title: 'Iconography Spec',
141+
render: (config: ThemeConfig) => {
142+
const icons = config.icons ?? iconsDefaults.icons;
143+
const sizes = ['xs', 'sm', 'md', 'lg', 'xl'];
144+
const strokes = ['thin', 'base', 'bold'];
145+
146+
// Simple SVG Icon Template
147+
const iconSvg = (sizeClass: string, strokeClass: string) => `
148+
<svg class="${sizeClass} ${strokeClass}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" style="transition: all 0.2s ease;">
149+
<circle cx="12" cy="12" r="10"></circle>
150+
<line x1="12" y1="8" x2="12" y2="12"></line>
151+
<line x1="12" y1="16" x2="12.01" y2="16"></line>
152+
</svg>
153+
`;
154+
155+
return `
156+
<div style="color: var(--on-background); font-family: var(--font-family, sans-serif);">
157+
<h3>Icon Sizes</h3>
158+
<p style="font-size: 12px; opacity: 0.7; margin-bottom: 24px;">Standardized size scale for all interface icons.</p>
159+
160+
<div style="display: flex; align-items: flex-end; gap: 32px; padding: 24px; background: var(--surface-card); border-radius: 12px; border: 1px solid rgba(128,128,128,0.1); margin-bottom: 40px;">
161+
${sizes.map(s => `
162+
<div style="display: flex; flex-direction: column; align-items: center; gap: 12px;">
163+
<div style="color: var(--color-primary-500);">${iconSvg(`icon-${s}`, 'icon-stroke-base')}</div>
164+
<div style="text-align: center;">
165+
<div style="font-weight: 800; font-size: 10px;">${s.toUpperCase()}</div>
166+
<div style="font-size: 9px; opacity: 0.5;">${icons.sizes[s]}</div>
167+
</div>
168+
</div>
169+
`).join('')}
170+
</div>
171+
172+
<h3>Stroke Weights</h3>
173+
<p style="font-size: 12px; opacity: 0.7; margin-bottom: 24px;">Standardized line weights for stroked icon sets.</p>
174+
175+
<div style="display: flex; gap: 40px; padding: 24px; background: var(--surface-card); border-radius: 12px; border: 1px solid rgba(128,128,128,0.1);">
176+
${strokes.map(st => `
177+
<div style="display: flex; align-items: center; gap: 16px;">
178+
<div style="color: var(--color-primary-500);">${iconSvg('icon-lg', `icon-stroke-${st}`)}</div>
179+
<div>
180+
<div style="font-weight: 800; font-size: 11px;">${st.toUpperCase()}</div>
181+
<div style="font-size: 10px; opacity: 0.5;">${icons.stroke[st]}px</div>
182+
</div>
183+
</div>
184+
`).join('')}
185+
</div>
186+
</div>
187+
`;
188+
},
189+
};
190+
191+
export const iconsDefaults = {
192+
icons: {
193+
sizes: {
194+
xs: '12px',
195+
sm: '16px',
196+
md: '24px',
197+
lg: '32px',
198+
xl: '48px',
199+
},
200+
stroke: {
201+
thin: '1',
202+
base: '2',
203+
bold: '3',
204+
},
205+
},
206+
};

src/plugins/basic-presets/presets.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ export const THEME_PRESETS: Record<string, PartialThemeConfig> = {
2525
elevation: {
2626
hide: -1, auto: 0, base: 0, raised: 10, dropdown: 1000, sticky: 1100,
2727
fixed: 1200, modal: 1300, popover: 1400, tooltip: 1500, toast: 1600, max: 2147483647
28+
},
29+
icons: {
30+
sizes: { xs: '12px', sm: '16px', md: '24px', lg: '32px', xl: '48px' },
31+
stroke: { thin: '1', base: '2', bold: '3' }
2832
}
2933
},
3034
midnight: {
@@ -54,6 +58,10 @@ export const THEME_PRESETS: Record<string, PartialThemeConfig> = {
5458
elevation: {
5559
hide: -1, auto: 0, base: 0, raised: 10, dropdown: 1000, sticky: 1100,
5660
fixed: 1200, modal: 1300, popover: 1400, tooltip: 1500, toast: 1600, max: 2147483647
61+
},
62+
icons: {
63+
sizes: { xs: '12px', sm: '18px', md: '28px', lg: '36px', xl: '56px' },
64+
stroke: { thin: '1', base: '1.5', bold: '2.5' }
5765
}
5866
},
5967
cyberpunk: {
@@ -83,6 +91,10 @@ export const THEME_PRESETS: Record<string, PartialThemeConfig> = {
8391
elevation: {
8492
hide: -1, auto: 0, base: 0, raised: 10, dropdown: 1000, sticky: 1100,
8593
fixed: 1200, modal: 1300, popover: 1400, tooltip: 1500, toast: 1600, max: 2147483647
94+
},
95+
icons: {
96+
sizes: { xs: '14px', sm: '20px', md: '32px', lg: '48px', xl: '64px' },
97+
stroke: { thin: '1.5', base: '2.5', bold: '4' }
8698
}
8799
},
88100
corporate: {
@@ -104,6 +116,10 @@ export const THEME_PRESETS: Record<string, PartialThemeConfig> = {
104116
elevation: {
105117
hide: -1, auto: 0, base: 0, raised: 10, dropdown: 1000, sticky: 1100,
106118
fixed: 1200, modal: 1300, popover: 1400, tooltip: 1500, toast: 1600, max: 2147483647
119+
},
120+
icons: {
121+
sizes: { xs: '12px', sm: '16px', md: '24px', lg: '32px', xl: '40px' },
122+
stroke: { thin: '1', base: '2', bold: '3' }
107123
}
108124
},
109125
minimalist: {
@@ -125,6 +141,10 @@ export const THEME_PRESETS: Record<string, PartialThemeConfig> = {
125141
elevation: {
126142
hide: -1, auto: 0, base: 0, raised: 10, dropdown: 1000, sticky: 1100,
127143
fixed: 1200, modal: 1300, popover: 1400, tooltip: 1500, toast: 1600, max: 2147483647
144+
},
145+
icons: {
146+
sizes: { xs: '10px', sm: '14px', md: '20px', lg: '28px', xl: '36px' },
147+
stroke: { thin: '0.5', base: '1', bold: '2' }
128148
}
129149
}
130150
};

0 commit comments

Comments
 (0)