@aspect-ops/exon-ui 0.2.1 → 0.2.2
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/components/AspectRatio/AspectRatio.svelte +1 -0
- package/dist/components/Card/FlipCard.svelte +155 -0
- package/dist/components/Card/FlipCard.svelte.d.ts +13 -0
- package/dist/components/Card/index.d.ts +1 -0
- package/dist/components/Card/index.js +1 -0
- package/dist/components/Container/Container.svelte +1 -0
- package/dist/components/DataTable/DataTable.svelte +460 -0
- package/dist/components/DataTable/DataTable.svelte.d.ts +49 -0
- package/dist/components/DataTable/index.d.ts +2 -0
- package/dist/components/DataTable/index.js +1 -0
- package/dist/components/DoughnutChart/DoughnutChart.svelte +20 -2
- package/dist/components/Icon/Icon.svelte +15 -18
- package/dist/components/Icon/Icon.svelte.d.ts +2 -1
- package/dist/components/Menu/MenuContent.svelte +1 -0
- package/dist/components/Menu/MenuSubContent.svelte +1 -0
- package/dist/components/Mermaid/Mermaid.svelte +121 -7
- package/dist/components/Mermaid/Mermaid.svelte.d.ts +10 -0
- package/dist/components/PageHeader/PageHeader.svelte +140 -0
- package/dist/components/PageHeader/PageHeader.svelte.d.ts +30 -0
- package/dist/components/PageHeader/index.d.ts +1 -0
- package/dist/components/PageHeader/index.js +1 -0
- package/dist/components/StatCircle/StatCircle.svelte +172 -0
- package/dist/components/StatCircle/StatCircle.svelte.d.ts +19 -0
- package/dist/components/StatCircle/index.d.ts +1 -0
- package/dist/components/StatCircle/index.js +1 -0
- package/dist/components/StatsCard/StatsCard.svelte +301 -0
- package/dist/components/StatsCard/StatsCard.svelte.d.ts +32 -0
- package/dist/components/StatsCard/index.d.ts +2 -0
- package/dist/components/StatsCard/index.js +1 -0
- package/dist/components/StatusBadge/StatusBadge.svelte +221 -0
- package/dist/components/StatusBadge/StatusBadge.svelte.d.ts +22 -0
- package/dist/components/StatusBadge/index.d.ts +2 -0
- package/dist/components/StatusBadge/index.js +1 -0
- package/dist/components/StatusBanner/StatusBanner.svelte +325 -0
- package/dist/components/StatusBanner/StatusBanner.svelte.d.ts +13 -0
- package/dist/components/StatusBanner/index.d.ts +1 -0
- package/dist/components/StatusBanner/index.js +1 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.js +7 -1
- package/dist/types/data-display.d.ts +72 -0
- package/dist/types/feedback.d.ts +10 -0
- package/dist/types/index.d.ts +2 -2
- package/package.json +3 -2
|
@@ -212,6 +212,17 @@
|
|
|
212
212
|
</text>
|
|
213
213
|
{/if}
|
|
214
214
|
{/each}
|
|
215
|
+
|
|
216
|
+
<!-- Background circle for center content -->
|
|
217
|
+
{#if showTotal}
|
|
218
|
+
<circle
|
|
219
|
+
cx={size / 2}
|
|
220
|
+
cy={size / 2}
|
|
221
|
+
r={innerRadius * 0.95}
|
|
222
|
+
fill="var(--color-bg, #ffffff)"
|
|
223
|
+
class="doughnut-chart__center-bg"
|
|
224
|
+
/>
|
|
225
|
+
{/if}
|
|
215
226
|
</svg>
|
|
216
227
|
|
|
217
228
|
<!-- Center total -->
|
|
@@ -290,6 +301,11 @@
|
|
|
290
301
|
pointer-events: none;
|
|
291
302
|
}
|
|
292
303
|
|
|
304
|
+
/* Center Background */
|
|
305
|
+
.doughnut-chart__center-bg {
|
|
306
|
+
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.1));
|
|
307
|
+
}
|
|
308
|
+
|
|
293
309
|
/* Center */
|
|
294
310
|
.doughnut-chart__center {
|
|
295
311
|
position: absolute;
|
|
@@ -298,12 +314,13 @@
|
|
|
298
314
|
transform: translate(-50%, -50%);
|
|
299
315
|
text-align: center;
|
|
300
316
|
pointer-events: none;
|
|
317
|
+
z-index: 10;
|
|
301
318
|
}
|
|
302
319
|
|
|
303
320
|
.doughnut-chart__total {
|
|
304
321
|
display: block;
|
|
305
|
-
font-size: var(--text-
|
|
306
|
-
font-weight: var(--font-
|
|
322
|
+
font-size: var(--text-lg, 1.125rem);
|
|
323
|
+
font-weight: var(--font-semibold, 600);
|
|
307
324
|
color: var(--color-text, #1f2937);
|
|
308
325
|
line-height: 1.2;
|
|
309
326
|
}
|
|
@@ -314,6 +331,7 @@
|
|
|
314
331
|
color: var(--color-text-muted, #6b7280);
|
|
315
332
|
text-transform: uppercase;
|
|
316
333
|
letter-spacing: 0.05em;
|
|
334
|
+
margin-top: 2px;
|
|
317
335
|
}
|
|
318
336
|
|
|
319
337
|
/* Legend */
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { IconSize } from '../../types/index.js';
|
|
3
|
+
import type { ComponentType } from 'svelte';
|
|
3
4
|
|
|
4
5
|
interface Props {
|
|
5
|
-
|
|
6
|
+
icon: ComponentType;
|
|
6
7
|
size?: IconSize;
|
|
7
8
|
class?: string;
|
|
8
9
|
'aria-label'?: string;
|
|
@@ -10,38 +11,34 @@
|
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
let {
|
|
13
|
-
|
|
14
|
+
icon: IconComponent,
|
|
14
15
|
size = 'md',
|
|
15
16
|
class: className = '',
|
|
16
17
|
'aria-label': ariaLabel,
|
|
17
18
|
'aria-hidden': ariaHidden = !ariaLabel
|
|
18
19
|
}: Props = $props();
|
|
19
20
|
|
|
20
|
-
// Size map aligned with typography scale
|
|
21
|
-
const sizeMap: Record<IconSize,
|
|
22
|
-
xs:
|
|
23
|
-
sm:
|
|
24
|
-
md:
|
|
25
|
-
lg:
|
|
26
|
-
xl:
|
|
21
|
+
// Size map aligned with typography scale (in pixels for Lucide)
|
|
22
|
+
const sizeMap: Record<IconSize, number> = {
|
|
23
|
+
xs: 12,
|
|
24
|
+
sm: 16,
|
|
25
|
+
md: 20,
|
|
26
|
+
lg: 24,
|
|
27
|
+
xl: 32
|
|
27
28
|
};
|
|
28
29
|
</script>
|
|
29
30
|
|
|
30
|
-
<
|
|
31
|
+
<IconComponent
|
|
32
|
+
size={sizeMap[size]}
|
|
31
33
|
class="icon icon--{size} {className}"
|
|
32
|
-
style="--icon-size: {sizeMap[size]}"
|
|
33
34
|
aria-label={ariaLabel}
|
|
34
35
|
aria-hidden={ariaHidden}
|
|
35
36
|
role={ariaLabel ? 'img' : undefined}
|
|
36
|
-
|
|
37
|
-
<use href="#{name}" />
|
|
38
|
-
</svg>
|
|
37
|
+
/>
|
|
39
38
|
|
|
40
39
|
<style>
|
|
41
|
-
.icon {
|
|
42
|
-
width: var(--icon-size);
|
|
43
|
-
height: var(--icon-size);
|
|
44
|
-
fill: currentColor;
|
|
40
|
+
:global(.icon) {
|
|
45
41
|
flex-shrink: 0;
|
|
42
|
+
color: currentColor;
|
|
46
43
|
}
|
|
47
44
|
</style>
|
|
@@ -15,6 +15,17 @@
|
|
|
15
15
|
clusterBkg?: string;
|
|
16
16
|
titleColor?: string;
|
|
17
17
|
edgeLabelBackground?: string;
|
|
18
|
+
textColor?: string;
|
|
19
|
+
// Sequence diagram specific
|
|
20
|
+
actorLineColor?: string;
|
|
21
|
+
signalColor?: string;
|
|
22
|
+
signalTextColor?: string;
|
|
23
|
+
labelTextColor?: string;
|
|
24
|
+
actorTextColor?: string;
|
|
25
|
+
messageTextColor?: string;
|
|
26
|
+
loopTextColor?: string;
|
|
27
|
+
activationBorderColor?: string;
|
|
28
|
+
sequenceNumberColor?: string;
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
interface Props {
|
|
@@ -28,17 +39,61 @@
|
|
|
28
39
|
children?: Snippet;
|
|
29
40
|
}
|
|
30
41
|
|
|
31
|
-
//
|
|
32
|
-
const
|
|
42
|
+
// Light theme colors
|
|
43
|
+
const lightTheme: ThemeVariables = {
|
|
33
44
|
primaryColor: '#4654A3',
|
|
34
|
-
primaryTextColor: '#
|
|
45
|
+
primaryTextColor: '#ffffff',
|
|
35
46
|
primaryBorderColor: '#4654A3',
|
|
36
|
-
lineColor: '#
|
|
47
|
+
lineColor: '#4b5563',
|
|
37
48
|
secondaryColor: '#FF3131',
|
|
38
|
-
tertiaryColor: '#f3f4f6'
|
|
49
|
+
tertiaryColor: '#f3f4f6',
|
|
50
|
+
textColor: '#000000',
|
|
51
|
+
// Sequence diagram - dark colors for light backgrounds
|
|
52
|
+
actorLineColor: '#4b5563',
|
|
53
|
+
signalColor: '#374151',
|
|
54
|
+
signalTextColor: '#000000',
|
|
55
|
+
labelTextColor: '#000000',
|
|
56
|
+
actorTextColor: '#ffffff',
|
|
57
|
+
messageTextColor: '#000000',
|
|
58
|
+
loopTextColor: '#000000',
|
|
59
|
+
activationBorderColor: '#4b5563',
|
|
60
|
+
sequenceNumberColor: '#ffffff'
|
|
39
61
|
};
|
|
40
62
|
|
|
41
|
-
|
|
63
|
+
// Dark theme colors
|
|
64
|
+
const darkTheme: ThemeVariables = {
|
|
65
|
+
primaryColor: '#4654A3',
|
|
66
|
+
primaryTextColor: '#ffffff',
|
|
67
|
+
primaryBorderColor: '#4654A3',
|
|
68
|
+
lineColor: '#9ca3af',
|
|
69
|
+
secondaryColor: '#FF3131',
|
|
70
|
+
tertiaryColor: '#1f2937',
|
|
71
|
+
textColor: '#7dd3fc',
|
|
72
|
+
// Sequence diagram - sky blue colors for dark backgrounds
|
|
73
|
+
actorLineColor: '#38bdf8',
|
|
74
|
+
signalColor: '#38bdf8',
|
|
75
|
+
signalTextColor: '#7dd3fc',
|
|
76
|
+
labelTextColor: '#7dd3fc',
|
|
77
|
+
actorTextColor: '#ffffff',
|
|
78
|
+
messageTextColor: '#7dd3fc',
|
|
79
|
+
loopTextColor: '#7dd3fc',
|
|
80
|
+
activationBorderColor: '#38bdf8',
|
|
81
|
+
sequenceNumberColor: '#ffffff'
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Detect theme
|
|
85
|
+
function getThemeColors(): ThemeVariables {
|
|
86
|
+
if (typeof window === 'undefined') return lightTheme;
|
|
87
|
+
|
|
88
|
+
const isDark =
|
|
89
|
+
document.documentElement.classList.contains('dark') ||
|
|
90
|
+
document.body.classList.contains('dark') ||
|
|
91
|
+
window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
92
|
+
|
|
93
|
+
return isDark ? darkTheme : lightTheme;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let { code, theme, class: className = '', children }: Props = $props();
|
|
42
97
|
|
|
43
98
|
let containerRef: HTMLDivElement | undefined = $state();
|
|
44
99
|
let slotRef: HTMLDivElement | undefined = $state();
|
|
@@ -62,11 +117,15 @@
|
|
|
62
117
|
const mermaidModule = await import('mermaid');
|
|
63
118
|
const mermaid = mermaidModule.default;
|
|
64
119
|
|
|
120
|
+
// Detect theme and merge with custom theme
|
|
121
|
+
const detectedTheme = getThemeColors();
|
|
122
|
+
const mergedTheme = { ...detectedTheme, ...theme };
|
|
123
|
+
|
|
65
124
|
// Initialize mermaid with theme
|
|
66
125
|
mermaid.initialize({
|
|
67
126
|
startOnLoad: false,
|
|
68
127
|
theme: 'base',
|
|
69
|
-
themeVariables:
|
|
128
|
+
themeVariables: mergedTheme,
|
|
70
129
|
securityLevel: 'loose',
|
|
71
130
|
fontFamily: 'inherit'
|
|
72
131
|
});
|
|
@@ -203,4 +262,59 @@
|
|
|
203
262
|
max-width: 100%;
|
|
204
263
|
height: auto;
|
|
205
264
|
}
|
|
265
|
+
|
|
266
|
+
/* Force text colors in dark mode for all diagrams (flowchart + sequence) */
|
|
267
|
+
:global(.dark) .mermaid-diagram :global(svg text),
|
|
268
|
+
:global(.dark) .mermaid-diagram :global(svg .messageText),
|
|
269
|
+
:global(body.dark) .mermaid-diagram :global(svg text),
|
|
270
|
+
:global(body.dark) .mermaid-diagram :global(svg .messageText),
|
|
271
|
+
:global(html.dark) .mermaid-diagram :global(svg text),
|
|
272
|
+
:global(html.dark) .mermaid-diagram :global(svg .messageText) {
|
|
273
|
+
fill: #7dd3fc !important;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/* Force line colors in dark mode for all diagrams */
|
|
277
|
+
:global(.dark) .mermaid-diagram :global(svg .messageLine0),
|
|
278
|
+
:global(.dark) .mermaid-diagram :global(svg .messageLine1),
|
|
279
|
+
:global(.dark) .mermaid-diagram :global(svg line),
|
|
280
|
+
:global(.dark) .mermaid-diagram :global(svg path),
|
|
281
|
+
:global(.dark) .mermaid-diagram :global(svg .edgePath path),
|
|
282
|
+
:global(.dark) .mermaid-diagram :global(svg .flowchart-link),
|
|
283
|
+
:global(body.dark) .mermaid-diagram :global(svg .messageLine0),
|
|
284
|
+
:global(body.dark) .mermaid-diagram :global(svg .messageLine1),
|
|
285
|
+
:global(body.dark) .mermaid-diagram :global(svg line),
|
|
286
|
+
:global(body.dark) .mermaid-diagram :global(svg path),
|
|
287
|
+
:global(body.dark) .mermaid-diagram :global(svg .edgePath path),
|
|
288
|
+
:global(body.dark) .mermaid-diagram :global(svg .flowchart-link),
|
|
289
|
+
:global(html.dark) .mermaid-diagram :global(svg .messageLine0),
|
|
290
|
+
:global(html.dark) .mermaid-diagram :global(svg .messageLine1),
|
|
291
|
+
:global(html.dark) .mermaid-diagram :global(svg line),
|
|
292
|
+
:global(html.dark) .mermaid-diagram :global(svg path),
|
|
293
|
+
:global(html.dark) .mermaid-diagram :global(svg .edgePath path),
|
|
294
|
+
:global(html.dark) .mermaid-diagram :global(svg .flowchart-link) {
|
|
295
|
+
stroke: #38bdf8 !important;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* Flowchart edge labels */
|
|
299
|
+
:global(.dark) .mermaid-diagram :global(svg .edgeLabel),
|
|
300
|
+
:global(body.dark) .mermaid-diagram :global(svg .edgeLabel),
|
|
301
|
+
:global(html.dark) .mermaid-diagram :global(svg .edgeLabel) {
|
|
302
|
+
color: #7dd3fc !important;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
:global(.dark) .mermaid-diagram :global(svg .edgeLabel text),
|
|
306
|
+
:global(body.dark) .mermaid-diagram :global(svg .edgeLabel text),
|
|
307
|
+
:global(html.dark) .mermaid-diagram :global(svg .edgeLabel text) {
|
|
308
|
+
fill: #7dd3fc !important;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/* Keep actor text and node text white for readability */
|
|
312
|
+
:global(.dark) .mermaid-diagram :global(svg .actor text),
|
|
313
|
+
:global(.dark) .mermaid-diagram :global(svg .node text),
|
|
314
|
+
:global(body.dark) .mermaid-diagram :global(svg .actor text),
|
|
315
|
+
:global(body.dark) .mermaid-diagram :global(svg .node text),
|
|
316
|
+
:global(html.dark) .mermaid-diagram :global(svg .actor text),
|
|
317
|
+
:global(html.dark) .mermaid-diagram :global(svg .node text) {
|
|
318
|
+
fill: #ffffff !important;
|
|
319
|
+
}
|
|
206
320
|
</style>
|
|
@@ -12,6 +12,16 @@ interface ThemeVariables {
|
|
|
12
12
|
clusterBkg?: string;
|
|
13
13
|
titleColor?: string;
|
|
14
14
|
edgeLabelBackground?: string;
|
|
15
|
+
textColor?: string;
|
|
16
|
+
actorLineColor?: string;
|
|
17
|
+
signalColor?: string;
|
|
18
|
+
signalTextColor?: string;
|
|
19
|
+
labelTextColor?: string;
|
|
20
|
+
actorTextColor?: string;
|
|
21
|
+
messageTextColor?: string;
|
|
22
|
+
loopTextColor?: string;
|
|
23
|
+
activationBorderColor?: string;
|
|
24
|
+
sequenceNumberColor?: string;
|
|
15
25
|
}
|
|
16
26
|
interface Props {
|
|
17
27
|
/** Mermaid diagram code. If not provided, uses slot content */
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Avatar from '../Avatar/Avatar.svelte';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
/**
|
|
6
|
+
* Greeting text to display (e.g., "Welcome back,")
|
|
7
|
+
* @default "Welcome back,"
|
|
8
|
+
*/
|
|
9
|
+
greeting?: string;
|
|
10
|
+
/**
|
|
11
|
+
* User name to display prominently
|
|
12
|
+
*/
|
|
13
|
+
userName: string;
|
|
14
|
+
/**
|
|
15
|
+
* Avatar image source URL
|
|
16
|
+
*/
|
|
17
|
+
avatarSrc?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Custom initials for avatar (overrides auto-generated from userName)
|
|
20
|
+
*/
|
|
21
|
+
avatarInitials?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Additional CSS classes
|
|
24
|
+
*/
|
|
25
|
+
class?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Action icons snippet (e.g., notification bell, sync icon)
|
|
28
|
+
*/
|
|
29
|
+
actions?: import('svelte').Snippet;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
greeting = 'Welcome back,',
|
|
34
|
+
userName,
|
|
35
|
+
avatarSrc,
|
|
36
|
+
avatarInitials,
|
|
37
|
+
class: className = '',
|
|
38
|
+
actions
|
|
39
|
+
}: Props = $props();
|
|
40
|
+
|
|
41
|
+
// Use custom initials if provided, otherwise derive from userName
|
|
42
|
+
const displayName = $derived(avatarInitials || userName);
|
|
43
|
+
</script>
|
|
44
|
+
|
|
45
|
+
<header class="page-header {className}">
|
|
46
|
+
<div class="page-header__content">
|
|
47
|
+
<div class="page-header__greeting">
|
|
48
|
+
<span class="page-header__greeting-text">{greeting}</span>
|
|
49
|
+
<h1 class="page-header__name">{userName}</h1>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div class="page-header__actions-wrapper">
|
|
53
|
+
{#if actions}
|
|
54
|
+
<div class="page-header__actions">
|
|
55
|
+
{@render actions()}
|
|
56
|
+
</div>
|
|
57
|
+
{/if}
|
|
58
|
+
|
|
59
|
+
<Avatar src={avatarSrc} name={displayName} size="md" class="page-header__avatar" />
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
</header>
|
|
63
|
+
|
|
64
|
+
<style>
|
|
65
|
+
.page-header {
|
|
66
|
+
font-family: inherit;
|
|
67
|
+
padding: var(--space-md, 1rem) var(--space-lg, 1.5rem);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.page-header__content {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
justify-content: space-between;
|
|
74
|
+
gap: var(--space-md, 1rem);
|
|
75
|
+
flex-wrap: wrap;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.page-header__greeting {
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
gap: var(--space-xs, 0.25rem);
|
|
82
|
+
min-width: 0;
|
|
83
|
+
flex: 1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.page-header__greeting-text {
|
|
87
|
+
font-size: 0.875rem;
|
|
88
|
+
line-height: 1.25rem;
|
|
89
|
+
color: var(--color-text-muted, #6b7280);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.page-header__name {
|
|
93
|
+
font-size: 1.5rem;
|
|
94
|
+
line-height: 2rem;
|
|
95
|
+
font-weight: 700;
|
|
96
|
+
color: var(--color-text, #1f2937);
|
|
97
|
+
margin: 0;
|
|
98
|
+
overflow: hidden;
|
|
99
|
+
text-overflow: ellipsis;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.page-header__actions-wrapper {
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
gap: var(--space-sm, 0.5rem);
|
|
106
|
+
flex-shrink: 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.page-header__actions {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: var(--space-xs, 0.25rem);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.page-header__avatar {
|
|
116
|
+
flex-shrink: 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* Dark mode support (F11) */
|
|
120
|
+
:global([data-theme='dark']) .page-header__greeting-text {
|
|
121
|
+
color: var(--color-text-muted, #9ca3af);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
:global([data-theme='dark']) .page-header__name {
|
|
125
|
+
color: var(--color-text, #f9fafb);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Responsive - stack on very narrow screens */
|
|
129
|
+
@media (max-width: 360px) {
|
|
130
|
+
.page-header__content {
|
|
131
|
+
flex-direction: column;
|
|
132
|
+
align-items: flex-start;
|
|
133
|
+
gap: var(--space-sm, 0.5rem);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.page-header__actions-wrapper {
|
|
137
|
+
align-self: flex-end;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
</style>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/**
|
|
3
|
+
* Greeting text to display (e.g., "Welcome back,")
|
|
4
|
+
* @default "Welcome back,"
|
|
5
|
+
*/
|
|
6
|
+
greeting?: string;
|
|
7
|
+
/**
|
|
8
|
+
* User name to display prominently
|
|
9
|
+
*/
|
|
10
|
+
userName: string;
|
|
11
|
+
/**
|
|
12
|
+
* Avatar image source URL
|
|
13
|
+
*/
|
|
14
|
+
avatarSrc?: string;
|
|
15
|
+
/**
|
|
16
|
+
* Custom initials for avatar (overrides auto-generated from userName)
|
|
17
|
+
*/
|
|
18
|
+
avatarInitials?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Additional CSS classes
|
|
21
|
+
*/
|
|
22
|
+
class?: string;
|
|
23
|
+
/**
|
|
24
|
+
* Action icons snippet (e.g., notification bell, sync icon)
|
|
25
|
+
*/
|
|
26
|
+
actions?: import('svelte').Snippet;
|
|
27
|
+
}
|
|
28
|
+
declare const PageHeader: import("svelte").Component<Props, {}, "">;
|
|
29
|
+
type PageHeader = ReturnType<typeof PageHeader>;
|
|
30
|
+
export default PageHeader;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as PageHeader } from './PageHeader.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as PageHeader } from './PageHeader.svelte';
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
/** The numeric value or string to display inside the circle */
|
|
4
|
+
value: number | string;
|
|
5
|
+
/** Label text displayed below the circle */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Color variant for the value text */
|
|
8
|
+
color?: 'default' | 'primary' | 'warning' | 'error' | 'success';
|
|
9
|
+
/** Size of the circle */
|
|
10
|
+
size?: 'sm' | 'md' | 'lg';
|
|
11
|
+
/** Makes the circle clickable with hover effects */
|
|
12
|
+
clickable?: boolean;
|
|
13
|
+
/** Additional CSS classes */
|
|
14
|
+
class?: string;
|
|
15
|
+
/** Click handler */
|
|
16
|
+
onclick?: (event: MouseEvent) => void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let {
|
|
20
|
+
value,
|
|
21
|
+
label,
|
|
22
|
+
color = 'default',
|
|
23
|
+
size = 'md',
|
|
24
|
+
clickable = false,
|
|
25
|
+
class: className = '',
|
|
26
|
+
onclick
|
|
27
|
+
}: Props = $props();
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
{#if clickable}
|
|
31
|
+
<button
|
|
32
|
+
class="stat-circle stat-circle--{color} stat-circle--{size} stat-circle--clickable {className}"
|
|
33
|
+
{onclick}
|
|
34
|
+
type="button"
|
|
35
|
+
>
|
|
36
|
+
<div class="stat-circle__circle">
|
|
37
|
+
<div class="stat-circle__value">{value}</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div class="stat-circle__label">{label}</div>
|
|
40
|
+
</button>
|
|
41
|
+
{:else}
|
|
42
|
+
<div class="stat-circle stat-circle--{color} stat-circle--{size} {className}">
|
|
43
|
+
<div class="stat-circle__circle">
|
|
44
|
+
<div class="stat-circle__value">{value}</div>
|
|
45
|
+
</div>
|
|
46
|
+
<div class="stat-circle__label">{label}</div>
|
|
47
|
+
</div>
|
|
48
|
+
{/if}
|
|
49
|
+
|
|
50
|
+
<style>
|
|
51
|
+
.stat-circle {
|
|
52
|
+
display: inline-flex;
|
|
53
|
+
flex-direction: column;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: var(--space-sm, 0.5rem);
|
|
56
|
+
font-family: inherit;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.stat-circle--clickable {
|
|
60
|
+
background: none;
|
|
61
|
+
border: none;
|
|
62
|
+
padding: 0;
|
|
63
|
+
cursor: pointer;
|
|
64
|
+
-webkit-tap-highlight-color: transparent;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.stat-circle--clickable:hover .stat-circle__circle {
|
|
68
|
+
transform: scale(1.05);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.stat-circle--clickable:active .stat-circle__circle {
|
|
72
|
+
transform: scale(0.98);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.stat-circle__circle {
|
|
76
|
+
display: flex;
|
|
77
|
+
align-items: center;
|
|
78
|
+
justify-content: center;
|
|
79
|
+
border-radius: var(--radius-full, 9999px);
|
|
80
|
+
background: var(--color-bg-subtle, #f9fafb);
|
|
81
|
+
border: 1px solid var(--color-border-subtle, #e5e7eb);
|
|
82
|
+
transition:
|
|
83
|
+
transform 0.2s ease,
|
|
84
|
+
box-shadow 0.2s ease;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.stat-circle--clickable:focus-visible .stat-circle__circle {
|
|
88
|
+
outline: 2px solid var(--color-focus, #3b82f6);
|
|
89
|
+
outline-offset: 2px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.stat-circle__value {
|
|
93
|
+
font-weight: 700;
|
|
94
|
+
line-height: 1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.stat-circle__label {
|
|
98
|
+
font-size: var(--text-sm, 0.875rem);
|
|
99
|
+
font-weight: 500;
|
|
100
|
+
color: var(--color-text-muted, #6b7280);
|
|
101
|
+
text-align: center;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Size variants */
|
|
105
|
+
.stat-circle--sm .stat-circle__circle {
|
|
106
|
+
width: 3rem;
|
|
107
|
+
height: 3rem;
|
|
108
|
+
min-width: 3rem;
|
|
109
|
+
min-height: 3rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.stat-circle--sm .stat-circle__value {
|
|
113
|
+
font-size: var(--text-lg, 1.125rem);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.stat-circle--md .stat-circle__circle {
|
|
117
|
+
width: 4rem;
|
|
118
|
+
height: 4rem;
|
|
119
|
+
min-width: 4rem;
|
|
120
|
+
min-height: 4rem;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.stat-circle--md .stat-circle__value {
|
|
124
|
+
font-size: var(--text-2xl, 1.5rem);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.stat-circle--lg .stat-circle__circle {
|
|
128
|
+
width: 5rem;
|
|
129
|
+
height: 5rem;
|
|
130
|
+
min-width: 5rem;
|
|
131
|
+
min-height: 5rem;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.stat-circle--lg .stat-circle__value {
|
|
135
|
+
font-size: var(--text-3xl, 1.875rem);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* Color variants */
|
|
139
|
+
.stat-circle--default .stat-circle__value {
|
|
140
|
+
color: var(--color-text, #1f2937);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.stat-circle--primary .stat-circle__value {
|
|
144
|
+
color: var(--color-primary, #3b82f6);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.stat-circle--warning .stat-circle__value {
|
|
148
|
+
color: var(--color-warning, #f59e0b);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.stat-circle--error .stat-circle__value {
|
|
152
|
+
color: var(--color-error, #ef4444);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.stat-circle--success .stat-circle__value {
|
|
156
|
+
color: var(--color-success, #10b981);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/* Dark mode support */
|
|
160
|
+
:global([data-theme='dark']) .stat-circle__circle {
|
|
161
|
+
background: var(--color-bg-subtle-dark, #1f2937);
|
|
162
|
+
border-color: var(--color-border-subtle-dark, #374151);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
:global([data-theme='dark']) .stat-circle__label {
|
|
166
|
+
color: var(--color-text-muted-dark, #9ca3af);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
:global([data-theme='dark']) .stat-circle--default .stat-circle__value {
|
|
170
|
+
color: var(--color-text-dark, #f9fafb);
|
|
171
|
+
}
|
|
172
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
/** The numeric value or string to display inside the circle */
|
|
3
|
+
value: number | string;
|
|
4
|
+
/** Label text displayed below the circle */
|
|
5
|
+
label: string;
|
|
6
|
+
/** Color variant for the value text */
|
|
7
|
+
color?: 'default' | 'primary' | 'warning' | 'error' | 'success';
|
|
8
|
+
/** Size of the circle */
|
|
9
|
+
size?: 'sm' | 'md' | 'lg';
|
|
10
|
+
/** Makes the circle clickable with hover effects */
|
|
11
|
+
clickable?: boolean;
|
|
12
|
+
/** Additional CSS classes */
|
|
13
|
+
class?: string;
|
|
14
|
+
/** Click handler */
|
|
15
|
+
onclick?: (event: MouseEvent) => void;
|
|
16
|
+
}
|
|
17
|
+
declare const StatCircle: import("svelte").Component<Props, {}, "">;
|
|
18
|
+
type StatCircle = ReturnType<typeof StatCircle>;
|
|
19
|
+
export default StatCircle;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as StatCircle } from './StatCircle.svelte';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as StatCircle } from './StatCircle.svelte';
|