@happyvertical/smrt-ui 0.30.0 → 0.31.1
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/actions/__tests__/ripple.test.js +55 -1
- package/dist/actions/ripple.d.ts +9 -1
- package/dist/actions/ripple.d.ts.map +1 -1
- package/dist/actions/ripple.js +56 -7
- package/dist/components/display/ConfidenceBadge.svelte +14 -10
- package/dist/components/display/ConfidenceBadge.svelte.d.ts.map +1 -1
- package/dist/components/display/StatusBadge.svelte +81 -75
- package/dist/components/display/StatusBadge.svelte.d.ts.map +1 -1
- package/dist/components/display/__tests__/ConfidenceBadge.test.js +13 -0
- package/dist/components/display/__tests__/StatusBadge.test.js +17 -2
- package/dist/components/feedback/ConfirmDialog.svelte +78 -2
- package/dist/components/feedback/ConfirmDialog.svelte.d.ts.map +1 -1
- package/dist/components/feedback/LoadingOverlay.svelte +6 -1
- package/dist/components/feedback/LoadingOverlay.svelte.d.ts.map +1 -1
- package/dist/components/feedback/__tests__/ConfirmDialog.test.js +55 -3
- package/dist/components/feedback/__tests__/LoadingOverlay.test.js +14 -0
- package/dist/components/roles/RoleSelector.svelte +106 -13
- package/dist/components/roles/RoleSelector.svelte.d.ts +10 -0
- package/dist/components/roles/RoleSelector.svelte.d.ts.map +1 -1
- package/dist/components/roles/__tests__/RoleSelector.test.js +134 -0
- package/dist/components/ui/Button.svelte +12 -2
- package/dist/components/ui/Pagination.svelte +3 -1
- package/dist/components/ui/Pagination.svelte.d.ts.map +1 -1
- package/dist/components/ui/__tests__/Pagination.test.js +14 -0
- package/dist/themes/__tests__/create-theme.test.js +79 -0
- package/dist/themes/__tests__/css-generator.test.js +55 -0
- package/dist/themes/create-theme.d.ts.map +1 -1
- package/dist/themes/create-theme.js +29 -22
- package/dist/themes/css-generator.d.ts.map +1 -1
- package/dist/themes/css-generator.js +4 -1
- package/dist/themes/studio/index.d.ts.map +1 -1
- package/dist/themes/studio/index.js +19 -1
- package/dist/themes/types.d.ts +8 -1
- package/dist/themes/types.d.ts.map +1 -1
- package/dist/utils/theme/__tests__/color.test.js +17 -0
- package/dist/utils/theme/color.d.ts +11 -0
- package/dist/utils/theme/color.d.ts.map +1 -1
- package/dist/utils/theme/color.js +39 -4
- package/package.json +2 -2
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unit test for the ripple action (coverage uplift, S6 gate).
|
|
3
3
|
*/
|
|
4
|
-
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
import { ripple } from '../ripple';
|
|
6
|
+
afterEach(() => {
|
|
7
|
+
vi.restoreAllMocks();
|
|
8
|
+
// Drop any matchMedia stub a test installed.
|
|
9
|
+
Reflect.deleteProperty(window, 'matchMedia');
|
|
10
|
+
});
|
|
6
11
|
describe('ripple action', () => {
|
|
7
12
|
it('appends a ripple element on pointer down', () => {
|
|
8
13
|
const node = document.createElement('button');
|
|
@@ -25,4 +30,53 @@ describe('ripple action', () => {
|
|
|
25
30
|
action?.destroy?.();
|
|
26
31
|
node.remove();
|
|
27
32
|
});
|
|
33
|
+
it('fills the ripple with the canonical --smrt-color-primary token (#1586)', () => {
|
|
34
|
+
// Regression: the canonical theme system only emits `--smrt-color-*`; the
|
|
35
|
+
// old `--md-sys-color-primary` namespace always fell back to currentColor.
|
|
36
|
+
const node = document.createElement('button');
|
|
37
|
+
document.body.appendChild(node);
|
|
38
|
+
const action = ripple(node);
|
|
39
|
+
node.dispatchEvent(new MouseEvent('mousedown', { clientX: 5, clientY: 5, bubbles: true }));
|
|
40
|
+
const span = node.querySelector('span');
|
|
41
|
+
expect(span.style.backgroundColor).toContain('--smrt-color-primary');
|
|
42
|
+
expect(span.style.backgroundColor).not.toContain('--md-sys');
|
|
43
|
+
action?.destroy?.();
|
|
44
|
+
node.remove();
|
|
45
|
+
});
|
|
46
|
+
it('destroy() removes pending ripple spans and clears window listeners (#1586)', () => {
|
|
47
|
+
const node = document.createElement('button');
|
|
48
|
+
document.body.appendChild(node);
|
|
49
|
+
const action = ripple(node);
|
|
50
|
+
// Press without releasing: the ripple span stays appended, and a one-shot
|
|
51
|
+
// window mouseup listener is still registered.
|
|
52
|
+
node.dispatchEvent(new MouseEvent('mousedown', { clientX: 5, clientY: 5, bubbles: true }));
|
|
53
|
+
expect(node.querySelectorAll('span.smrt-ripple').length).toBe(1);
|
|
54
|
+
const removeSpy = vi.spyOn(window, 'removeEventListener');
|
|
55
|
+
action?.destroy?.();
|
|
56
|
+
// The leaked span is gone immediately (no waiting for a later global mouseup).
|
|
57
|
+
expect(node.querySelectorAll('span.smrt-ripple').length).toBe(0);
|
|
58
|
+
// And the pending window pointer-up listeners were detached.
|
|
59
|
+
expect(removeSpy).toHaveBeenCalledWith('mouseup', expect.any(Function));
|
|
60
|
+
expect(removeSpy).toHaveBeenCalledWith('touchend', expect.any(Function));
|
|
61
|
+
node.remove();
|
|
62
|
+
});
|
|
63
|
+
it('skips the ripple when prefers-reduced-motion is set (#1586)', () => {
|
|
64
|
+
window.matchMedia = vi.fn().mockImplementation((query) => ({
|
|
65
|
+
matches: query.includes('prefers-reduced-motion'),
|
|
66
|
+
media: query,
|
|
67
|
+
onchange: null,
|
|
68
|
+
addEventListener: vi.fn(),
|
|
69
|
+
removeEventListener: vi.fn(),
|
|
70
|
+
addListener: vi.fn(),
|
|
71
|
+
removeListener: vi.fn(),
|
|
72
|
+
dispatchEvent: vi.fn(),
|
|
73
|
+
}));
|
|
74
|
+
const node = document.createElement('button');
|
|
75
|
+
document.body.appendChild(node);
|
|
76
|
+
const action = ripple(node);
|
|
77
|
+
node.dispatchEvent(new MouseEvent('mousedown', { clientX: 5, clientY: 5, bubbles: true }));
|
|
78
|
+
expect(node.querySelectorAll('span.smrt-ripple').length).toBe(0);
|
|
79
|
+
action?.destroy?.();
|
|
80
|
+
node.remove();
|
|
81
|
+
});
|
|
28
82
|
});
|
package/dist/actions/ripple.d.ts
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import type { Action } from 'svelte/action';
|
|
2
2
|
/**
|
|
3
3
|
* Svelte action to add a Material 3 ripple effect to an element.
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* Uses the canonical `--smrt-color-primary` token (the SMRT theme system only
|
|
6
|
+
* ever emits `--smrt-color-*`; the legacy `--md-sys-*` namespace is not produced
|
|
7
|
+
* by the canonical providers), falling back to `currentColor`.
|
|
8
|
+
*
|
|
9
|
+
* Respects `prefers-reduced-motion`: when the user requests reduced motion the
|
|
10
|
+
* ripple is skipped entirely. On `destroy()` all pending ripple spans, their
|
|
11
|
+
* removal timeouts, and the window pointer-up listeners are cleaned up so an
|
|
12
|
+
* unmount mid-press cannot leak DOM nodes or listeners.
|
|
5
13
|
*/
|
|
6
14
|
export declare const ripple: Action<HTMLElement>;
|
|
7
15
|
//# sourceMappingURL=ripple.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ripple.d.ts","sourceRoot":"","sources":["../../src/actions/ripple.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C
|
|
1
|
+
{"version":3,"file":"ripple.d.ts","sourceRoot":"","sources":["../../src/actions/ripple.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,WAAW,CA2HtC,CAAC"}
|
package/dist/actions/ripple.js
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Svelte action to add a Material 3 ripple effect to an element.
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Uses the canonical `--smrt-color-primary` token (the SMRT theme system only
|
|
5
|
+
* ever emits `--smrt-color-*`; the legacy `--md-sys-*` namespace is not produced
|
|
6
|
+
* by the canonical providers), falling back to `currentColor`.
|
|
7
|
+
*
|
|
8
|
+
* Respects `prefers-reduced-motion`: when the user requests reduced motion the
|
|
9
|
+
* ripple is skipped entirely. On `destroy()` all pending ripple spans, their
|
|
10
|
+
* removal timeouts, and the window pointer-up listeners are cleaned up so an
|
|
11
|
+
* unmount mid-press cannot leak DOM nodes or listeners.
|
|
4
12
|
*/
|
|
5
13
|
export const ripple = (node) => {
|
|
14
|
+
const prefersReducedMotion = () => typeof window !== 'undefined' &&
|
|
15
|
+
typeof window.matchMedia === 'function' &&
|
|
16
|
+
window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
17
|
+
// Track in-flight ripples so destroy() can tear them down deterministically.
|
|
18
|
+
const activeSpans = new Set();
|
|
19
|
+
const pendingTimeouts = new Set();
|
|
20
|
+
const pendingPointerUp = new Set();
|
|
6
21
|
const handleStart = (e) => {
|
|
22
|
+
// Honor reduced-motion: no ripple animation at all.
|
|
23
|
+
if (prefersReducedMotion())
|
|
24
|
+
return;
|
|
7
25
|
const rect = node.getBoundingClientRect();
|
|
8
26
|
const clientX = 'touches' in e
|
|
9
27
|
? e.touches[0].clientX
|
|
@@ -23,32 +41,45 @@ export const ripple = (node) => {
|
|
|
23
41
|
rippleEl.style.width = rippleEl.style.height = `${diameter}px`;
|
|
24
42
|
rippleEl.style.left = `${x - radius}px`;
|
|
25
43
|
rippleEl.style.top = `${y - radius}px`;
|
|
26
|
-
rippleEl.style.backgroundColor =
|
|
27
|
-
'var(--md-sys-color-primary, currentColor)';
|
|
44
|
+
rippleEl.style.backgroundColor = 'var(--smrt-color-primary, currentColor)';
|
|
28
45
|
rippleEl.style.opacity = '0.12';
|
|
29
46
|
rippleEl.style.transform = 'scale(0)';
|
|
30
47
|
rippleEl.style.transition =
|
|
31
48
|
'transform 600ms cubic-bezier(0.4, 0, 0.2, 1), opacity 600ms linear';
|
|
32
49
|
rippleEl.classList.add('smrt-ripple');
|
|
33
50
|
node.appendChild(rippleEl);
|
|
51
|
+
activeSpans.add(rippleEl);
|
|
34
52
|
// Trigger animation
|
|
35
53
|
requestAnimationFrame(() => {
|
|
36
54
|
rippleEl.style.transform = 'scale(1)';
|
|
37
55
|
});
|
|
38
|
-
|
|
56
|
+
// Bound to this ripple so the pointer-up handler and its timeout can be
|
|
57
|
+
// forgotten once they run (or torn down early in destroy()).
|
|
58
|
+
let removeRipple;
|
|
59
|
+
const detachPointerUp = () => {
|
|
60
|
+
window.removeEventListener('mouseup', removeRipple);
|
|
61
|
+
window.removeEventListener('touchend', removeRipple);
|
|
62
|
+
pendingPointerUp.delete(removeRipple);
|
|
63
|
+
};
|
|
64
|
+
removeRipple = () => {
|
|
65
|
+
detachPointerUp();
|
|
39
66
|
rippleEl.style.opacity = '0';
|
|
40
|
-
setTimeout(() => {
|
|
67
|
+
const timeout = setTimeout(() => {
|
|
68
|
+
pendingTimeouts.delete(timeout);
|
|
69
|
+
activeSpans.delete(rippleEl);
|
|
41
70
|
if (rippleEl.parentNode === node) {
|
|
42
71
|
node.removeChild(rippleEl);
|
|
43
72
|
}
|
|
44
73
|
}, 600);
|
|
74
|
+
pendingTimeouts.add(timeout);
|
|
45
75
|
};
|
|
76
|
+
pendingPointerUp.add(removeRipple);
|
|
46
77
|
window.addEventListener('mouseup', removeRipple, { once: true });
|
|
47
78
|
window.addEventListener('touchend', removeRipple, { once: true });
|
|
48
79
|
};
|
|
49
80
|
node.addEventListener('mousedown', handleStart);
|
|
50
81
|
node.addEventListener('touchstart', handleStart, { passive: true });
|
|
51
|
-
// Ensure node is prepared
|
|
82
|
+
// Ensure node is prepared (and remember prior values so destroy() can restore).
|
|
52
83
|
const originalPosition = node.style.position;
|
|
53
84
|
const originalOverflow = node.style.overflow;
|
|
54
85
|
if (!node.style.position || node.style.position === 'static') {
|
|
@@ -59,7 +90,25 @@ export const ripple = (node) => {
|
|
|
59
90
|
destroy() {
|
|
60
91
|
node.removeEventListener('mousedown', handleStart);
|
|
61
92
|
node.removeEventListener('touchstart', handleStart);
|
|
62
|
-
//
|
|
93
|
+
// Cancel pending removal timeouts.
|
|
94
|
+
for (const timeout of pendingTimeouts)
|
|
95
|
+
clearTimeout(timeout);
|
|
96
|
+
pendingTimeouts.clear();
|
|
97
|
+
// Detach any window pointer-up listeners still waiting for a release.
|
|
98
|
+
for (const handler of pendingPointerUp) {
|
|
99
|
+
window.removeEventListener('mouseup', handler);
|
|
100
|
+
window.removeEventListener('touchend', handler);
|
|
101
|
+
}
|
|
102
|
+
pendingPointerUp.clear();
|
|
103
|
+
// Remove any ripple spans still appended to the node.
|
|
104
|
+
for (const span of activeSpans) {
|
|
105
|
+
if (span.parentNode === node)
|
|
106
|
+
node.removeChild(span);
|
|
107
|
+
}
|
|
108
|
+
activeSpans.clear();
|
|
109
|
+
// Restore the node's original inline layout styles.
|
|
110
|
+
node.style.position = originalPosition;
|
|
111
|
+
node.style.overflow = originalOverflow;
|
|
63
112
|
},
|
|
64
113
|
};
|
|
65
114
|
};
|
|
@@ -40,26 +40,30 @@ const level = $derived.by(() => {
|
|
|
40
40
|
return 'low';
|
|
41
41
|
});
|
|
42
42
|
|
|
43
|
-
// Color scheme based on level
|
|
43
|
+
// Color scheme based on level, referencing the canonical `--smrt-color-*`
|
|
44
|
+
// tokens directly. Literal hex fallbacks are intentionally omitted: smrt-ui owns
|
|
45
|
+
// the theme system so the tokens are always defined, and the old hardcoded
|
|
46
|
+
// fallbacks were the wrong colors (e.g. high → green #dcfce7, real material
|
|
47
|
+
// primary-container light is the blue #d3e3fd) — see #1586.
|
|
44
48
|
const colors = $derived.by(() => {
|
|
45
49
|
switch (level) {
|
|
46
50
|
case 'high':
|
|
47
51
|
return {
|
|
48
|
-
bg: 'var(--smrt-color-primary-container
|
|
49
|
-
text: 'var(--smrt-color-on-primary-container
|
|
50
|
-
bar: 'var(--smrt-color-primary
|
|
52
|
+
bg: 'var(--smrt-color-primary-container)',
|
|
53
|
+
text: 'var(--smrt-color-on-primary-container)',
|
|
54
|
+
bar: 'var(--smrt-color-primary)',
|
|
51
55
|
};
|
|
52
56
|
case 'medium':
|
|
53
57
|
return {
|
|
54
|
-
bg: 'var(--smrt-color-secondary-container
|
|
55
|
-
text: 'var(--smrt-color-on-secondary-container
|
|
56
|
-
bar: 'var(--smrt-color-secondary
|
|
58
|
+
bg: 'var(--smrt-color-secondary-container)',
|
|
59
|
+
text: 'var(--smrt-color-on-secondary-container)',
|
|
60
|
+
bar: 'var(--smrt-color-secondary)',
|
|
57
61
|
};
|
|
58
62
|
case 'low':
|
|
59
63
|
return {
|
|
60
|
-
bg: 'var(--smrt-color-error-container
|
|
61
|
-
text: 'var(--smrt-color-on-error-container
|
|
62
|
-
bar: 'var(--smrt-color-error
|
|
64
|
+
bg: 'var(--smrt-color-error-container)',
|
|
65
|
+
text: 'var(--smrt-color-on-error-container)',
|
|
66
|
+
bar: 'var(--smrt-color-error)',
|
|
63
67
|
};
|
|
64
68
|
}
|
|
65
69
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConfidenceBadge.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/display/ConfidenceBadge.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH,0CAA0C;AAC1C,MAAM,WAAW,KAAK;IACpB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"ConfidenceBadge.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/display/ConfidenceBadge.svelte.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH,0CAA0C;AAC1C,MAAM,WAAW,KAAK;IACpB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,mBAAmB;IACnB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAyED,QAAA,MAAM,eAAe,2CAAwC,CAAC;AAC9D,KAAK,eAAe,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC;AAC1D,eAAe,eAAe,CAAC"}
|
|
@@ -30,167 +30,173 @@ const {
|
|
|
30
30
|
label,
|
|
31
31
|
}: Props = $props();
|
|
32
32
|
|
|
33
|
-
// Color mappings for different status domains
|
|
33
|
+
// Color mappings for different status domains, referencing the canonical
|
|
34
|
+
// `--smrt-color-*` tokens directly. smrt-ui owns the theme system, so every
|
|
35
|
+
// preset (material/glass/studio) always defines these tokens. Literal hex
|
|
36
|
+
// fallbacks are intentionally omitted: a fallback only belongs here if it equals
|
|
37
|
+
// the token's real light value, and the previous hardcoded greens/blues did not
|
|
38
|
+
// (e.g. primary-container fell back to green #dcfce7, real material light is the
|
|
39
|
+
// blue #d3e3fd) — see #1586.
|
|
34
40
|
const colorSchemes: Record<
|
|
35
41
|
StatusType,
|
|
36
42
|
Record<string, { bg: string; text: string; border?: string }>
|
|
37
43
|
> = {
|
|
38
44
|
default: {
|
|
39
45
|
active: {
|
|
40
|
-
bg: 'var(--smrt-color-primary-container
|
|
41
|
-
text: 'var(--smrt-color-on-primary-container
|
|
46
|
+
bg: 'var(--smrt-color-primary-container)',
|
|
47
|
+
text: 'var(--smrt-color-on-primary-container)',
|
|
42
48
|
},
|
|
43
49
|
inactive: {
|
|
44
|
-
bg: 'var(--smrt-color-surface-container-highest
|
|
45
|
-
text: 'var(--smrt-color-on-surface-variant
|
|
50
|
+
bg: 'var(--smrt-color-surface-container-highest)',
|
|
51
|
+
text: 'var(--smrt-color-on-surface-variant)',
|
|
46
52
|
},
|
|
47
53
|
pending: {
|
|
48
|
-
bg: 'var(--smrt-color-secondary-container
|
|
49
|
-
text: 'var(--smrt-color-on-secondary-container
|
|
54
|
+
bg: 'var(--smrt-color-secondary-container)',
|
|
55
|
+
text: 'var(--smrt-color-on-secondary-container)',
|
|
50
56
|
},
|
|
51
57
|
error: {
|
|
52
|
-
bg: 'var(--smrt-color-error-container
|
|
53
|
-
text: 'var(--smrt-color-on-error-container
|
|
58
|
+
bg: 'var(--smrt-color-error-container)',
|
|
59
|
+
text: 'var(--smrt-color-on-error-container)',
|
|
54
60
|
},
|
|
55
61
|
success: {
|
|
56
|
-
bg: 'var(--smrt-color-primary-container
|
|
57
|
-
text: 'var(--smrt-color-on-primary-container
|
|
62
|
+
bg: 'var(--smrt-color-primary-container)',
|
|
63
|
+
text: 'var(--smrt-color-on-primary-container)',
|
|
58
64
|
},
|
|
59
65
|
warning: {
|
|
60
|
-
bg: 'var(--smrt-color-secondary-container
|
|
61
|
-
text: 'var(--smrt-color-on-secondary-container
|
|
66
|
+
bg: 'var(--smrt-color-secondary-container)',
|
|
67
|
+
text: 'var(--smrt-color-on-secondary-container)',
|
|
62
68
|
},
|
|
63
69
|
},
|
|
64
70
|
invoice: {
|
|
65
71
|
draft: {
|
|
66
|
-
bg: 'var(--smrt-color-surface-container-highest
|
|
67
|
-
text: 'var(--smrt-color-on-surface-variant
|
|
72
|
+
bg: 'var(--smrt-color-surface-container-highest)',
|
|
73
|
+
text: 'var(--smrt-color-on-surface-variant)',
|
|
68
74
|
},
|
|
69
75
|
sent: {
|
|
70
|
-
bg: 'var(--smrt-color-tertiary-container
|
|
71
|
-
text: 'var(--smrt-color-on-tertiary-container
|
|
76
|
+
bg: 'var(--smrt-color-tertiary-container)',
|
|
77
|
+
text: 'var(--smrt-color-on-tertiary-container)',
|
|
72
78
|
},
|
|
73
79
|
viewed: {
|
|
74
|
-
bg: 'var(--smrt-color-primary-container
|
|
75
|
-
text: 'var(--smrt-color-on-primary-container
|
|
80
|
+
bg: 'var(--smrt-color-primary-container)',
|
|
81
|
+
text: 'var(--smrt-color-on-primary-container)',
|
|
76
82
|
},
|
|
77
83
|
paid: {
|
|
78
|
-
bg: 'var(--smrt-color-primary-container
|
|
79
|
-
text: 'var(--smrt-color-on-primary-container
|
|
84
|
+
bg: 'var(--smrt-color-primary-container)',
|
|
85
|
+
text: 'var(--smrt-color-on-primary-container)',
|
|
80
86
|
},
|
|
81
87
|
overdue: {
|
|
82
|
-
bg: 'var(--smrt-color-error-container
|
|
83
|
-
text: 'var(--smrt-color-on-error-container
|
|
88
|
+
bg: 'var(--smrt-color-error-container)',
|
|
89
|
+
text: 'var(--smrt-color-on-error-container)',
|
|
84
90
|
},
|
|
85
91
|
cancelled: {
|
|
86
|
-
bg: 'var(--smrt-color-error-container
|
|
87
|
-
text: 'var(--smrt-color-on-error-container
|
|
92
|
+
bg: 'var(--smrt-color-error-container)',
|
|
93
|
+
text: 'var(--smrt-color-on-error-container)',
|
|
88
94
|
},
|
|
89
95
|
},
|
|
90
96
|
project: {
|
|
91
97
|
lead: {
|
|
92
|
-
bg: 'var(--smrt-color-primary-container
|
|
93
|
-
text: 'var(--smrt-color-on-primary-container
|
|
98
|
+
bg: 'var(--smrt-color-primary-container)',
|
|
99
|
+
text: 'var(--smrt-color-on-primary-container)',
|
|
94
100
|
},
|
|
95
101
|
quoted: {
|
|
96
|
-
bg: 'var(--smrt-color-secondary-container
|
|
97
|
-
text: 'var(--smrt-color-on-secondary-container
|
|
102
|
+
bg: 'var(--smrt-color-secondary-container)',
|
|
103
|
+
text: 'var(--smrt-color-on-secondary-container)',
|
|
98
104
|
},
|
|
99
105
|
active: {
|
|
100
|
-
bg: 'var(--smrt-color-tertiary-container
|
|
101
|
-
text: 'var(--smrt-color-on-tertiary-container
|
|
106
|
+
bg: 'var(--smrt-color-tertiary-container)',
|
|
107
|
+
text: 'var(--smrt-color-on-tertiary-container)',
|
|
102
108
|
},
|
|
103
109
|
on_hold: {
|
|
104
|
-
bg: 'var(--smrt-color-secondary-container
|
|
105
|
-
text: 'var(--smrt-color-on-secondary-container
|
|
110
|
+
bg: 'var(--smrt-color-secondary-container)',
|
|
111
|
+
text: 'var(--smrt-color-on-secondary-container)',
|
|
106
112
|
},
|
|
107
113
|
completed: {
|
|
108
|
-
bg: 'var(--smrt-color-primary-container
|
|
109
|
-
text: 'var(--smrt-color-on-primary-container
|
|
114
|
+
bg: 'var(--smrt-color-primary-container)',
|
|
115
|
+
text: 'var(--smrt-color-on-primary-container)',
|
|
110
116
|
},
|
|
111
117
|
archived: {
|
|
112
|
-
bg: 'var(--smrt-color-surface-container-highest
|
|
113
|
-
text: 'var(--smrt-color-on-surface-variant
|
|
118
|
+
bg: 'var(--smrt-color-surface-container-highest)',
|
|
119
|
+
text: 'var(--smrt-color-on-surface-variant)',
|
|
114
120
|
},
|
|
115
121
|
},
|
|
116
122
|
expense: {
|
|
117
123
|
unbilled: {
|
|
118
|
-
bg: 'var(--smrt-color-secondary-container
|
|
119
|
-
text: 'var(--smrt-color-on-secondary-container
|
|
124
|
+
bg: 'var(--smrt-color-secondary-container)',
|
|
125
|
+
text: 'var(--smrt-color-on-secondary-container)',
|
|
120
126
|
},
|
|
121
127
|
billed: {
|
|
122
|
-
bg: 'var(--smrt-color-primary-container
|
|
123
|
-
text: 'var(--smrt-color-on-primary-container
|
|
128
|
+
bg: 'var(--smrt-color-primary-container)',
|
|
129
|
+
text: 'var(--smrt-color-on-primary-container)',
|
|
124
130
|
},
|
|
125
131
|
reimbursed: {
|
|
126
|
-
bg: 'var(--smrt-color-tertiary-container
|
|
127
|
-
text: 'var(--smrt-color-on-tertiary-container
|
|
132
|
+
bg: 'var(--smrt-color-tertiary-container)',
|
|
133
|
+
text: 'var(--smrt-color-on-tertiary-container)',
|
|
128
134
|
},
|
|
129
135
|
rejected: {
|
|
130
|
-
bg: 'var(--smrt-color-error-container
|
|
131
|
-
text: 'var(--smrt-color-on-error-container
|
|
136
|
+
bg: 'var(--smrt-color-error-container)',
|
|
137
|
+
text: 'var(--smrt-color-on-error-container)',
|
|
132
138
|
},
|
|
133
139
|
},
|
|
134
140
|
time: {
|
|
135
141
|
draft: {
|
|
136
|
-
bg: 'var(--smrt-color-surface-container-highest
|
|
137
|
-
text: 'var(--smrt-color-on-surface-variant
|
|
142
|
+
bg: 'var(--smrt-color-surface-container-highest)',
|
|
143
|
+
text: 'var(--smrt-color-on-surface-variant)',
|
|
138
144
|
},
|
|
139
145
|
submitted: {
|
|
140
|
-
bg: 'var(--smrt-color-tertiary-container
|
|
141
|
-
text: 'var(--smrt-color-on-tertiary-container
|
|
146
|
+
bg: 'var(--smrt-color-tertiary-container)',
|
|
147
|
+
text: 'var(--smrt-color-on-tertiary-container)',
|
|
142
148
|
},
|
|
143
149
|
approved: {
|
|
144
|
-
bg: 'var(--smrt-color-primary-container
|
|
145
|
-
text: 'var(--smrt-color-on-primary-container
|
|
150
|
+
bg: 'var(--smrt-color-primary-container)',
|
|
151
|
+
text: 'var(--smrt-color-on-primary-container)',
|
|
146
152
|
},
|
|
147
153
|
rejected: {
|
|
148
|
-
bg: 'var(--smrt-color-error-container
|
|
149
|
-
text: 'var(--smrt-color-on-error-container
|
|
154
|
+
bg: 'var(--smrt-color-error-container)',
|
|
155
|
+
text: 'var(--smrt-color-on-error-container)',
|
|
150
156
|
},
|
|
151
157
|
billed: {
|
|
152
|
-
bg: 'var(--smrt-color-primary-container
|
|
153
|
-
text: 'var(--smrt-color-on-primary-container
|
|
158
|
+
bg: 'var(--smrt-color-primary-container)',
|
|
159
|
+
text: 'var(--smrt-color-on-primary-container)',
|
|
154
160
|
},
|
|
155
161
|
},
|
|
156
162
|
compliance: {
|
|
157
163
|
valid: {
|
|
158
|
-
bg: 'var(--smrt-color-primary-container
|
|
159
|
-
text: 'var(--smrt-color-on-primary-container
|
|
164
|
+
bg: 'var(--smrt-color-primary-container)',
|
|
165
|
+
text: 'var(--smrt-color-on-primary-container)',
|
|
160
166
|
},
|
|
161
167
|
expiring: {
|
|
162
|
-
bg: 'var(--smrt-color-secondary-container
|
|
163
|
-
text: 'var(--smrt-color-on-secondary-container
|
|
168
|
+
bg: 'var(--smrt-color-secondary-container)',
|
|
169
|
+
text: 'var(--smrt-color-on-secondary-container)',
|
|
164
170
|
},
|
|
165
171
|
expired: {
|
|
166
|
-
bg: 'var(--smrt-color-error-container
|
|
167
|
-
text: 'var(--smrt-color-on-error-container
|
|
172
|
+
bg: 'var(--smrt-color-error-container)',
|
|
173
|
+
text: 'var(--smrt-color-on-error-container)',
|
|
168
174
|
},
|
|
169
175
|
pending: {
|
|
170
|
-
bg: 'var(--smrt-color-tertiary-container
|
|
171
|
-
text: 'var(--smrt-color-on-tertiary-container
|
|
176
|
+
bg: 'var(--smrt-color-tertiary-container)',
|
|
177
|
+
text: 'var(--smrt-color-on-tertiary-container)',
|
|
172
178
|
},
|
|
173
179
|
},
|
|
174
180
|
estimate: {
|
|
175
181
|
draft: {
|
|
176
|
-
bg: 'var(--smrt-color-surface-container-highest
|
|
177
|
-
text: 'var(--smrt-color-on-surface-variant
|
|
182
|
+
bg: 'var(--smrt-color-surface-container-highest)',
|
|
183
|
+
text: 'var(--smrt-color-on-surface-variant)',
|
|
178
184
|
},
|
|
179
185
|
presented: {
|
|
180
|
-
bg: 'var(--smrt-color-tertiary-container
|
|
181
|
-
text: 'var(--smrt-color-on-tertiary-container
|
|
186
|
+
bg: 'var(--smrt-color-tertiary-container)',
|
|
187
|
+
text: 'var(--smrt-color-on-tertiary-container)',
|
|
182
188
|
},
|
|
183
189
|
accepted: {
|
|
184
|
-
bg: 'var(--smrt-color-primary-container
|
|
185
|
-
text: 'var(--smrt-color-on-primary-container
|
|
190
|
+
bg: 'var(--smrt-color-primary-container)',
|
|
191
|
+
text: 'var(--smrt-color-on-primary-container)',
|
|
186
192
|
},
|
|
187
193
|
declined: {
|
|
188
|
-
bg: 'var(--smrt-color-error-container
|
|
189
|
-
text: 'var(--smrt-color-on-error-container
|
|
194
|
+
bg: 'var(--smrt-color-error-container)',
|
|
195
|
+
text: 'var(--smrt-color-on-error-container)',
|
|
190
196
|
},
|
|
191
197
|
expired: {
|
|
192
|
-
bg: 'var(--smrt-color-error-container
|
|
193
|
-
text: 'var(--smrt-color-on-error-container
|
|
198
|
+
bg: 'var(--smrt-color-error-container)',
|
|
199
|
+
text: 'var(--smrt-color-on-error-container)',
|
|
194
200
|
},
|
|
195
201
|
},
|
|
196
202
|
};
|
|
@@ -203,8 +209,8 @@ const colors = $derived.by(() => {
|
|
|
203
209
|
const scheme = colorSchemes[type] ?? colorSchemes.default;
|
|
204
210
|
return (
|
|
205
211
|
scheme[normalizedStatus] ?? {
|
|
206
|
-
bg: 'var(--smrt-color-surface-container-highest
|
|
207
|
-
text: 'var(--smrt-color-on-surface-variant
|
|
212
|
+
bg: 'var(--smrt-color-surface-container-highest)',
|
|
213
|
+
text: 'var(--smrt-color-on-surface-variant)',
|
|
208
214
|
}
|
|
209
215
|
);
|
|
210
216
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StatusBadge.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/display/StatusBadge.svelte.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,sCAAsC;AACtC,MAAM,WAAW,KAAK;IACpB,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,iBAAiB;IACjB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,qBAAqB;IACrB,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"StatusBadge.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/display/StatusBadge.svelte.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,sCAAsC;AACtC,MAAM,WAAW,KAAK;IACpB,kCAAkC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,oCAAoC;IACpC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,iBAAiB;IACjB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,qBAAqB;IACrB,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAoND,QAAA,MAAM,WAAW,2CAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -71,6 +71,19 @@ describe('ConfidenceBadge', () => {
|
|
|
71
71
|
const badge = container.querySelector('.confidence-badge');
|
|
72
72
|
expect(badge.style.getPropertyValue('--bar-color')).toContain(`smrt-color-${token}`);
|
|
73
73
|
});
|
|
74
|
+
it('emits bare design tokens with no literal hex fallback', () => {
|
|
75
|
+
// Regression for #1586: the old `var(--token, #hex)` fallbacks were the
|
|
76
|
+
// wrong colors (high → green #dcfce7 vs real material light blue #d3e3fd).
|
|
77
|
+
const { container } = render(ConfidenceBadge, {
|
|
78
|
+
props: { confidence: 90 },
|
|
79
|
+
});
|
|
80
|
+
const badge = container.querySelector('.confidence-badge');
|
|
81
|
+
for (const prop of ['--badge-bg', '--badge-text', '--bar-color']) {
|
|
82
|
+
const value = badge.style.getPropertyValue(prop);
|
|
83
|
+
expect(value).toMatch(/^var\(--smrt-color-[a-z-]+\)$/);
|
|
84
|
+
expect(value).not.toContain('#');
|
|
85
|
+
}
|
|
86
|
+
});
|
|
74
87
|
it('appends a custom class alongside the base class', () => {
|
|
75
88
|
const { container } = render(ConfidenceBadge, {
|
|
76
89
|
props: { confidence: 50, class: 'my-badge' },
|
|
@@ -66,8 +66,23 @@ describe('StatusBadge', () => {
|
|
|
66
66
|
props: { status: 'totally-unknown', type: 'invoice' },
|
|
67
67
|
});
|
|
68
68
|
const span = container.querySelector('span');
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
// The neutral fallback resolves to canonical surface tokens, not a literal
|
|
70
|
+
// hex. smrt-ui owns the theme system, so the tokens are always defined; a
|
|
71
|
+
// hardcoded fallback would freeze the wrong color across presets (#1586).
|
|
72
|
+
expect(span.style.getPropertyValue('--badge-bg')).toBe('var(--smrt-color-surface-container-highest)');
|
|
73
|
+
expect(span.style.getPropertyValue('--badge-text')).toBe('var(--smrt-color-on-surface-variant)');
|
|
74
|
+
});
|
|
75
|
+
it('uses bare design tokens with no literal hex fallback in scheme colors', () => {
|
|
76
|
+
// Regression for #1586: the `var(--token, #hex)` fallbacks were the wrong
|
|
77
|
+
// colors (e.g. primary-container → green #dcfce7 vs real material light blue
|
|
78
|
+
// #d3e3fd). Every emitted custom property must be a bare token reference.
|
|
79
|
+
const { container } = render(StatusBadge, {
|
|
80
|
+
props: { status: 'paid', type: 'invoice' },
|
|
81
|
+
});
|
|
82
|
+
const span = container.querySelector('span');
|
|
83
|
+
expect(span.style.getPropertyValue('--badge-bg')).toBe('var(--smrt-color-primary-container)');
|
|
84
|
+
expect(span.style.getPropertyValue('--badge-bg')).not.toContain('#');
|
|
85
|
+
expect(span.style.getPropertyValue('--badge-text')).not.toContain('#');
|
|
71
86
|
});
|
|
72
87
|
it('normalizes spaces and hyphens when matching a scheme key', () => {
|
|
73
88
|
// 'On-Hold' normalizes to 'on_hold', a known project status.
|