@umituz/web-design-system 6.15.0 → 6.17.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.17.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,68 +4,168 @@ export const oceanTheme: ThemeConfig = {
4
4
  name: 'ocean',
5
5
  mode: 'light',
6
6
  colors: {
7
- background: '#f8fafc',
8
- foreground: '#0f172a',
7
+ background: '#edf1f3',
8
+ foreground: '#0a1e26',
9
9
  card: '#ffffff',
10
- cardForeground: '#0f172a',
10
+ cardForeground: '#0a1e26',
11
11
  popover: '#ffffff',
12
- popoverForeground: '#0f172a',
12
+ popoverForeground: '#0a1e26',
13
13
  primary: '#0284c7',
14
14
  primaryForeground: '#ffffff',
15
15
  primarySoft: 'rgba(2, 132, 199, 0.1)',
16
- secondary: '#0ea5e9',
17
- secondaryForeground: '#ffffff',
18
- muted: '#f0f9ff',
19
- mutedForeground: '#64748b',
20
- accent: '#e0f2fe',
21
- accentForeground: '#0369a1',
22
- border: '#e2e8f0',
23
- input: '#e2e8f0',
24
- destructive: '#ef4444',
16
+ secondary: '#f6f9fa',
17
+ secondaryForeground: '#35494f',
18
+ muted: '#eef4f6',
19
+ mutedForeground: '#5c7077',
20
+ accent: '#eefafc',
21
+ accentForeground: '#076a80',
22
+ border: '#d9e3e7',
23
+ input: '#afc2c9',
24
+ destructive: '#bf3b2b',
25
25
  destructiveForeground: '#ffffff',
26
- ring: 'rgba(2, 132, 199, 0.45)',
27
- success: '#10b981',
26
+ ring: 'rgba(44, 192, 220, 0.35)',
27
+ success: '#0e8a5f',
28
28
  successForeground: '#ffffff',
29
- warning: '#f59e0b',
29
+ warning: '#a05e03',
30
30
  warningForeground: '#ffffff',
31
- info: '#0284c7',
31
+ info: '#1f6fae',
32
32
  infoForeground: '#ffffff',
33
+ violet: '#6d5bd0',
34
+ violetForeground: '#ffffff',
33
35
  },
34
36
  darkColors: {
35
- background: '#08111e',
36
- foreground: '#f0f9ff',
37
- card: '#0c1a2e',
38
- cardForeground: '#f0f9ff',
39
- popover: '#0c1a2e',
40
- popoverForeground: '#f0f9ff',
37
+ background: '#041219',
38
+ foreground: '#e8f4f8',
39
+ card: '#071c26',
40
+ cardForeground: '#e8f4f8',
41
+ popover: '#071c26',
42
+ popoverForeground: '#e8f4f8',
41
43
  primary: '#38bdf8',
42
- primaryForeground: '#08111e',
43
- secondary: '#0284c7',
44
- secondaryForeground: '#ffffff',
45
- muted: '#13233b',
46
- mutedForeground: '#94a3b8',
47
- accent: '#183052',
48
- accentForeground: '#7dd3fc',
49
- border: '#1b3252',
50
- input: '#1b3252',
44
+ primaryForeground: '#041219',
45
+ primarySoft: 'rgba(56, 189, 248, 0.15)',
46
+ secondary: '#092029',
47
+ secondaryForeground: '#9db9c4',
48
+ muted: '#0a2532',
49
+ mutedForeground: '#8298a0',
50
+ accent: 'rgba(11, 165, 196, 0.2)',
51
+ accentForeground: '#7ad7ea',
52
+ border: '#103344',
53
+ input: '#164458',
51
54
  destructive: '#f87171',
52
55
  destructiveForeground: '#ffffff',
53
- ring: 'rgba(56, 189, 248, 0.5)',
56
+ ring: 'rgba(44, 192, 220, 0.5)',
54
57
  success: '#34d399',
55
58
  successForeground: '#ffffff',
56
59
  warning: '#fbbf24',
57
60
  warningForeground: '#ffffff',
58
61
  info: '#38bdf8',
59
62
  infoForeground: '#ffffff',
63
+ violet: '#a78bfa',
64
+ violetForeground: '#ffffff',
60
65
  },
61
66
  typography: {
62
- fontSans: 'ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
63
- fontMono: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
67
+ fontSans: "'Archivo', 'Segoe UI', system-ui, sans-serif",
68
+ fontDisplay: "'Saira Condensed', 'Saira', 'Archivo', sans-serif",
69
+ fontMono: "'IBM Plex Mono', 'SFMono-Regular', Menlo, monospace",
64
70
  },
65
71
  radii: {
72
+ xs: '4px',
66
73
  sm: '6px',
67
74
  md: '8px',
68
- lg: '12px',
69
- xl: '16px',
75
+ lg: '10px',
76
+ xl: '12px',
77
+ },
78
+ customVars: {
79
+ 'font-display': "'Saira Condensed', 'Saira', 'Archivo', sans-serif",
80
+ 'font-head': "'Saira', 'Archivo', sans-serif",
81
+ 'font-body': "'Archivo', 'Segoe UI', sans-serif",
82
+ 'font-mono': "'IBM Plex Mono', 'SFMono-Regular', Menlo, monospace",
83
+ 'ink-950': '#041219',
84
+ 'ink-900': '#071c26',
85
+ 'ink-850': '#092029',
86
+ 'ink-800': '#0a2532',
87
+ 'ink-700': '#103344',
88
+ 'ink-600': '#164458',
89
+ 'ink-line': 'rgba(122, 215, 234, 0.14)',
90
+ bg: '#edf1f3',
91
+ surface: '#ffffff',
92
+ 'surface-2': '#f6f9fa',
93
+ 'surface-3': '#eef4f6',
94
+ line: '#d9e3e7',
95
+ 'line-soft': '#e6edef',
96
+ 'line-strong': '#afc2c9',
97
+ 'text-900': '#0a1e26',
98
+ 'text-700': '#35494f',
99
+ 'text-500': '#5c7077',
100
+ 'text-400': '#8298a0',
101
+ 'text-inv': '#e8f4f8',
102
+ 'text-inv-dim': '#9db9c4',
103
+ 'aqua-700': '#076a80',
104
+ 'aqua-600': '#0888a3',
105
+ 'aqua-500': '#0ba5c4',
106
+ 'aqua-400': '#2cc0dc',
107
+ 'aqua-300': '#7ad7ea',
108
+ 'aqua-100': '#d8f1f7',
109
+ 'aqua-50': '#eefafc',
110
+ 'aqua-glow': 'rgba(44, 192, 220, 0.35)',
111
+ ok: '#0e8a5f',
112
+ 'ok-bg': '#e0f4ea',
113
+ 'ok-line': '#b5e0c8',
114
+ warn: '#a05e03',
115
+ 'warn-bg': '#fbf0dc',
116
+ 'warn-line': '#ecd6a8',
117
+ danger: '#bf3b2b',
118
+ 'danger-bg': '#fbe8e5',
119
+ 'danger-line': '#f0c0b8',
120
+ info: '#1f6fae',
121
+ 'info-bg': '#e3eff9',
122
+ 'info-line': '#bcd6ec',
123
+ hold: '#5b7078',
124
+ 'hold-bg': '#e8eeF0',
125
+ 'hold-line': '#cdd9de',
126
+ violet: '#6d5bd0',
127
+ 'violet-bg': '#ece9fb',
128
+ 'violet-line': '#cdc6f1',
129
+ radius: '8px',
130
+ 'radius-sm': '6px',
131
+ 'radius-xs': '4px',
132
+ 'shadow-1': '0 1px 2px rgba(10, 30, 38, 0.05), 0 1px 1px rgba(10, 30, 38, 0.03)',
133
+ 'shadow-2': '0 4px 14px rgba(10, 30, 38, 0.08), 0 1px 3px rgba(10, 30, 38, 0.05)',
134
+ 'shadow-3': '0 12px 34px rgba(7, 28, 38, 0.16), 0 4px 10px rgba(7, 28, 38, 0.08)',
135
+ 'shadow-pop': '0 16px 44px rgba(7, 28, 38, 0.22)',
136
+ ease: 'cubic-bezier(0.32, 0.72, 0, 1)',
137
+ 'ease-out': 'cubic-bezier(0.16, 1, 0.3, 1)',
138
+ 'dur-1': '140ms',
139
+ 'dur-2': '240ms',
140
+ 'dur-3': '380ms',
141
+ 'sidebar-w': '248px',
142
+ 'topbar-h': '56px',
143
+ },
144
+ darkCustomVars: {
145
+ bg: '#041219',
146
+ surface: '#071c26',
147
+ 'surface-2': '#092029',
148
+ 'surface-3': '#0a2532',
149
+ line: '#103344',
150
+ 'line-soft': '#0d2937',
151
+ 'line-strong': '#164458',
152
+ 'text-900': '#e8f4f8',
153
+ 'text-700': '#9db9c4',
154
+ 'text-500': '#8298a0',
155
+ 'text-400': '#5c7077',
156
+ 'text-inv': '#0a1e26',
157
+ 'text-inv-dim': '#35494f',
158
+ 'ok-bg': 'rgba(14, 138, 95, 0.2)',
159
+ 'ok-line': '#166534',
160
+ 'warn-bg': 'rgba(160, 94, 3, 0.2)',
161
+ 'warn-line': '#854d0e',
162
+ 'danger-bg': 'rgba(191, 59, 43, 0.2)',
163
+ 'danger-line': '#991b1b',
164
+ 'info-bg': 'rgba(31, 111, 174, 0.2)',
165
+ 'info-line': '#1e40af',
166
+ 'hold-bg': 'rgba(91, 112, 120, 0.2)',
167
+ 'hold-line': '#374151',
168
+ 'violet-bg': 'rgba(109, 91, 208, 0.2)',
169
+ 'violet-line': '#5b21b6',
70
170
  },
71
171
  };
@@ -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';