@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.
@@ -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';
@@ -3,6 +3,7 @@
3
3
  @import "tw-animate-css";
4
4
  @import "./theme.css";
5
5
  @import "./charts.css";
6
+ @import "./console.css";
6
7
  @source "../src";
7
8
 
8
9
  @media (max-width: 767.98px) {