@umituz/web-design-system 6.15.0 → 6.16.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/web-design-system",
3
- "version": "6.15.0",
3
+ "version": "6.16.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Web Design System - Tailwind v4 authored, precompiled CSS shipped. Atomic Design components (Atoms, Molecules, Organisms, Templates) for React applications.",
@@ -0,0 +1,31 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { render } from '@testing-library/react';
3
+ import { ConditionPips } from '../presentation/molecules/ConditionPips';
4
+
5
+ describe('ConditionPips', () => {
6
+ it('renders score pips with default max of 5', () => {
7
+ const { container } = render(<ConditionPips score={3} />);
8
+ const root = container.querySelector('.cond-pips');
9
+ expect(root).toBeDefined();
10
+ expect(root?.getAttribute('aria-label')).toBe('3/5');
11
+
12
+ const pips = container.querySelectorAll('.pip');
13
+ expect(pips.length).toBe(5);
14
+
15
+ const onPips = container.querySelectorAll('.pip.on');
16
+ expect(onPips.length).toBe(3);
17
+ });
18
+
19
+ it('handles custom max and zero or null scores', () => {
20
+ const { container: c1 } = render(<ConditionPips score={null} max={4} />);
21
+ const root1 = c1.querySelector('.cond-pips');
22
+ expect(root1?.getAttribute('aria-label')).toBe('0/4');
23
+ expect(c1.querySelectorAll('.pip').length).toBe(4);
24
+ expect(c1.querySelectorAll('.pip.on').length).toBe(0);
25
+
26
+ const { container: c2 } = render(<ConditionPips score={7} max={5} ariaLabel="Excellent (5/5)" />);
27
+ const root2 = c2.querySelector('.cond-pips');
28
+ expect(root2?.getAttribute('aria-label')).toBe('Excellent (5/5)');
29
+ expect(c2.querySelectorAll('.pip.on').length).toBe(5);
30
+ });
31
+ });
@@ -15,6 +15,7 @@ import {
15
15
  oceanTheme,
16
16
  defaultTheme,
17
17
  forestTheme,
18
+ fieldTheme,
18
19
  } from '../infrastructure/themes';
19
20
  import type { ThemeConfig } from '../domain/types';
20
21
 
@@ -32,6 +33,7 @@ describe('Theme System and Provider', () => {
32
33
  expect(getTheme('editorial').name).toBe('editorial');
33
34
  expect(getTheme('ocean').name).toBe('ocean');
34
35
  expect(getTheme('forest').name).toBe('forest');
36
+ expect(getTheme('field').name).toBe('field');
35
37
  expect(getTheme('default').name).toBe('default');
36
38
  expect(getTheme('non-existent').name).toBe('default');
37
39
  });
@@ -171,4 +173,14 @@ describe('Theme System and Provider', () => {
171
173
  expect(screen.getByTestId('theme-mode').textContent).toBe('dark');
172
174
  expect(document.documentElement.classList.contains('dark')).toBe(true);
173
175
  });
176
+
177
+ it('generates field theme CSS custom variables and dark mode overrides', () => {
178
+ const css = themeToCssVariables(fieldTheme);
179
+ expect(css).toContain('--paper: #f4f3ee;');
180
+ expect(css).toContain('--primary-500: #357344;');
181
+ expect(css).toContain('--font-display: \'Fraunces\', Georgia, serif;');
182
+ expect(css).toContain('.dark, [data-theme="dark"]');
183
+ expect(css).toContain('--paper: #10140f;');
184
+ expect(css).toContain('--primary-500: #6aa878;');
185
+ });
174
186
  });
@@ -53,4 +53,5 @@ export interface ThemeConfig {
53
53
  typography?: ThemeTypography;
54
54
  radii?: ThemeRadii;
55
55
  customVars?: Record<string, string>;
56
+ darkCustomVars?: Record<string, string>;
56
57
  }
package/src/index.ts CHANGED
@@ -151,4 +151,6 @@ export { EntityRow } from './presentation/molecules/EntityRow';
151
151
  export type { EntityRowProps } from './presentation/molecules/EntityRow';
