@umituz/web-design-system 6.13.0 → 6.15.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 +2 -1
- package/src/__tests__/entity-row.test.tsx +72 -0
- package/src/__tests__/theme-provider.test.tsx +7 -0
- package/src/index.ts +4 -0
- package/src/infrastructure/themes/forest.theme.ts +145 -0
- package/src/infrastructure/themes/index.ts +1 -0
- package/src/infrastructure/themes/registry.ts +2 -0
- package/src/presentation/molecules/EntityRow.tsx +76 -0
- package/src/presentation/molecules/PlaceholderPanel.tsx +40 -0
- package/src/presentation/molecules/index.ts +6 -0
- package/src/styles/compile.css +1 -0
- package/src/styles/console.css +3029 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import { MemoryRouter } from 'react-router';
|
|
4
|
+
import { EntityRow } from '../presentation/molecules/EntityRow';
|
|
5
|
+
import { PlaceholderPanel } from '../presentation/molecules/PlaceholderPanel';
|
|
6
|
+
|
|
7
|
+
describe('EntityRow', () => {
|
|
8
|
+
it('renders title, icon, meta and aside correctly', () => {
|
|
9
|
+
render(
|
|
10
|
+
<MemoryRouter>
|
|
11
|
+
<ul>
|
|
12
|
+
<EntityRow
|
|
13
|
+
icon={<span data-testid="icon">ICON</span>}
|
|
14
|
+
title="Comprehensive Auto"
|
|
15
|
+
meta={['Renewal date: 2026-12-01', 'Policy #A-9928']}
|
|
16
|
+
badge={<span data-testid="badge">Active</span>}
|
|
17
|
+
aside={<span data-testid="aside">$120/mo</span>}
|
|
18
|
+
/>
|
|
19
|
+
</ul>
|
|
20
|
+
</MemoryRouter>
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
expect(screen.getByText('Comprehensive Auto')).toBeDefined();
|
|
24
|
+
expect(screen.getByTestId('icon')).toBeDefined();
|
|
25
|
+
expect(screen.getByText('Renewal date: 2026-12-01')).toBeDefined();
|
|
26
|
+
expect(screen.getByText('Policy #A-9928')).toBeDefined();
|
|
27
|
+
expect(screen.getByTestId('badge')).toBeDefined();
|
|
28
|
+
expect(screen.getByTestId('aside')).toBeDefined();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('renders as a link when to prop is provided', () => {
|
|
32
|
+
render(
|
|
33
|
+
<MemoryRouter>
|
|
34
|
+
<ul>
|
|
35
|
+
<EntityRow
|
|
36
|
+
icon={<span>ICON</span>}
|
|
37
|
+
title="Homeowners Plus"
|
|
38
|
+
to="/policies/123"
|
|
39
|
+
highlight={true}
|
|
40
|
+
/>
|
|
41
|
+
</ul>
|
|
42
|
+
</MemoryRouter>
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const link = screen.getByRole('link');
|
|
46
|
+
expect(link.getAttribute('href')).toBe('/policies/123');
|
|
47
|
+
expect(link.className).toContain('highlight');
|
|
48
|
+
expect(screen.getByRole('img', { name: 'Unread' })).toBeDefined();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('PlaceholderPanel', () => {
|
|
53
|
+
it('renders step, title, description and capabilities', () => {
|
|
54
|
+
render(
|
|
55
|
+
<PlaceholderPanel
|
|
56
|
+
step="Step 4"
|
|
57
|
+
title="Automated Claims"
|
|
58
|
+
description="Submit photos and get instant estimates."
|
|
59
|
+
capabilities={['Direct damage upload', 'Fast adjuster review']}
|
|
60
|
+
>
|
|
61
|
+
<div data-testid="extra-child">Extra content</div>
|
|
62
|
+
</PlaceholderPanel>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
expect(screen.getByText('Roadmap · Step 4')).toBeDefined();
|
|
66
|
+
expect(screen.getByText('Automated Claims')).toBeDefined();
|
|
67
|
+
expect(screen.getByText('Submit photos and get instant estimates.')).toBeDefined();
|
|
68
|
+
expect(screen.getByText('Direct damage upload')).toBeDefined();
|
|
69
|
+
expect(screen.getByText('Fast adjuster review')).toBeDefined();
|
|
70
|
+
expect(screen.getByTestId('extra-child')).toBeDefined();
|
|
71
|
+
});
|
|
72
|
+
});
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
editorialTheme,
|
|
15
15
|
oceanTheme,
|
|
16
16
|
defaultTheme,
|
|
17
|
+
forestTheme,
|
|
17
18
|
} from '../infrastructure/themes';
|
|
18
19
|
import type { ThemeConfig } from '../domain/types';
|
|
19
20
|
|
|
@@ -30,6 +31,7 @@ describe('Theme System and Provider', () => {
|
|
|
30
31
|
expect(getTheme('midnight').name).toBe('midnight');
|
|
31
32
|
expect(getTheme('editorial').name).toBe('editorial');
|
|
32
33
|
expect(getTheme('ocean').name).toBe('ocean');
|
|
34
|
+
expect(getTheme('forest').name).toBe('forest');
|
|
33
35
|
expect(getTheme('default').name).toBe('default');
|
|
34
36
|
expect(getTheme('non-existent').name).toBe('default');
|
|
35
37
|
});
|
|
@@ -114,6 +116,11 @@ describe('Theme System and Provider', () => {
|
|
|
114
116
|
|
|
115
117
|
const defaultCss = themeToCssVariables(defaultTheme);
|
|
116
118
|
expect(defaultCss).toContain('--color-primary: #2563eb;');
|
|
119
|
+
|
|
120
|
+
const forestCss = themeToCssVariables(forestTheme);
|
|
121
|
+
expect(forestCss).toContain('--color-primary: #14705f;');
|
|
122
|
+
expect(forestCss).toContain('--color-background: #faf9f7;');
|
|
123
|
+
expect(forestCss).toContain('--color-brand-600: #14705f;');
|
|
117
124
|
});
|
|
118
125
|
|
|
119
126
|
it('applies theme variables to document head', () => {
|
package/src/index.ts
CHANGED
|
@@ -147,4 +147,8 @@ export { MarkdownText } from './presentation/molecules/MarkdownText';
|
|
|
147
147
|
export type { MarkdownTextProps } from './presentation/molecules/MarkdownText';
|
|
148
148
|
export { FilterSelect } from './presentation/molecules/FilterSelect';
|
|
149
149
|
export type { FilterSelectProps, FilterSelectOption } from './presentation/molecules/FilterSelect';
|
|
150
|
+
export { EntityRow } from './presentation/molecules/EntityRow';
|
|
151
|
+
export type { EntityRowProps } from './presentation/molecules/EntityRow';
|
|
152
|
+
export { PlaceholderPanel } from './presentation/molecules/PlaceholderPanel';
|
|
153
|
+
export type { PlaceholderPanelProps } from './presentation/molecules/PlaceholderPanel';
|
|
150
154
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { ThemeConfig } from '../../domain/types';
|
|
2
|
+
|
|
3
|
+
export const forestTheme: ThemeConfig = {
|
|
4
|
+
name: 'forest',
|
|
5
|
+
mode: 'light',
|
|
6
|
+
colors: {
|
|
7
|
+
background: '#faf9f7',
|
|
8
|
+
foreground: '#101c19',
|
|
9
|
+
card: '#ffffff',
|
|
10
|
+
cardForeground: '#1a2723',
|
|
11
|
+
popover: '#ffffff',
|
|
12
|
+
popoverForeground: '#1a2723',
|
|
13
|
+
primary: '#14705f',
|
|
14
|
+
primaryForeground: '#ffffff',
|
|
15
|
+
primarySoft: '#eef5f2',
|
|
16
|
+
secondary: '#f3f2ee',
|
|
17
|
+
secondaryForeground: '#33413d',
|
|
18
|
+
muted: '#f3f2ee',
|
|
19
|
+
mutedForeground: '#5c6b66',
|
|
20
|
+
accent: '#f4ead4',
|
|
21
|
+
accentForeground: '#9a7328',
|
|
22
|
+
border: '#e7e5df',
|
|
23
|
+
input: '#d5d2ca',
|
|
24
|
+
destructive: '#a32d22',
|
|
25
|
+
destructiveForeground: '#ffffff',
|
|
26
|
+
ring: 'rgba(20, 112, 95, 0.45)',
|
|
27
|
+
success: '#1d6b46',
|
|
28
|
+
successForeground: '#ffffff',
|
|
29
|
+
warning: '#8a5a00',
|
|
30
|
+
warningForeground: '#ffffff',
|
|
31
|
+
info: '#1c5a80',
|
|
32
|
+
infoForeground: '#ffffff',
|
|
33
|
+
},
|
|
34
|
+
darkColors: {
|
|
35
|
+
background: '#0c1916',
|
|
36
|
+
foreground: '#e6ece9',
|
|
37
|
+
card: '#10221e',
|
|
38
|
+
cardForeground: '#e6ece9',
|
|
39
|
+
popover: '#10221e',
|
|
40
|
+
popoverForeground: '#e6ece9',
|
|
41
|
+
primary: '#4f9c86',
|
|
42
|
+
primaryForeground: '#0c1916',
|
|
43
|
+
secondary: '#16332d',
|
|
44
|
+
secondaryForeground: '#a3ada9',
|
|
45
|
+
muted: '#16332d',
|
|
46
|
+
mutedForeground: '#a3ada9',
|
|
47
|
+
accent: '#16332d',
|
|
48
|
+
accentForeground: '#e6ece9',
|
|
49
|
+
border: '#1c423a',
|
|
50
|
+
input: '#245248',
|
|
51
|
+
destructive: '#c2665c',
|
|
52
|
+
destructiveForeground: '#2a1210',
|
|
53
|
+
ring: 'rgba(79, 156, 134, 0.45)',
|
|
54
|
+
success: '#5ea184',
|
|
55
|
+
successForeground: '#0e1f17',
|
|
56
|
+
warning: '#c98f4a',
|
|
57
|
+
warningForeground: '#221607',
|
|
58
|
+
info: '#7f92cc',
|
|
59
|
+
infoForeground: '#0e1424',
|
|
60
|
+
},
|
|
61
|
+
typography: {
|
|
62
|
+
fontSans: "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif",
|
|
63
|
+
fontDisplay: "'Fraunces', Georgia, 'Times New Roman', serif",
|
|
64
|
+
},
|
|
65
|
+
radii: {
|
|
66
|
+
sm: '6px',
|
|
67
|
+
md: '10px',
|
|
68
|
+
lg: '16px',
|
|
69
|
+
xl: '999px',
|
|
70
|
+
},
|
|
71
|
+
customVars: {
|
|
72
|
+
'color-brand-50': '#eef5f2',
|
|
73
|
+
'color-brand-100': '#d9eae3',
|
|
74
|
+
'color-brand-200': '#b3d5c8',
|
|
75
|
+
'color-brand-400': '#4f9c86',
|
|
76
|
+
'color-brand-500': '#2c8268',
|
|
77
|
+
'color-brand-600': '#14705f',
|
|
78
|
+
'color-brand-700': '#11574b',
|
|
79
|
+
'color-brand-800': '#0f463d',
|
|
80
|
+
'color-brand-900': '#0c3b33',
|
|
81
|
+
'color-ink-950': '#101c19',
|
|
82
|
+
'color-ink-900': '#1a2723',
|
|
83
|
+
'color-ink-700': '#33413d',
|
|
84
|
+
'color-ink-500': '#5c6b66',
|
|
85
|
+
'color-ink-400': '#7d8a85',
|
|
86
|
+
'color-ink-300': '#a3ada9',
|
|
87
|
+
'color-paper': '#faf9f7',
|
|
88
|
+
'color-surface': '#ffffff',
|
|
89
|
+
'color-surface-subtle': '#f3f2ee',
|
|
90
|
+
'color-line': '#e7e5df',
|
|
91
|
+
'color-line-strong': '#d5d2ca',
|
|
92
|
+
'color-inverse': '#0f2e28',
|
|
93
|
+
'color-scrim': 'rgba(16, 28, 25, 0.4)',
|
|
94
|
+
'color-hairline-inverse': 'rgba(238, 245, 242, 0.14)',
|
|
95
|
+
'color-accent-600': '#9a7328',
|
|
96
|
+
'color-accent-100': '#f4ead4',
|
|
97
|
+
'color-success-700': '#1d6b46',
|
|
98
|
+
'color-success-100': '#e0f0e6',
|
|
99
|
+
'color-warning-700': '#8a5a00',
|
|
100
|
+
'color-warning-100': '#f9eed3',
|
|
101
|
+
'color-danger-700': '#a32d22',
|
|
102
|
+
'color-danger-100': '#f9e3df',
|
|
103
|
+
'color-info-700': '#1c5a80',
|
|
104
|
+
'color-info-100': '#e1eef6',
|
|
105
|
+
'space-1': '0.25rem',
|
|
106
|
+
'space-2': '0.5rem',
|
|
107
|
+
'space-3': '0.75rem',
|
|
108
|
+
'space-4': '1rem',
|
|
109
|
+
'space-5': '1.5rem',
|
|
110
|
+
'space-6': '2rem',
|
|
111
|
+
'space-8': '3rem',
|
|
112
|
+
'space-10': '4rem',
|
|
113
|
+
'space-12': '5rem',
|
|
114
|
+
'space-16': '7rem',
|
|
115
|
+
'radius-pill': '999px',
|
|
116
|
+
'shadow-sm': '0 1px 2px rgba(16, 28, 25, 0.05)',
|
|
117
|
+
'shadow-md': '0 10px 30px rgba(16, 28, 25, 0.08)',
|
|
118
|
+
'container-max': '72rem',
|
|
119
|
+
'container-narrow': '48rem',
|
|
120
|
+
'header-height': '4rem',
|
|
121
|
+
'duration-fast': '140ms',
|
|
122
|
+
'duration-slow': '240ms',
|
|
123
|
+
'ease-out': 'cubic-bezier(0.2, 0.7, 0.3, 1)',
|
|
124
|
+
'focus-ring': '0 0 0 2px #faf9f7, 0 0 0 4px #2c8268',
|
|
125
|
+
'text-xs': '0.75rem',
|
|
126
|
+
'text-sm': '0.875rem',
|
|
127
|
+
'text-base': '1rem',
|
|
128
|
+
'text-lg': '1.125rem',
|
|
129
|
+
'text-xl': '1.375rem',
|
|
130
|
+
'text-2xl': '1.75rem',
|
|
131
|
+
'text-3xl': '2.125rem',
|
|
132
|
+
'text-4xl': '2.75rem',
|
|
133
|
+
'text-hero': 'clamp(2.625rem, 6vw, 4.25rem)',
|
|
134
|
+
'leading-tight': '1.15',
|
|
135
|
+
'leading-snug': '1.35',
|
|
136
|
+
'leading-normal': '1.55',
|
|
137
|
+
'leading-relaxed': '1.7',
|
|
138
|
+
'tracking-tight': '-0.02em',
|
|
139
|
+
'tracking-display': '-0.025em',
|
|
140
|
+
'tracking-wide': '0.08em',
|
|
141
|
+
'weight-regular': '400',
|
|
142
|
+
'weight-medium': '500',
|
|
143
|
+
'weight-semibold': '600',
|
|
144
|
+
},
|
|
145
|
+
};
|
|
@@ -2,6 +2,7 @@ export { defaultTheme } from './default.theme';
|
|
|
2
2
|
export { oceanTheme } from './ocean.theme';
|
|
3
3
|
export { midnightTheme } from './midnight.theme';
|
|
4
4
|
export { editorialTheme } from './editorial.theme';
|
|
5
|
+
export { forestTheme } from './forest.theme';
|
|
5
6
|
export { createTheme } from './create-theme';
|
|
6
7
|
export type { ThemeOverrides } from './create-theme';
|
|
7
8
|
export { registerTheme, getTheme, hasTheme, listThemes } from './registry';
|
|
@@ -3,12 +3,14 @@ import { defaultTheme } from './default.theme';
|
|
|
3
3
|
import { oceanTheme } from './ocean.theme';
|
|
4
4
|
import { midnightTheme } from './midnight.theme';
|
|
5
5
|
import { editorialTheme } from './editorial.theme';
|
|
6
|
+
import { forestTheme } from './forest.theme';
|
|
6
7
|
|
|
7
8
|
const themeRegistry = new Map<string, ThemeConfig>([
|
|
8
9
|
['default', defaultTheme],
|
|
9
10
|
['ocean', oceanTheme],
|
|
10
11
|
['midnight', midnightTheme],
|
|
11
12
|
['editorial', editorialTheme],
|
|
13
|
+
['forest', forestTheme],
|
|
12
14
|
]);
|
|
13
15
|
|
|
14
16
|
export function registerTheme(theme: ThemeConfig): void {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { forwardRef, type ReactNode } from 'react';
|
|
2
|
+
import { Link } from 'react-router';
|
|
3
|
+
import { cn } from '../../infrastructure/utils';
|
|
4
|
+
|
|
5
|
+
export interface EntityRowProps {
|
|
6
|
+
icon: ReactNode;
|
|
7
|
+
title: ReactNode;
|
|
8
|
+
meta?: ReactNode[];
|
|
9
|
+
badge?: ReactNode;
|
|
10
|
+
aside?: ReactNode;
|
|
11
|
+
to?: string;
|
|
12
|
+
highlight?: boolean;
|
|
13
|
+
className?: string;
|
|
14
|
+
onClick?: () => void;
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const EntityRow = forwardRef<HTMLLIElement, EntityRowProps>(
|
|
19
|
+
(
|
|
20
|
+
{
|
|
21
|
+
icon,
|
|
22
|
+
title,
|
|
23
|
+
meta,
|
|
24
|
+
badge,
|
|
25
|
+
aside,
|
|
26
|
+
to,
|
|
27
|
+
highlight = false,
|
|
28
|
+
className,
|
|
29
|
+
onClick,
|
|
30
|
+
children,
|
|
31
|
+
},
|
|
32
|
+
ref
|
|
33
|
+
) => {
|
|
34
|
+
const content = (
|
|
35
|
+
<>
|
|
36
|
+
<span className="entity-row-icon" aria-hidden="true">
|
|
37
|
+
{icon}
|
|
38
|
+
</span>
|
|
39
|
+
<span className="entity-row-main">
|
|
40
|
+
<span className={cn('entity-row-title', highlight && 'highlight')}>
|
|
41
|
+
{title}
|
|
42
|
+
{highlight && <span className="entity-row-dot" role="img" aria-label="Unread" />}
|
|
43
|
+
</span>
|
|
44
|
+
{meta?.map((line, index) => (
|
|
45
|
+
<span key={index} className="entity-row-meta">
|
|
46
|
+
{line}
|
|
47
|
+
</span>
|
|
48
|
+
))}
|
|
49
|
+
{children}
|
|
50
|
+
</span>
|
|
51
|
+
{badge && <span className="entity-row-badge">{badge}</span>}
|
|
52
|
+
{aside && <span className="entity-row-aside">{aside}</span>}
|
|
53
|
+
</>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const rowClasses = cn('entity-row', highlight && 'highlight', className);
|
|
57
|
+
|
|
58
|
+
if (to) {
|
|
59
|
+
return (
|
|
60
|
+
<li ref={ref} className="list-none">
|
|
61
|
+
<Link to={to} className={rowClasses} onClick={onClick}>
|
|
62
|
+
{content}
|
|
63
|
+
</Link>
|
|
64
|
+
</li>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<li ref={ref} className={rowClasses} onClick={onClick}>
|
|
70
|
+
{content}
|
|
71
|
+
</li>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
EntityRow.displayName = 'EntityRow';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { Check } from 'lucide-react';
|
|
3
|
+
import { cn } from '../../infrastructure/utils';
|
|
4
|
+
|
|
5
|
+
export interface PlaceholderPanelProps {
|
|
6
|
+
step: string;
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
capabilities: string[];
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
className?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function PlaceholderPanel({
|
|
15
|
+
step,
|
|
16
|
+
title,
|
|
17
|
+
description,
|
|
18
|
+
capabilities,
|
|
19
|
+
children,
|
|
20
|
+
className,
|
|
21
|
+
}: PlaceholderPanelProps) {
|
|
22
|
+
return (
|
|
23
|
+
<section className={cn('placeholder-panel', className)} aria-label={title}>
|
|
24
|
+
<div>
|
|
25
|
+
<span className="placeholder-panel-step">Roadmap · {step}</span>
|
|
26
|
+
<h2 className="placeholder-panel-title">{title}</h2>
|
|
27
|
+
<p className="placeholder-panel-desc">{description}</p>
|
|
28
|
+
</div>
|
|
29
|
+
<ul className="placeholder-panel-caps">
|
|
30
|
+
{capabilities.map((capability) => (
|
|
31
|
+
<li key={capability} className="placeholder-panel-cap">
|
|
32
|
+
<Check size={15} className="placeholder-panel-check" aria-hidden="true" />
|
|
33
|
+
{capability}
|
|
34
|
+
</li>
|
|
35
|
+
))}
|
|
36
|
+
</ul>
|
|
37
|
+
{children}
|
|
38
|
+
</section>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -93,3 +93,9 @@ export type { TabNavProps, TabNavItem } from './TabNav';
|
|
|
93
93
|
|
|
94
94
|
export { FilterSelect } from './FilterSelect';
|
|
95
95
|
export type { FilterSelectProps, FilterSelectOption } from './FilterSelect';
|
|
96
|
+
|
|
97
|
+
export { EntityRow } from './EntityRow';
|
|
98
|
+
export type { EntityRowProps } from './EntityRow';
|
|
99
|
+
|
|
100
|
+
export { PlaceholderPanel } from './PlaceholderPanel';
|
|
101
|
+
export type { PlaceholderPanelProps } from './PlaceholderPanel';
|