@umituz/web-design-system 6.11.0 → 6.13.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/dist/styles.css +1 -1
- package/package.json +3 -1
- package/src/__tests__/layout-primitives.test.tsx +85 -0
- package/src/__tests__/theme-provider.test.tsx +167 -0
- package/src/domain/types/index.ts +9 -0
- package/src/domain/types/theme.types.ts +56 -0
- package/src/index.ts +10 -7
- package/src/infrastructure/themes/apply-theme.ts +149 -0
- package/src/infrastructure/themes/create-theme.ts +49 -0
- package/src/infrastructure/themes/default.theme.ts +73 -0
- package/src/infrastructure/themes/editorial.theme.ts +65 -0
- package/src/infrastructure/themes/index.ts +8 -0
- package/src/infrastructure/themes/midnight.theme.ts +79 -0
- package/src/infrastructure/themes/ocean.theme.ts +71 -0
- package/src/infrastructure/themes/registry.ts +28 -0
- package/src/presentation/atoms/Grid.tsx +129 -0
- package/src/presentation/atoms/Row.tsx +81 -0
- package/src/presentation/atoms/Stack.tsx +78 -0
- package/src/presentation/atoms/index.ts +10 -1
- package/src/presentation/providers/DesignSystemProvider.tsx +111 -0
- package/src/presentation/providers/index.ts +5 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
2
|
+
import { render, screen, act } from '@testing-library/react';
|
|
3
|
+
import {
|
|
4
|
+
DesignSystemProvider,
|
|
5
|
+
useDesignSystem,
|
|
6
|
+
} from '../presentation/providers/DesignSystemProvider';
|
|
7
|
+
import {
|
|
8
|
+
getTheme,
|
|
9
|
+
registerTheme,
|
|
10
|
+
applyTheme,
|
|
11
|
+
themeToCssVariables,
|
|
12
|
+
createTheme,
|
|
13
|
+
midnightTheme,
|
|
14
|
+
editorialTheme,
|
|
15
|
+
oceanTheme,
|
|
16
|
+
defaultTheme,
|
|
17
|
+
} from '../infrastructure/themes';
|
|
18
|
+
import type { ThemeConfig } from '../domain/types';
|
|
19
|
+
|
|
20
|
+
describe('Theme System and Provider', () => {
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
localStorage.clear();
|
|
23
|
+
document.documentElement.className = '';
|
|
24
|
+
document.documentElement.removeAttribute('data-theme');
|
|
25
|
+
const existing = document.getElementById('umituz-theme-vars');
|
|
26
|
+
if (existing) existing.remove();
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('retrieves default and built-in themes by generic archetype name', () => {
|
|
30
|
+
expect(getTheme('midnight').name).toBe('midnight');
|
|
31
|
+
expect(getTheme('editorial').name).toBe('editorial');
|
|
32
|
+
expect(getTheme('ocean').name).toBe('ocean');
|
|
33
|
+
expect(getTheme('default').name).toBe('default');
|
|
34
|
+
expect(getTheme('non-existent').name).toBe('default');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('allows createTheme to customize an existing preset or define from scratch', () => {
|
|
38
|
+
const customized = createTheme('midnight', {
|
|
39
|
+
name: 'midnight-custom',
|
|
40
|
+
colors: { primary: '#10b981' },
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
expect(customized.name).toBe('midnight-custom');
|
|
44
|
+
expect(customized.colors.primary).toBe('#10b981');
|
|
45
|
+
expect(customized.colors.background).toBe(midnightTheme.colors.background);
|
|
46
|
+
|
|
47
|
+
const fromScratch = createTheme({
|
|
48
|
+
name: 'standalone',
|
|
49
|
+
colors: {
|
|
50
|
+
background: '#000',
|
|
51
|
+
foreground: '#fff',
|
|
52
|
+
card: '#111',
|
|
53
|
+
cardForeground: '#fff',
|
|
54
|
+
popover: '#111',
|
|
55
|
+
popoverForeground: '#fff',
|
|
56
|
+
primary: '#f00',
|
|
57
|
+
primaryForeground: '#fff',
|
|
58
|
+
secondary: '#222',
|
|
59
|
+
secondaryForeground: '#fff',
|
|
60
|
+
muted: '#222',
|
|
61
|
+
mutedForeground: '#888',
|
|
62
|
+
accent: '#333',
|
|
63
|
+
accentForeground: '#fff',
|
|
64
|
+
border: '#444',
|
|
65
|
+
input: '#444',
|
|
66
|
+
destructive: '#f00',
|
|
67
|
+
destructiveForeground: '#fff',
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
expect(fromScratch.name).toBe('standalone');
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('registers and retrieves custom themes', () => {
|
|
74
|
+
const custom: ThemeConfig = {
|
|
75
|
+
name: 'custom-corp',
|
|
76
|
+
colors: {
|
|
77
|
+
background: '#111111',
|
|
78
|
+
foreground: '#eeeeee',
|
|
79
|
+
card: '#222222',
|
|
80
|
+
cardForeground: '#eeeeee',
|
|
81
|
+
popover: '#222222',
|
|
82
|
+
popoverForeground: '#eeeeee',
|
|
83
|
+
primary: '#ff0055',
|
|
84
|
+
primaryForeground: '#ffffff',
|
|
85
|
+
secondary: '#333333',
|
|
86
|
+
secondaryForeground: '#ffffff',
|
|
87
|
+
muted: '#333333',
|
|
88
|
+
mutedForeground: '#888888',
|
|
89
|
+
accent: '#444444',
|
|
90
|
+
accentForeground: '#ffffff',
|
|
91
|
+
border: '#555555',
|
|
92
|
+
input: '#555555',
|
|
93
|
+
destructive: '#ff0000',
|
|
94
|
+
destructiveForeground: '#ffffff',
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
registerTheme(custom);
|
|
99
|
+
expect(getTheme('custom-corp').colors.primary).toBe('#ff0055');
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('converts theme config to CSS variables string', () => {
|
|
103
|
+
const css = themeToCssVariables(midnightTheme);
|
|
104
|
+
expect(css).toContain('--color-primary: #4e8cff;');
|
|
105
|
+
expect(css).toContain('--app-primary: #4e8cff;');
|
|
106
|
+
expect(css).toContain('--color-background: #060a12;');
|
|
107
|
+
expect(css).toContain('--font-sans:');
|
|
108
|
+
|
|
109
|
+
const editorialCss = themeToCssVariables(editorialTheme);
|
|
110
|
+
expect(editorialCss).toContain('--color-primary: #33406b;');
|
|
111
|
+
|
|
112
|
+
const oceanCss = themeToCssVariables(oceanTheme);
|
|
113
|
+
expect(oceanCss).toContain('--color-primary: #0284c7;');
|
|
114
|
+
|
|
115
|
+
const defaultCss = themeToCssVariables(defaultTheme);
|
|
116
|
+
expect(defaultCss).toContain('--color-primary: #2563eb;');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('applies theme variables to document head', () => {
|
|
120
|
+
applyTheme(midnightTheme);
|
|
121
|
+
const styleEl = document.getElementById('umituz-theme-vars');
|
|
122
|
+
expect(styleEl).toBeTruthy();
|
|
123
|
+
expect(styleEl?.textContent).toContain('--color-primary: #4e8cff;');
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('DesignSystemProvider provides theme and allows mode toggle', () => {
|
|
127
|
+
function Consumer() {
|
|
128
|
+
const { theme, mode, toggleMode, setMode } = useDesignSystem();
|
|
129
|
+
return (
|
|
130
|
+
<div>
|
|
131
|
+
<span data-testid="theme-name">{theme.name}</span>
|
|
132
|
+
<span data-testid="theme-mode">{mode}</span>
|
|
133
|
+
<button data-testid="toggle-btn" onClick={toggleMode}>
|
|
134
|
+
Toggle
|
|
135
|
+
</button>
|
|
136
|
+
<button data-testid="set-dark" onClick={() => setMode('dark')}>
|
|
137
|
+
Dark
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
render(
|
|
144
|
+
<DesignSystemProvider theme="midnight" mode="dark">
|
|
145
|
+
<Consumer />
|
|
146
|
+
</DesignSystemProvider>
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(screen.getByTestId('theme-name').textContent).toBe('midnight');
|
|
150
|
+
expect(screen.getByTestId('theme-mode').textContent).toBe('dark');
|
|
151
|
+
expect(document.documentElement.classList.contains('dark')).toBe(true);
|
|
152
|
+
|
|
153
|
+
act(() => {
|
|
154
|
+
screen.getByTestId('toggle-btn').click();
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
expect(screen.getByTestId('theme-mode').textContent).toBe('light');
|
|
158
|
+
expect(document.documentElement.classList.contains('dark')).toBe(false);
|
|
159
|
+
|
|
160
|
+
act(() => {
|
|
161
|
+
screen.getByTestId('set-dark').click();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
expect(screen.getByTestId('theme-mode').textContent).toBe('dark');
|
|
165
|
+
expect(document.documentElement.classList.contains('dark')).toBe(true);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type ThemeMode = 'light' | 'dark' | 'system';
|
|
2
|
+
|
|
3
|
+
export interface ThemeColors {
|
|
4
|
+
background: string;
|
|
5
|
+
foreground: string;
|
|
6
|
+
card: string;
|
|
7
|
+
cardForeground: string;
|
|
8
|
+
popover: string;
|
|
9
|
+
popoverForeground: string;
|
|
10
|
+
primary: string;
|
|
11
|
+
primaryForeground: string;
|
|
12
|
+
secondary: string;
|
|
13
|
+
secondaryForeground: string;
|
|
14
|
+
muted: string;
|
|
15
|
+
mutedForeground: string;
|
|
16
|
+
accent: string;
|
|
17
|
+
accentForeground: string;
|
|
18
|
+
border: string;
|
|
19
|
+
input: string;
|
|
20
|
+
destructive: string;
|
|
21
|
+
destructiveForeground: string;
|
|
22
|
+
ring?: string;
|
|
23
|
+
success?: string;
|
|
24
|
+
successForeground?: string;
|
|
25
|
+
warning?: string;
|
|
26
|
+
warningForeground?: string;
|
|
27
|
+
info?: string;
|
|
28
|
+
infoForeground?: string;
|
|
29
|
+
violet?: string;
|
|
30
|
+
violetForeground?: string;
|
|
31
|
+
primarySoft?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ThemeTypography {
|
|
35
|
+
fontSans?: string;
|
|
36
|
+
fontMono?: string;
|
|
37
|
+
fontDisplay?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ThemeRadii {
|
|
41
|
+
xs?: string;
|
|
42
|
+
sm?: string;
|
|
43
|
+
md?: string;
|
|
44
|
+
lg?: string;
|
|
45
|
+
xl?: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ThemeConfig {
|
|
49
|
+
name: string;
|
|
50
|
+
mode?: 'light' | 'dark';
|
|
51
|
+
colors: ThemeColors;
|
|
52
|
+
darkColors?: Partial<ThemeColors>;
|
|
53
|
+
typography?: ThemeTypography;
|
|
54
|
+
radii?: ThemeRadii;
|
|
55
|
+
customVars?: Record<string, string>;
|
|
56
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,19 +1,23 @@
|
|
|
1
|
-
// Core Tokens, Types, Constants, Hooks & Utils
|
|
2
1
|
export * from './domain/tokens';
|
|
3
2
|
export * from './domain/types';
|
|
4
3
|
export * from './infrastructure/utils';
|
|
5
4
|
export * from './infrastructure/constants';
|
|
5
|
+
export * from './infrastructure/themes';
|
|
6
6
|
export * from './presentation/hooks';
|
|
7
|
-
|
|
8
|
-
// Templates
|
|
9
7
|
export * from './presentation/templates';
|
|
10
|
-
|
|
11
|
-
// Organisms
|
|
12
8
|
export * from './presentation/organisms';
|
|
9
|
+
export * from './presentation/providers';
|
|
10
|
+
|
|
13
11
|
|
|
14
|
-
// Atoms
|
|
15
12
|
export type { ButtonProps } from './presentation/atoms/Button';
|
|
16
13
|
export { Button, buttonVariants } from './presentation/atoms/Button';
|
|
14
|
+
export type { StackProps, StackGap, StackAlign, StackJustify } from './presentation/atoms/Stack';
|
|
15
|
+
export { Stack } from './presentation/atoms/Stack';
|
|
16
|
+
export type { RowProps, RowGap, RowAlign, RowJustify } from './presentation/atoms/Row';
|
|
17
|
+
export { Row } from './presentation/atoms/Row';
|
|
18
|
+
export type { GridProps, GridCols, GridColsNumber, ResponsiveGridCols, GridGap } from './presentation/atoms/Grid';
|
|
19
|
+
export { Grid } from './presentation/atoms/Grid';
|
|
20
|
+
|
|
17
21
|
export type { BadgeProps, BadgeVariant } from './presentation/atoms/Badge';
|
|
18
22
|
export { Badge } from './presentation/atoms/Badge';
|
|
19
23
|
export type { InputProps } from './presentation/atoms/Input';
|
|
@@ -74,7 +78,6 @@ export type { CopyButtonProps } from './presentation/atoms/CopyButton';
|
|
|
74
78
|
export { StatusDot } from './presentation/atoms/StatusDot';
|
|
75
79
|
export type { StatusDotProps, StatusType } from './presentation/atoms/StatusDot';
|
|
76
80
|
|
|
77
|
-
// Molecules
|
|
78
81
|
export type { FormFieldProps } from './presentation/molecules/FormField';
|
|
79
82
|
export { FormField } from './presentation/molecules/FormField';
|
|
80
83
|
export type { SearchBoxProps } from './presentation/molecules/SearchBox';
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { ThemeConfig, ThemeColors } from '../../domain/types';
|
|
2
|
+
import { getTheme } from './registry';
|
|
3
|
+
|
|
4
|
+
function colorPropsToCss(colors: ThemeColors): string {
|
|
5
|
+
const lines: string[] = [];
|
|
6
|
+
const entries: [keyof ThemeColors, string][] = [
|
|
7
|
+
['background', '--color-background'],
|
|
8
|
+
['foreground', '--color-foreground'],
|
|
9
|
+
['card', '--color-card'],
|
|
10
|
+
['cardForeground', '--color-card-foreground'],
|
|
11
|
+
['popover', '--color-popover'],
|
|
12
|
+
['popoverForeground', '--color-popover-foreground'],
|
|
13
|
+
['primary', '--color-primary'],
|
|
14
|
+
['primaryForeground', '--color-primary-foreground'],
|
|
15
|
+
['secondary', '--color-secondary'],
|
|
16
|
+
['secondaryForeground', '--color-secondary-foreground'],
|
|
17
|
+
['muted', '--color-muted'],
|
|
18
|
+
['mutedForeground', '--color-muted-foreground'],
|
|
19
|
+
['accent', '--color-accent'],
|
|
20
|
+
['accentForeground', '--color-accent-foreground'],
|
|
21
|
+
['border', '--color-border'],
|
|
22
|
+
['input', '--color-input'],
|
|
23
|
+
['destructive', '--color-destructive'],
|
|
24
|
+
['destructiveForeground', '--color-destructive-foreground'],
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
for (const [key, cssVar] of entries) {
|
|
28
|
+
const val = colors[key];
|
|
29
|
+
if (val) {
|
|
30
|
+
lines.push(`${cssVar}: ${val};`);
|
|
31
|
+
const appVar = cssVar.replace('--color-', '--app-');
|
|
32
|
+
lines.push(`${appVar}: ${val};`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (colors.ring) {
|
|
37
|
+
lines.push(`--color-ring: ${colors.ring};`);
|
|
38
|
+
lines.push(`--app-ring: ${colors.ring};`);
|
|
39
|
+
}
|
|
40
|
+
if (colors.success) {
|
|
41
|
+
lines.push(`--color-success: ${colors.success};`);
|
|
42
|
+
lines.push(`--app-success: ${colors.success};`);
|
|
43
|
+
}
|
|
44
|
+
if (colors.successForeground) {
|
|
45
|
+
lines.push(`--color-success-foreground: ${colors.successForeground};`);
|
|
46
|
+
lines.push(`--app-success-foreground: ${colors.successForeground};`);
|
|
47
|
+
}
|
|
48
|
+
if (colors.warning) {
|
|
49
|
+
lines.push(`--color-warning: ${colors.warning};`);
|
|
50
|
+
lines.push(`--app-warning: ${colors.warning};`);
|
|
51
|
+
}
|
|
52
|
+
if (colors.warningForeground) {
|
|
53
|
+
lines.push(`--color-warning-foreground: ${colors.warningForeground};`);
|
|
54
|
+
lines.push(`--app-warning-foreground: ${colors.warningForeground};`);
|
|
55
|
+
}
|
|
56
|
+
if (colors.info) {
|
|
57
|
+
lines.push(`--color-info: ${colors.info};`);
|
|
58
|
+
lines.push(`--app-info: ${colors.info};`);
|
|
59
|
+
}
|
|
60
|
+
if (colors.infoForeground) {
|
|
61
|
+
lines.push(`--color-info-foreground: ${colors.infoForeground};`);
|
|
62
|
+
lines.push(`--app-info-foreground: ${colors.infoForeground};`);
|
|
63
|
+
}
|
|
64
|
+
if (colors.violet) {
|
|
65
|
+
lines.push(`--color-violet: ${colors.violet};`);
|
|
66
|
+
lines.push(`--app-violet: ${colors.violet};`);
|
|
67
|
+
}
|
|
68
|
+
if (colors.violetForeground) {
|
|
69
|
+
lines.push(`--color-violet-foreground: ${colors.violetForeground};`);
|
|
70
|
+
lines.push(`--app-violet-foreground: ${colors.violetForeground};`);
|
|
71
|
+
}
|
|
72
|
+
if (colors.primarySoft) {
|
|
73
|
+
lines.push(`--color-primary-soft: ${colors.primarySoft};`);
|
|
74
|
+
lines.push(`--app-primary-soft: ${colors.primarySoft};`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return lines.join('\n ');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function themeToCssVariables(theme: ThemeConfig): string {
|
|
81
|
+
const rootLines: string[] = [colorPropsToCss(theme.colors)];
|
|
82
|
+
|
|
83
|
+
if (theme.typography?.fontSans) {
|
|
84
|
+
rootLines.push(`--font-sans: ${theme.typography.fontSans};`);
|
|
85
|
+
rootLines.push(`--app-font-sans: ${theme.typography.fontSans};`);
|
|
86
|
+
}
|
|
87
|
+
if (theme.typography?.fontMono) {
|
|
88
|
+
rootLines.push(`--font-mono: ${theme.typography.fontMono};`);
|
|
89
|
+
rootLines.push(`--app-font-mono: ${theme.typography.fontMono};`);
|
|
90
|
+
}
|
|
91
|
+
if (theme.typography?.fontDisplay) {
|
|
92
|
+
rootLines.push(`--font-display: ${theme.typography.fontDisplay};`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (theme.radii) {
|
|
96
|
+
if (theme.radii.xs) rootLines.push(`--radius-xs: ${theme.radii.xs};`);
|
|
97
|
+
if (theme.radii.sm) rootLines.push(`--radius-sm: ${theme.radii.sm};`);
|
|
98
|
+
if (theme.radii.md) rootLines.push(`--radius-md: ${theme.radii.md};`);
|
|
99
|
+
if (theme.radii.lg) rootLines.push(`--radius-lg: ${theme.radii.lg};`);
|
|
100
|
+
if (theme.radii.xl) rootLines.push(`--radius-xl: ${theme.radii.xl};`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (theme.customVars) {
|
|
104
|
+
for (const [k, v] of Object.entries(theme.customVars)) {
|
|
105
|
+
rootLines.push(`--${k}: ${v};`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let css = `:root {\n ${rootLines.join('\n ')}\n}\n`;
|
|
110
|
+
|
|
111
|
+
if (theme.darkColors && Object.keys(theme.darkColors).length > 0) {
|
|
112
|
+
const darkMerged: ThemeColors = {
|
|
113
|
+
...theme.colors,
|
|
114
|
+
...theme.darkColors,
|
|
115
|
+
};
|
|
116
|
+
css += `\n.dark, [data-theme="dark"] {\n ${colorPropsToCss(darkMerged)}\n}\n`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return css;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function applyTheme(
|
|
123
|
+
themeOrName: string | ThemeConfig,
|
|
124
|
+
targetDoc?: Document
|
|
125
|
+
): ThemeConfig {
|
|
126
|
+
const theme = typeof themeOrName === 'string' ? getTheme(themeOrName) : themeOrName;
|
|
127
|
+
const doc = targetDoc ?? (typeof document !== 'undefined' ? document : undefined);
|
|
128
|
+
if (!doc) return theme;
|
|
129
|
+
|
|
130
|
+
const styleId = 'umituz-theme-vars';
|
|
131
|
+
let styleEl = doc.getElementById(styleId) as HTMLStyleElement | null;
|
|
132
|
+
if (!styleEl) {
|
|
133
|
+
styleEl = doc.createElement('style');
|
|
134
|
+
styleEl.id = styleId;
|
|
135
|
+
doc.head.appendChild(styleEl);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
styleEl.textContent = themeToCssVariables(theme);
|
|
139
|
+
|
|
140
|
+
if (theme.mode === 'dark') {
|
|
141
|
+
doc.documentElement.classList.add('dark');
|
|
142
|
+
doc.documentElement.setAttribute('data-theme', 'dark');
|
|
143
|
+
} else if (theme.mode === 'light') {
|
|
144
|
+
doc.documentElement.classList.remove('dark');
|
|
145
|
+
doc.documentElement.setAttribute('data-theme', 'light');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return theme;
|
|
149
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { ThemeConfig, ThemeColors, ThemeTypography, ThemeRadii } from '../../domain/types';
|
|
2
|
+
import { getTheme } from './registry';
|
|
3
|
+
|
|
4
|
+
export interface ThemeOverrides {
|
|
5
|
+
name?: string;
|
|
6
|
+
mode?: 'light' | 'dark';
|
|
7
|
+
colors?: Partial<ThemeColors>;
|
|
8
|
+
darkColors?: Partial<ThemeColors>;
|
|
9
|
+
typography?: Partial<ThemeTypography>;
|
|
10
|
+
radii?: Partial<ThemeRadii>;
|
|
11
|
+
customVars?: Record<string, string>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function createTheme(
|
|
15
|
+
baseOrConfig: string | ThemeConfig,
|
|
16
|
+
overrides?: ThemeOverrides
|
|
17
|
+
): ThemeConfig {
|
|
18
|
+
if (typeof baseOrConfig === 'object' && !overrides) {
|
|
19
|
+
return baseOrConfig;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const base = typeof baseOrConfig === 'string' ? getTheme(baseOrConfig) : baseOrConfig;
|
|
23
|
+
if (!overrides) return base;
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
name: overrides.name ?? base.name,
|
|
27
|
+
mode: overrides.mode ?? base.mode,
|
|
28
|
+
colors: {
|
|
29
|
+
...base.colors,
|
|
30
|
+
...(overrides.colors ?? {}),
|
|
31
|
+
},
|
|
32
|
+
darkColors: {
|
|
33
|
+
...(base.darkColors ?? {}),
|
|
34
|
+
...(overrides.darkColors ?? {}),
|
|
35
|
+
},
|
|
36
|
+
typography: {
|
|
37
|
+
...(base.typography ?? {}),
|
|
38
|
+
...(overrides.typography ?? {}),
|
|
39
|
+
},
|
|
40
|
+
radii: {
|
|
41
|
+
...(base.radii ?? {}),
|
|
42
|
+
...(overrides.radii ?? {}),
|
|
43
|
+
},
|
|
44
|
+
customVars: {
|
|
45
|
+
...(base.customVars ?? {}),
|
|
46
|
+
...(overrides.customVars ?? {}),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ThemeConfig } from '../../domain/types';
|
|
2
|
+
|
|
3
|
+
export const defaultTheme: ThemeConfig = {
|
|
4
|
+
name: 'default',
|
|
5
|
+
mode: 'light',
|
|
6
|
+
colors: {
|
|
7
|
+
background: '#f8fafc',
|
|
8
|
+
foreground: '#0f172a',
|
|
9
|
+
card: '#ffffff',
|
|
10
|
+
cardForeground: '#0f172a',
|
|
11
|
+
popover: '#ffffff',
|
|
12
|
+
popoverForeground: '#0f172a',
|
|
13
|
+
primary: '#2563eb',
|
|
14
|
+
primaryForeground: '#ffffff',
|
|
15
|
+
primarySoft: 'rgba(37, 99, 235, 0.1)',
|
|
16
|
+
secondary: '#8b5cf6',
|
|
17
|
+
secondaryForeground: '#ffffff',
|
|
18
|
+
muted: '#f1f5f9',
|
|
19
|
+
mutedForeground: '#64748b',
|
|
20
|
+
accent: '#f1f5f9',
|
|
21
|
+
accentForeground: '#0f172a',
|
|
22
|
+
border: '#e2e8f0',
|
|
23
|
+
input: '#e2e8f0',
|
|
24
|
+
destructive: '#dc2626',
|
|
25
|
+
destructiveForeground: '#ffffff',
|
|
26
|
+
ring: 'rgba(37, 99, 235, 0.45)',
|
|
27
|
+
success: '#16a34a',
|
|
28
|
+
successForeground: '#ffffff',
|
|
29
|
+
warning: '#d97706',
|
|
30
|
+
warningForeground: '#ffffff',
|
|
31
|
+
info: '#2563eb',
|
|
32
|
+
infoForeground: '#ffffff',
|
|
33
|
+
violet: '#7c3aed',
|
|
34
|
+
violetForeground: '#ffffff',
|
|
35
|
+
},
|
|
36
|
+
darkColors: {
|
|
37
|
+
background: '#0b1120',
|
|
38
|
+
foreground: '#f1f5f9',
|
|
39
|
+
card: '#111827',
|
|
40
|
+
cardForeground: '#f1f5f9',
|
|
41
|
+
popover: '#111827',
|
|
42
|
+
popoverForeground: '#f1f5f9',
|
|
43
|
+
primary: '#3b82f6',
|
|
44
|
+
primaryForeground: '#ffffff',
|
|
45
|
+
secondary: '#a78bfa',
|
|
46
|
+
secondaryForeground: '#ffffff',
|
|
47
|
+
muted: '#1e293b',
|
|
48
|
+
mutedForeground: '#94a3b8',
|
|
49
|
+
accent: '#1e293b',
|
|
50
|
+
accentForeground: '#f1f5f9',
|
|
51
|
+
border: '#1f2a3c',
|
|
52
|
+
input: '#1f2a3c',
|
|
53
|
+
destructive: '#ef4444',
|
|
54
|
+
destructiveForeground: '#ffffff',
|
|
55
|
+
ring: 'rgba(59, 130, 246, 0.5)',
|
|
56
|
+
success: '#22c55e',
|
|
57
|
+
successForeground: '#ffffff',
|
|
58
|
+
warning: '#f59e0b',
|
|
59
|
+
warningForeground: '#ffffff',
|
|
60
|
+
info: '#3b82f6',
|
|
61
|
+
infoForeground: '#ffffff',
|
|
62
|
+
},
|
|
63
|
+
typography: {
|
|
64
|
+
fontSans: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
65
|
+
fontMono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
|
66
|
+
},
|
|
67
|
+
radii: {
|
|
68
|
+
sm: '6px',
|
|
69
|
+
md: '8px',
|
|
70
|
+
lg: '12px',
|
|
71
|
+
xl: '16px',
|
|
72
|
+
},
|
|
73
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ThemeConfig } from '../../domain/types';
|
|
2
|
+
|
|
3
|
+
export const editorialTheme: ThemeConfig = {
|
|
4
|
+
name: 'editorial',
|
|
5
|
+
mode: 'light',
|
|
6
|
+
colors: {
|
|
7
|
+
background: '#f4f1e9',
|
|
8
|
+
foreground: '#23252b',
|
|
9
|
+
card: '#fdfcf8',
|
|
10
|
+
cardForeground: '#23252b',
|
|
11
|
+
popover: '#fdfcf8',
|
|
12
|
+
popoverForeground: '#23252b',
|
|
13
|
+
primary: '#33406b',
|
|
14
|
+
primaryForeground: '#ffffff',
|
|
15
|
+
primarySoft: 'rgba(51, 64, 107, 0.12)',
|
|
16
|
+
secondary: '#efeadc',
|
|
17
|
+
secondaryForeground: '#5a5d66',
|
|
18
|
+
muted: '#efeadc',
|
|
19
|
+
mutedForeground: '#5a5d66',
|
|
20
|
+
accent: '#efeadc',
|
|
21
|
+
accentForeground: '#23252b',
|
|
22
|
+
border: '#e5e1d4',
|
|
23
|
+
input: '#d4cfbf',
|
|
24
|
+
destructive: '#a6483f',
|
|
25
|
+
destructiveForeground: '#ffffff',
|
|
26
|
+
ring: 'rgba(51, 64, 107, 0.4)',
|
|
27
|
+
success: '#3b7a5e',
|
|
28
|
+
successForeground: '#ffffff',
|
|
29
|
+
warning: '#a26b2f',
|
|
30
|
+
warningForeground: '#ffffff',
|
|
31
|
+
info: '#46618f',
|
|
32
|
+
infoForeground: '#ffffff',
|
|
33
|
+
},
|
|
34
|
+
darkColors: {
|
|
35
|
+
background: '#14161f',
|
|
36
|
+
foreground: '#eceadf',
|
|
37
|
+
card: '#1b1e2a',
|
|
38
|
+
cardForeground: '#eceadf',
|
|
39
|
+
popover: '#1b1e2a',
|
|
40
|
+
popoverForeground: '#eceadf',
|
|
41
|
+
primary: '#7f92cc',
|
|
42
|
+
primaryForeground: '#14161f',
|
|
43
|
+
secondary: '#212533',
|
|
44
|
+
secondaryForeground: '#a8abba',
|
|
45
|
+
muted: '#212533',
|
|
46
|
+
mutedForeground: '#a8abba',
|
|
47
|
+
accent: '#212533',
|
|
48
|
+
accentForeground: '#eceadf',
|
|
49
|
+
border: '#2e3344',
|
|
50
|
+
input: '#3d4358',
|
|
51
|
+
destructive: '#c2665c',
|
|
52
|
+
destructiveForeground: '#2a1210',
|
|
53
|
+
ring: 'rgba(143, 160, 214, 0.4)',
|
|
54
|
+
success: '#5ea184',
|
|
55
|
+
successForeground: '#0e1f17',
|
|
56
|
+
warning: '#c98f4a',
|
|
57
|
+
warningForeground: '#221607',
|
|
58
|
+
info: '#7f92cc',
|
|
59
|
+
infoForeground: '#0e1424',
|
|
60
|
+
},
|
|
61
|
+
typography: {
|
|
62
|
+
fontSans: '"Hanken Grotesk", "Noto Sans", -apple-system, "Segoe UI", sans-serif',
|
|
63
|
+
fontDisplay: '"Newsreader", "Iowan Old Style", Georgia, serif',
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { defaultTheme } from './default.theme';
|
|
2
|
+
export { oceanTheme } from './ocean.theme';
|
|
3
|
+
export { midnightTheme } from './midnight.theme';
|
|
4
|
+
export { editorialTheme } from './editorial.theme';
|
|
5
|
+
export { createTheme } from './create-theme';
|
|
6
|
+
export type { ThemeOverrides } from './create-theme';
|
|
7
|
+
export { registerTheme, getTheme, hasTheme, listThemes } from './registry';
|
|
8
|
+
export { themeToCssVariables, applyTheme } from './apply-theme';
|