152
152
  export { PlaceholderPanel } from './presentation/molecules/PlaceholderPanel';
153
153
  export type { PlaceholderPanelProps } from './presentation/molecules/PlaceholderPanel';
154
+ export { ConditionPips } from './presentation/molecules/ConditionPips';
155
+ export type { ConditionPipsProps } from './presentation/molecules/ConditionPips';
154
156
 
@@ -108,12 +108,24 @@ export function themeToCssVariables(theme: ThemeConfig): string {
108
108
 
109
109
  let css = `:root {\n ${rootLines.join('\n ')}\n}\n`;
110
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`;
111
+ const hasDarkColors = Boolean(theme.darkColors && Object.keys(theme.darkColors).length > 0);
112
+ const hasDarkVars = Boolean(theme.darkCustomVars && Object.keys(theme.darkCustomVars).length > 0);
113
+
114
+ if (hasDarkColors || hasDarkVars) {
115
+ const darkLines: string[] = [];
116
+ if (hasDarkColors) {
117
+ const darkMerged: ThemeColors = {
118
+ ...theme.colors,
119
+ ...theme.darkColors,
120
+ };
121
+ darkLines.push(colorPropsToCss(darkMerged));
122
+ }
123
+ if (hasDarkVars && theme.darkCustomVars) {
124
+ for (const [k, v] of Object.entries(theme.darkCustomVars)) {
125
+ darkLines.push(`--${k}: ${v};`);
126
+ }
127
+ }
128
+ css += `\n.dark, [data-theme="dark"] {\n ${darkLines.join('\n ')}\n}\n`;
117
129
  }
118
130
 
119
131
  return css;
@@ -0,0 +1,230 @@
1
+ import type { ThemeConfig } from '../../domain/types';
2
+
3
+ export const fieldTheme: ThemeConfig = {
4
+ name: 'field',
5
+ mode: 'light',
6
+ colors: {
7
+ background: '#f4f3ee',
8
+ foreground: '#1a231c',
9
+ card: '#ffffff',
10
+ cardForeground: '#212c23',
11
+ popover: '#ffffff',
12
+ popoverForeground: '#212c23',
13
+ primary: '#357344',
14
+ primaryForeground: '#ffffff',
15
+ primarySoft: '#dcead9',
16
+ secondary: '#f1f4ef',
17
+ secondaryForeground: '#2b382d',
18
+ muted: '#edebe3',
19
+ mutedForeground: '#5a655b',
20
+ accent: '#f5e8d3',
21
+ accentForeground: '#c9862f',
22
+ border: '#e2dfd3',
23
+ input: '#cfcaba',
24
+ destructive: '#a53f2a',
25
+ destructiveForeground: '#ffffff',
26
+ ring: 'rgba(53, 115, 68, 0.45)',
27
+ success: '#2f6b40',
28
+ successForeground: '#ffffff',
29
+ warning: '#96690f',
30
+ warningForeground: '#ffffff',
31
+ info: '#3e5f80',
32
+ infoForeground: '#ffffff',
33
+ },
34
+ darkColors: {
35
+ background: '#10140f',
36
+ foreground: '#edf0e7',
37
+ card: '#191f18',
38
+ cardForeground: '#dce1d2',
39
+ popover: '#191f18',
40
+ popoverForeground: '#dce1d2',
41
+ primary: '#6aa878',
42
+ primaryForeground: '#10140f',
43
+ secondary: '#1f261e',
44
+ secondaryForeground: '#c3c9b6',
45
+ muted: '#151a14',
46
+ mutedForeground: '#828b79',
47
+ accent: 'rgba(217, 165, 79, 0.15)',
48
+ accentForeground: '#cf9a48',
49
+ border: '#262e24',
50
+ input: '#333d30',
51
+ destructive: '#d07a63',
52
+ destructiveForeground: '#10140f',
53
+ ring: 'rgba(122, 183, 136, 0.45)',
54
+ success: '#7cb989',
55
+ successForeground: '#10140f',
56
+ warning: '#d3a04e',
57
+ warningForeground: '#10140f',
58
+ info: '#8fb0d4',
59
+ infoForeground: '#10140f',
60
+ },
61
+ typography: {
62
+ fontSans: "'Archivo', 'Segoe UI', system-ui, sans-serif",
63
+ fontDisplay: "'Fraunces', Georgia, serif",
64
+ fontMono: "'IBM Plex Mono', 'SFMono-Regular', Menlo, monospace",
65
+ },
66
+ radii: {
67
+ xs: '2px',
68
+ sm: '4px',
69
+ md: '6px',
70
+ lg: '9px',
71
+ xl: '999px',
72
+ },
73
+ customVars: {
74
+ paper: '#f4f3ee',
75
+ 'paper-deep': '#edebe3',
76
+ surface: '#ffffff',
77
+ 'surface-sunken': '#faf9f5',
78
+ 'surface-tint': '#f1f4ef',
79
+ 'ink-900': '#1a231c',
80
+ 'ink-800': '#212c23',
81
+ 'ink-700': '#2b382d',
82
+ 'ink-600': '#414d42',
83
+ 'ink-500': '#5a655b',
84
+ 'ink-400': '#79827a',
85
+ 'ink-300': '#9aa29a',
86
+ 'ink-200': '#c3c8bd',
87
+ 'ink-100': '#dfe2d8',
88
+ 'chrome-900': '#131f17',
89
+ 'chrome-800': '#16251b',
90
+ 'chrome-700': '#1c2e21',
91
+ 'chrome-600': '#233a2a',
92
+ 'chrome-500': '#2c4734',
93
+ 'chrome-line': '#2a3f30',
94
+ 'chrome-text': '#c5cfc4',
95
+ 'chrome-text-dim': '#7f9484',
96
+ 'chrome-active': '#d8e6d4',
97
+ 'primary-700': '#24502f',
98
+ 'primary-600': '#2c6139',
99
+ 'primary-500': '#357344',
100
+ 'primary-100': '#dcead9',
101
+ 'primary-050': '#edf4ea',
102
+ 'accent-600': '#b06e22',
103
+ 'accent-500': '#c9862f',
104
+ 'accent-100': '#f5e8d3',
105
+ 'status-success': '#2f6b40',
106
+ 'status-success-bg': '#e2efdf',
107
+ 'status-warning': '#96690f',
108
+ 'status-warning-bg': '#f6ecd4',
109
+ 'status-danger': '#a53f2a',
110
+ 'status-danger-bg': '#f7e2db',
111
+ 'status-info': '#3e5f80',
112
+ 'status-info-bg': '#e0eaf2',
113
+ 'status-neutral': '#55605a',
114
+ 'status-neutral-bg': '#e8eae4',
115
+ 'status-critical': '#8f1f14',
116
+ 'danger-600': '#a53f2a',
117
+ 'warning-600': '#96690f',
118
+ line: '#e2dfd3',
119
+ 'line-strong': '#cfcaba',
120
+ 'line-chrome': '#2a3f30',
121
+ 'font-ui': "'Archivo', 'Segoe UI', system-ui, sans-serif",
122
+ 'font-display': "'Fraunces', Georgia, serif",
123
+ 'font-mono': "'IBM Plex Mono', 'SFMono-Regular', Menlo, monospace",
124
+ 'text-xs': '11px',
125
+ 'text-sm': '12.5px',
126
+ 'text-base': '13.5px',
127
+ 'text-md': '15px',
128
+ 'text-lg': '17px',
129
+ 'text-xl': '21px',
130
+ 'text-2xl': '26px',
131
+ 'sp-1': '4px',
132
+ 'sp-2': '8px',
133
+ 'sp-3': '12px',
134
+ 'sp-4': '16px',
135
+ 'sp-5': '20px',
136
+ 'sp-6': '24px',
137
+ 'sp-7': '32px',
138
+ 'sp-8': '40px',
139
+ 'sp-9': '56px',
140
+ 'r-sm': '4px',
141
+ 'r-md': '6px',
142
+ 'r-lg': '9px',
143
+ 'r-pill': '999px',
144
+ 'shadow-1': '0 1px 2px rgba(26, 35, 28, 0.05)',
145
+ 'shadow-2': '0 2px 6px rgba(26, 35, 28, 0.07), 0 1px 2px rgba(26, 35, 28, 0.05)',
146
+ 'shadow-3': '0 12px 32px -8px rgba(26, 35, 28, 0.18), 0 2px 6px rgba(26, 35, 28, 0.06)',
147
+ 'shadow-overlay': '0 24px 64px -16px rgba(19, 31, 23, 0.35)',
148
+ 'sidebar-w': '244px',
149
+ 'sidebar-w-collapsed': '64px',
150
+ 'topbar-h': '54px',
151
+ 'page-max': '1560px',
152
+ 'chart-1': '#357344',
153
+ 'chart-2': '#b06e22',
154
+ 'chart-3': '#3e5f80',
155
+ 'chart-4': '#7a6ba8',
156
+ 'chart-5': '#8a5a3b',
157
+ 'chart-6': '#5e7d5a',
158
+ 'chart-faint': '#e5e9df',
159
+ 'tint-hover': 'rgba(26, 35, 28, 0.06)',
160
+ 'tint-press': 'rgba(26, 35, 28, 0.09)',
161
+ 'topbar-glass': 'rgba(244, 243, 238, 0.86)',
162
+ 'dur-1': '110ms',
163
+ 'dur-2': '180ms',
164
+ 'dur-3': '280ms',
165
+ ease: 'cubic-bezier(0.32, 0.72, 0, 1)',
166
+ },
167
+ darkCustomVars: {
168
+ paper: '#10140f',
169
+ 'paper-deep': '#151a14',
170
+ surface: '#191f18',
171
+ 'surface-sunken': '#141a13',
172
+ 'surface-tint': '#1f261e',
173
+ 'ink-900': '#edf0e7',
174
+ 'ink-800': '#dce1d2',
175
+ 'ink-700': '#c3c9b6',
176
+ 'ink-600': '#a2aa96',
177
+ 'ink-500': '#828b79',
178
+ 'ink-400': '#646d5d',
179
+ 'ink-300': '#4b5346',
180
+ 'ink-200': '#333c30',
181
+ 'ink-100': '#232b21',
182
+ 'chrome-900': '#0c130d',
183
+ 'chrome-800': '#0f1810',
184
+ 'chrome-700': '#132016',
185
+ 'chrome-600': '#182718',
186
+ 'chrome-500': '#1d2f1e',
187
+ 'chrome-line': '#1c2a1d',
188
+ 'chrome-text': '#b9c6b6',
189
+ 'chrome-text-dim': '#71836f',
190
+ 'chrome-active': '#cfe2ca',
191
+ 'primary-700': '#8fc79c',
192
+ 'primary-600': '#79b788',
193
+ 'primary-500': '#6aa878',
194
+ 'primary-100': 'rgba(121, 183, 136, 0.16)',
195
+ 'primary-050': 'rgba(121, 183, 136, 0.09)',
196
+ 'accent-600': '#d9a54f',
197
+ 'accent-500': '#cf9a48',
198
+ 'accent-100': 'rgba(217, 165, 79, 0.15)',
199
+ 'status-success': '#7cb989',
200
+ 'status-success-bg': 'rgba(124, 185, 137, 0.14)',
201
+ 'status-warning': '#d3a04e',
202
+ 'status-warning-bg': 'rgba(211, 160, 78, 0.14)',
203
+ 'status-danger': '#d07a63',
204
+ 'status-danger-bg': 'rgba(208, 122, 99, 0.15)',
205
+ 'status-info': '#8fb0d4',
206
+ 'status-info-bg': 'rgba(143, 176, 212, 0.14)',
207
+ 'status-neutral': '#a3a898',
208
+ 'status-neutral-bg': 'rgba(163, 168, 152, 0.13)',
209
+ 'status-critical': '#e08a73',
210
+ 'danger-600': '#d07a63',
211
+ 'warning-600': '#d3a04e',
212
+ line: '#262e24',
213
+ 'line-strong': '#333d30',
214
+ 'line-chrome': '#1c2a1d',
215
+ 'shadow-1': '0 1px 2px rgba(0, 0, 0, 0.35)',
216
+ 'shadow-2': '0 2px 6px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.3)',
217
+ 'shadow-3': '0 12px 32px -8px rgba(0, 0, 0, 0.55), 0 2px 6px rgba(0, 0, 0, 0.35)',
218
+ 'shadow-overlay': '0 24px 64px -16px rgba(0, 0, 0, 0.7)',
219
+ 'chart-1': '#79b788',
220
+ 'chart-2': '#d9a54f',
221
+ 'chart-3': '#8fb0d4',
222
+ 'chart-4': '#a79bd4',
223
+ 'chart-5': '#c78e6b',
224
+ 'chart-6': '#93b389',
225
+ 'chart-faint': '#232b21',
226
+ 'tint-hover': 'rgba(237, 240, 231, 0.06)',
227
+ 'tint-press': 'rgba(237, 240, 231, 0.09)',
228
+ 'topbar-glass': 'rgba(16, 20, 15, 0.86)',
229
+ },
230
+ };
@@ -3,6 +3,7 @@ export { oceanTheme } from './ocean.theme';
3
3
  export { midnightTheme } from './midnight.theme';
4
4
  export { editorialTheme } from './editorial.theme';
5
5
  export { forestTheme } from './forest.theme';
6
+ export { fieldTheme } from './field.theme';
6
7
  export { createTheme } from './create-theme';
7
8
  export type { ThemeOverrides } from './create-theme';
8
9
  export { registerTheme, getTheme, hasTheme, listThemes } from './registry';
@@ -4,6 +4,7 @@ import { oceanTheme } from './ocean.theme';
4
4
  import { midnightTheme } from './midnight.theme';
5
5
  import { editorialTheme } from './editorial.theme';
6
6
  import { forestTheme } from './forest.theme';
7
+ import { fieldTheme } from './field.theme';
7
8
 
8
9
  const themeRegistry = new Map<string, ThemeConfig>([
9
10
  ['default', defaultTheme],
@@ -11,6 +12,7 @@ const themeRegistry = new Map<string, ThemeConfig>([
11
12
  ['midnight', midnightTheme],
12
13
  ['editorial', editorialTheme],
13
14
  ['forest', forestTheme],
15
+ ['field', fieldTheme],
14
16
  ]);
15
17
 
16
18
  export function registerTheme(theme: ThemeConfig): void {
@@ -0,0 +1,37 @@
1
+ import type { HTMLAttributes } from 'react';
2
+ import { cn } from '../../infrastructure/utils';
3
+
4
+ export interface ConditionPipsProps extends HTMLAttributes<HTMLSpanElement> {
5
+ score: number | null | undefined;
6
+ max?: number;
7
+ className?: string;
8
+ ariaLabel?: string;
9
+ }
10
+
11
+ export function ConditionPips({
12
+ score,
13
+ max = 5,
14
+ className,
15
+ ariaLabel,
16
+ ...props
17
+ }: ConditionPipsProps) {
18
+ const value = Math.max(0, Math.min(score ?? 0, max));
19
+ const label = ariaLabel ?? `${value}/${max}`;
20
+
21
+ return (
22
+ <span
23
+ className={cn('cond-pips', className)}
24
+ role="img"
25
+ aria-label={label}
26
+ {...props}
27
+ >
28
+ {Array.from({ length: max }, (_, i) => (
29
+ <span
30
+ key={i}
31
+ className={cn('pip', i < value && 'on')}
32
+ aria-hidden="true"
33
+ />
34
+ ))}
35
+ </span>
36
+ );
37
+ }
@@ -99,3 +99,6 @@ export type { EntityRowProps } from './EntityRow';
99
99
 
100
100
  export { PlaceholderPanel } from './PlaceholderPanel';
101
101
  export type { PlaceholderPanelProps } from './PlaceholderPanel';
102
+
103
+ export { ConditionPips } from './ConditionPips';
104
+ export type { ConditionPipsProps } from './ConditionPips';