@umituz/web-design-system 6.11.0 → 6.12.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 +118 -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/accessia.theme.ts +79 -0
- package/src/infrastructure/themes/apply-theme.ts +149 -0
- package/src/infrastructure/themes/default.theme.ts +73 -0
- package/src/infrastructure/themes/hangora.theme.ts +65 -0
- package/src/infrastructure/themes/index.ts +5 -0
- package/src/infrastructure/themes/registry.ts +26 -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,118 @@
|
|
|
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
|
+
accessiaTheme,
|
|
13
|
+
} from '../infrastructure/themes';
|
|
14
|
+
import type { ThemeConfig } from '../domain/types';
|
|
15
|
+
|
|
16
|
+
describe('Theme System and Provider', () => {
|
|
17
|
+
beforeEach(() => {
|
|
18
|
+
localStorage.clear();
|
|
19
|
+
document.documentElement.className = '';
|
|
20
|
+
document.documentElement.removeAttribute('data-theme');
|
|
21
|
+
const existing = document.getElementById('umituz-theme-vars');
|
|
22
|
+
if (existing) existing.remove();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('retrieves default and built-in themes by name', () => {
|
|
26
|
+
expect(getTheme('accessia').name).toBe('accessia');
|
|
27
|
+
expect(getTheme('hangora').name).toBe('hangora');
|
|
28
|
+
expect(getTheme('default').name).toBe('default');
|
|
29
|
+
expect(getTheme('non-existent').name).toBe('default');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('registers and retrieves custom themes', () => {
|
|
33
|
+
const custom: ThemeConfig = {
|
|
34
|
+
name: 'custom-corp',
|
|
35
|
+
colors: {
|
|
36
|
+
background: '#111111',
|
|
37
|
+
foreground: '#eeeeee',
|
|
38
|
+
card: '#222222',
|
|
39
|
+
cardForeground: '#eeeeee',
|
|
40
|
+
popover: '#222222',
|
|
41
|
+
popoverForeground: '#eeeeee',
|
|
42
|
+
primary: '#ff0055',
|
|
43
|
+
primaryForeground: '#ffffff',
|
|
44
|
+
secondary: '#333333',
|
|
45
|
+
secondaryForeground: '#ffffff',
|
|
46
|
+
muted: '#333333',
|
|
47
|
+
mutedForeground: '#888888',
|
|
48
|
+
accent: '#444444',
|
|
49
|
+
accentForeground: '#ffffff',
|
|
50
|
+
border: '#555555',
|
|
51
|
+
input: '#555555',
|
|
52
|
+
destructive: '#ff0000',
|
|
53
|
+
destructiveForeground: '#ffffff',
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
registerTheme(custom);
|
|
58
|
+
expect(getTheme('custom-corp').colors.primary).toBe('#ff0055');
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('converts theme config to CSS variables string', () => {
|
|
62
|
+
const css = themeToCssVariables(accessiaTheme);
|
|
63
|
+
expect(css).toContain('--color-primary: #4e8cff;');
|
|
64
|
+
expect(css).toContain('--app-primary: #4e8cff;');
|
|
65
|
+
expect(css).toContain('--color-background: #060a12;');
|
|
66
|
+
expect(css).toContain('--bg-0: #060a12;');
|
|
67
|
+
expect(css).toContain('--font-sans:');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('applies theme variables to document head', () => {
|
|
71
|
+
applyTheme(accessiaTheme);
|
|
72
|
+
const styleEl = document.getElementById('umituz-theme-vars');
|
|
73
|
+
expect(styleEl).toBeTruthy();
|
|
74
|
+
expect(styleEl?.textContent).toContain('--color-primary: #4e8cff;');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('DesignSystemProvider provides theme and allows mode toggle', () => {
|
|
78
|
+
function Consumer() {
|
|
79
|
+
const { theme, mode, toggleMode, setMode } = useDesignSystem();
|
|
80
|
+
return (
|
|
81
|
+
<div>
|
|
82
|
+
<span data-testid="theme-name">{theme.name}</span>
|
|
83
|
+
<span data-testid="theme-mode">{mode}</span>
|
|
84
|
+
<button data-testid="toggle-btn" onClick={toggleMode}>
|
|
85
|
+
Toggle
|
|
86
|
+
</button>
|
|
87
|
+
<button data-testid="set-dark" onClick={() => setMode('dark')}>
|
|
88
|
+
Dark
|
|
89
|
+
</button>
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
render(
|
|
95
|
+
<DesignSystemProvider theme="accessia" mode="dark">
|
|
96
|
+
<Consumer />
|
|
97
|
+
</DesignSystemProvider>
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
expect(screen.getByTestId('theme-name').textContent).toBe('accessia');
|
|
101
|
+
expect(screen.getByTestId('theme-mode').textContent).toBe('dark');
|
|
102
|
+
expect(document.documentElement.classList.contains('dark')).toBe(true);
|
|
103
|
+
|
|
104
|
+
act(() => {
|
|
105
|
+
screen.getByTestId('toggle-btn').click();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(screen.getByTestId('theme-mode').textContent).toBe('light');
|
|
109
|
+
expect(document.documentElement.classList.contains('dark')).toBe(false);
|
|
110
|
+
|
|
111
|
+
act(() => {
|
|
112
|
+
screen.getByTestId('set-dark').click();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
expect(screen.getByTestId('theme-mode').textContent).toBe('dark');
|
|
116
|
+
expect(document.documentElement.classList.contains('dark')).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -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,79 @@
|
|
|
1
|
+
import type { ThemeConfig } from '../../domain/types';
|
|
2
|
+
|
|
3
|
+
export const accessiaTheme: ThemeConfig = {
|
|
4
|
+
name: 'accessia',
|
|
5
|
+
mode: 'dark',
|
|
6
|
+
colors: {
|
|
7
|
+
background: '#060a12',
|
|
8
|
+
foreground: '#e8edf8',
|
|
9
|
+
card: '#0d1422',
|
|
10
|
+
cardForeground: '#e8edf8',
|
|
11
|
+
popover: '#121b2c',
|
|
12
|
+
popoverForeground: '#e8edf8',
|
|
13
|
+
primary: '#4e8cff',
|
|
14
|
+
primaryForeground: '#ffffff',
|
|
15
|
+
primarySoft: 'rgba(78, 140, 255, 0.13)',
|
|
16
|
+
secondary: '#121b2c',
|
|
17
|
+
secondaryForeground: '#e8edf8',
|
|
18
|
+
muted: '#121b2c',
|
|
19
|
+
mutedForeground: '#9aa7c0',
|
|
20
|
+
accent: '#182338',
|
|
21
|
+
accentForeground: '#e8edf8',
|
|
22
|
+
border: '#27334a',
|
|
23
|
+
input: '#27334a',
|
|
24
|
+
destructive: '#f4585f',
|
|
25
|
+
destructiveForeground: '#ffffff',
|
|
26
|
+
ring: 'rgba(78, 140, 255, 0.45)',
|
|
27
|
+
success: '#3ddc97',
|
|
28
|
+
successForeground: '#ffffff',
|
|
29
|
+
warning: '#f5b04a',
|
|
30
|
+
warningForeground: '#060a12',
|
|
31
|
+
info: '#4e8cff',
|
|
32
|
+
infoForeground: '#ffffff',
|
|
33
|
+
},
|
|
34
|
+
typography: {
|
|
35
|
+
fontSans: "'IBM Plex Sans', -apple-system, 'Segoe UI', sans-serif",
|
|
36
|
+
fontMono: "'IBM Plex Mono', ui-monospace, 'SF Mono', Menlo, monospace",
|
|
37
|
+
},
|
|
38
|
+
radii: {
|
|
39
|
+
xs: '3px',
|
|
40
|
+
sm: '5px',
|
|
41
|
+
md: '8px',
|
|
42
|
+
lg: '12px',
|
|
43
|
+
xl: '16px',
|
|
44
|
+
},
|
|
45
|
+
customVars: {
|
|
46
|
+
'bg-0': '#060a12',
|
|
47
|
+
'bg-1': '#090f1a',
|
|
48
|
+
'bg-2': '#0d1422',
|
|
49
|
+
'bg-3': '#121b2c',
|
|
50
|
+
'bg-4': '#182338',
|
|
51
|
+
'bg-inset': '#070c15',
|
|
52
|
+
'line-1': '#1b2536',
|
|
53
|
+
'line-2': '#27334a',
|
|
54
|
+
'line-3': '#35435f',
|
|
55
|
+
'ink-1': '#e8edf8',
|
|
56
|
+
'ink-2': '#9aa7c0',
|
|
57
|
+
'ink-3': '#5e6c88',
|
|
58
|
+
'ink-4': '#3d4a63',
|
|
59
|
+
'blue': '#4e8cff',
|
|
60
|
+
'blue-soft': 'rgba(78, 140, 255, 0.13)',
|
|
61
|
+
'blue-line': 'rgba(78, 140, 255, 0.35)',
|
|
62
|
+
'green': '#3ddc97',
|
|
63
|
+
'green-soft': 'rgba(61, 220, 151, 0.12)',
|
|
64
|
+
'green-line': 'rgba(61, 220, 151, 0.35)',
|
|
65
|
+
'amber': '#f5b04a',
|
|
66
|
+
'amber-soft': 'rgba(245, 176, 74, 0.13)',
|
|
67
|
+
'amber-line': 'rgba(245, 176, 74, 0.35)',
|
|
68
|
+
'red': '#f4585f',
|
|
69
|
+
'red-soft': 'rgba(244, 88, 95, 0.12)',
|
|
70
|
+
'red-line': 'rgba(244, 88, 95, 0.4)',
|
|
71
|
+
'idle': '#7683a0',
|
|
72
|
+
'idle-soft': 'rgba(118, 131, 160, 0.14)',
|
|
73
|
+
'shadow-1': '0 1px 2px rgba(2, 6, 14, 0.5)',
|
|
74
|
+
'shadow-2': '0 8px 28px rgba(2, 6, 14, 0.55)',
|
|
75
|
+
'shadow-3': '0 18px 60px rgba(1, 4, 10, 0.7)',
|
|
76
|
+
'sidebar-w': '250px',
|
|
77
|
+
'topbar-h': '56px',
|
|
78
|
+
},
|
|
79
|
+
};
|
|
@@ -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,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 hangoraTheme: ThemeConfig = {
|
|
4
|
+
name: 'hangora',
|
|
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,5 @@
|
|
|
1
|
+
export { accessiaTheme } from './accessia.theme';
|
|
2
|
+
export { hangoraTheme } from './hangora.theme';
|
|
3
|
+
export { defaultTheme } from './default.theme';
|
|
4
|
+
export { registerTheme, getTheme, hasTheme, listThemes } from './registry';
|
|
5
|
+
export { themeToCssVariables, applyTheme } from './apply-theme';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ThemeConfig } from '../../domain/types';
|
|
2
|
+
import { accessiaTheme } from './accessia.theme';
|
|
3
|
+
import { hangoraTheme } from './hangora.theme';
|
|
4
|
+
import { defaultTheme } from './default.theme';
|
|
5
|
+
|
|
6
|
+
const themeRegistry = new Map<string, ThemeConfig>([
|
|
7
|
+
['default', defaultTheme],
|
|
8
|
+
['accessia', accessiaTheme],
|
|
9
|
+
['hangora', hangoraTheme],
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
export function registerTheme(theme: ThemeConfig): void {
|
|
13
|
+
themeRegistry.set(theme.name, theme);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getTheme(name: string): ThemeConfig {
|
|
17
|
+
return themeRegistry.get(name) ?? defaultTheme;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function hasTheme(name: string): boolean {
|
|
21
|
+
return themeRegistry.has(name);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function listThemes(): string[] {
|
|
25
|
+
return Array.from(themeRegistry.keys());
|
|
26
|
+
}
|