@aspect-ops/exon-ui 0.2.1 → 0.3.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/dist/components/AspectRatio/AspectRatio.svelte +1 -0
- package/dist/components/CTASection/CTASection.svelte +298 -0
- package/dist/components/CTASection/CTASection.svelte.d.ts +15 -0
- package/dist/components/CTASection/index.d.ts +2 -0
- package/dist/components/CTASection/index.js +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/GlobalHeader/GlobalHeader.svelte +692 -0
- package/dist/components/GlobalHeader/GlobalHeader.svelte.d.ts +3 -0
- package/dist/components/GlobalHeader/index.d.ts +2 -0
- package/dist/components/GlobalHeader/index.js +1 -0
- package/dist/components/Hero/Hero.svelte +306 -0
- package/dist/components/Hero/Hero.svelte.d.ts +18 -0
- package/dist/components/Hero/index.d.ts +2 -0
- package/dist/components/Hero/index.js +1 -0
- package/dist/components/Icon/Icon.svelte +15 -18
- package/dist/components/Icon/Icon.svelte.d.ts +2 -1
- package/dist/components/LogoCloud/LogoCloud.svelte +333 -0
- package/dist/components/LogoCloud/LogoCloud.svelte.d.ts +20 -0
- package/dist/components/LogoCloud/index.d.ts +2 -0
- package/dist/components/LogoCloud/index.js +1 -0
- 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/ServiceCard/ServiceCard.svelte +359 -0
- package/dist/components/ServiceCard/ServiceCard.svelte.d.ts +16 -0
- package/dist/components/ServiceCard/index.d.ts +1 -0
- package/dist/components/ServiceCard/index.js +1 -0
- package/dist/components/SplitSection/SplitSection.svelte +194 -0
- package/dist/components/SplitSection/SplitSection.svelte.d.ts +15 -0
- package/dist/components/SplitSection/index.d.ts +1 -0
- package/dist/components/SplitSection/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/components/TestimonialCard/TestimonialCard.svelte +290 -0
- package/dist/components/TestimonialCard/TestimonialCard.svelte.d.ts +14 -0
- package/dist/components/TestimonialCard/index.d.ts +1 -0
- package/dist/components/TestimonialCard/index.js +1 -0
- package/dist/components/Timeline/Timeline.svelte +444 -0
- package/dist/components/Timeline/Timeline.svelte.d.ts +19 -0
- package/dist/components/Timeline/index.d.ts +2 -0
- package/dist/components/Timeline/index.js +1 -0
- package/dist/index.d.ts +24 -2
- package/dist/index.js +16 -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
|
@@ -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';
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* StatsCard - A clickable card for displaying statistics with optional trends
|
|
4
|
+
* Commonly used for dashboard stat summaries and filter toggles
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
type ColorVariant = 'default' | 'primary' | 'success' | 'warning' | 'error';
|
|
8
|
+
|
|
9
|
+
interface TrendData {
|
|
10
|
+
value: number;
|
|
11
|
+
direction: 'up' | 'down';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
/** Label text displayed above the value */
|
|
16
|
+
label: string;
|
|
17
|
+
/** The numeric or string value to display */
|
|
18
|
+
value: number | string;
|
|
19
|
+
/** Color variant for the value text */
|
|
20
|
+
color?: ColorVariant;
|
|
21
|
+
/** Optional icon snippet rendered before the value */
|
|
22
|
+
icon?: import('svelte').Snippet;
|
|
23
|
+
/** Whether this card is in selected/active state */
|
|
24
|
+
selected?: boolean;
|
|
25
|
+
/** Click handler - makes the card interactive */
|
|
26
|
+
onclick?: (event: MouseEvent) => void;
|
|
27
|
+
/** Optional trend indicator showing change direction and percentage */
|
|
28
|
+
trend?: TrendData;
|
|
29
|
+
/** Show loading skeleton instead of content */
|
|
30
|
+
loading?: boolean;
|
|
31
|
+
/** Additional CSS classes */
|
|
32
|
+
class?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
label,
|
|
37
|
+
value,
|
|
38
|
+
color = 'default',
|
|
39
|
+
icon,
|
|
40
|
+
selected = false,
|
|
41
|
+
onclick,
|
|
42
|
+
trend,
|
|
43
|
+
loading = false,
|
|
44
|
+
class: className = ''
|
|
45
|
+
}: Props = $props();
|
|
46
|
+
|
|
47
|
+
const isClickable = $derived(!!onclick);
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
{#if loading}
|
|
51
|
+
<div class="stats-card stats-card--loading {className}">
|
|
52
|
+
<div class="stats-card__skeleton-label"></div>
|
|
53
|
+
<div class="stats-card__skeleton-value"></div>
|
|
54
|
+
</div>
|
|
55
|
+
{:else}
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
class="stats-card stats-card--{color} {className}"
|
|
59
|
+
class:stats-card--selected={selected}
|
|
60
|
+
class:stats-card--clickable={isClickable}
|
|
61
|
+
{onclick}
|
|
62
|
+
disabled={!isClickable}
|
|
63
|
+
>
|
|
64
|
+
<div class="stats-card__label">{label}</div>
|
|
65
|
+
<div class="stats-card__content">
|
|
66
|
+
{#if icon}
|
|
67
|
+
<span class="stats-card__icon">
|
|
68
|
+
{@render icon()}
|
|
69
|
+
</span>
|
|
70
|
+
{/if}
|
|
71
|
+
<span class="stats-card__value">{value}</span>
|
|
72
|
+
{#if trend}
|
|
73
|
+
<span
|
|
74
|
+
class="stats-card__trend"
|
|
75
|
+
class:stats-card__trend--up={trend.direction === 'up'}
|
|
76
|
+
class:stats-card__trend--down={trend.direction === 'down'}
|
|
77
|
+
>
|
|
78
|
+
<svg
|
|
79
|
+
class="stats-card__trend-icon"
|
|
80
|
+
viewBox="0 0 20 20"
|
|
81
|
+
fill="currentColor"
|
|
82
|
+
aria-hidden="true"
|
|
83
|
+
>
|
|
84
|
+
{#if trend.direction === 'up'}
|
|
85
|
+
<path
|
|
86
|
+
fill-rule="evenodd"
|
|
87
|
+
d="M5.293 9.707a1 1 0 010-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 01-1.414 1.414L11 7.414V15a1 1 0 11-2 0V7.414L6.707 9.707a1 1 0 01-1.414 0z"
|
|
88
|
+
clip-rule="evenodd"
|
|
89
|
+
/>
|
|
90
|
+
{:else}
|
|
91
|
+
<path
|
|
92
|
+
fill-rule="evenodd"
|
|
93
|
+
d="M14.707 10.293a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L9 12.586V5a1 1 0 012 0v7.586l2.293-2.293a1 1 0 011.414 0z"
|
|
94
|
+
clip-rule="evenodd"
|
|
95
|
+
/>
|
|
96
|
+
{/if}
|
|
97
|
+
</svg>
|
|
98
|
+
<span class="stats-card__trend-value">{trend.value}%</span>
|
|
99
|
+
</span>
|
|
100
|
+
{/if}
|
|
101
|
+
</div>
|
|
102
|
+
</button>
|
|
103
|
+
{/if}
|
|
104
|
+
|
|
105
|
+
<style>
|
|
106
|
+
.stats-card {
|
|
107
|
+
display: flex;
|
|
108
|
+
flex-direction: column;
|
|
109
|
+
align-items: flex-start;
|
|
110
|
+
padding: var(--space-lg, 1rem);
|
|
111
|
+
background: var(--color-bg-card, #ffffff);
|
|
112
|
+
border-radius: var(--radius-lg, 0.5rem);
|
|
113
|
+
border: 2px solid transparent;
|
|
114
|
+
box-shadow: var(--shadow-sm, 0 1px 2px 0 rgb(0 0 0 / 0.05));
|
|
115
|
+
font-family: inherit;
|
|
116
|
+
text-align: left;
|
|
117
|
+
width: 100%;
|
|
118
|
+
transition:
|
|
119
|
+
box-shadow 0.2s ease,
|
|
120
|
+
border-color 0.2s ease,
|
|
121
|
+
transform 0.1s ease;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/* Reset button styles */
|
|
125
|
+
button.stats-card {
|
|
126
|
+
cursor: default;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
button.stats-card:disabled {
|
|
130
|
+
opacity: 1;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Clickable state */
|
|
134
|
+
.stats-card--clickable {
|
|
135
|
+
cursor: pointer;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.stats-card--clickable:hover {
|
|
139
|
+
box-shadow: var(--shadow-md, 0 4px 6px -1px rgb(0 0 0 / 0.1));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.stats-card--clickable:active {
|
|
143
|
+
transform: scale(0.98);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Selected state */
|
|
147
|
+
.stats-card--selected {
|
|
148
|
+
border-color: var(--color-primary, #3b82f6);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* Label */
|
|
152
|
+
.stats-card__label {
|
|
153
|
+
font-size: var(--text-sm, 0.875rem);
|
|
154
|
+
color: var(--color-text-muted, #6b7280);
|
|
155
|
+
margin-bottom: var(--space-xs, 0.25rem);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* Content wrapper */
|
|
159
|
+
.stats-card__content {
|
|
160
|
+
display: flex;
|
|
161
|
+
align-items: baseline;
|
|
162
|
+
gap: var(--space-sm, 0.5rem);
|
|
163
|
+
flex-wrap: wrap;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* Icon */
|
|
167
|
+
.stats-card__icon {
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
color: inherit;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.stats-card__icon :global(svg) {
|
|
174
|
+
width: 1.5rem;
|
|
175
|
+
height: 1.5rem;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* Value */
|
|
179
|
+
.stats-card__value {
|
|
180
|
+
font-size: var(--text-2xl, 1.5rem);
|
|
181
|
+
font-weight: 700;
|
|
182
|
+
line-height: 1.2;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* Color variants for value */
|
|
186
|
+
.stats-card--default .stats-card__value {
|
|
187
|
+
color: var(--color-text, #1f2937);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.stats-card--primary .stats-card__value {
|
|
191
|
+
color: var(--color-primary, #3b82f6);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.stats-card--success .stats-card__value {
|
|
195
|
+
color: var(--color-success, #22c55e);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.stats-card--warning .stats-card__value {
|
|
199
|
+
color: var(--color-warning, #f59e0b);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.stats-card--error .stats-card__value {
|
|
203
|
+
color: var(--color-error, #ef4444);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* Trend indicator */
|
|
207
|
+
.stats-card__trend {
|
|
208
|
+
display: inline-flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
gap: 0.125rem;
|
|
211
|
+
font-size: var(--text-sm, 0.875rem);
|
|
212
|
+
font-weight: 500;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.stats-card__trend--up {
|
|
216
|
+
color: var(--color-success, #22c55e);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.stats-card__trend--down {
|
|
220
|
+
color: var(--color-error, #ef4444);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.stats-card__trend-icon {
|
|
224
|
+
width: 1rem;
|
|
225
|
+
height: 1rem;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/* Loading skeleton */
|
|
229
|
+
.stats-card--loading {
|
|
230
|
+
pointer-events: none;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.stats-card__skeleton-label,
|
|
234
|
+
.stats-card__skeleton-value {
|
|
235
|
+
background: linear-gradient(
|
|
236
|
+
90deg,
|
|
237
|
+
var(--color-bg-muted, #e5e7eb) 25%,
|
|
238
|
+
var(--color-bg-skeleton-shine, #f3f4f6) 50%,
|
|
239
|
+
var(--color-bg-muted, #e5e7eb) 75%
|
|
240
|
+
);
|
|
241
|
+
background-size: 200% 100%;
|
|
242
|
+
animation: skeleton-shimmer 1.5s ease-in-out infinite;
|
|
243
|
+
border-radius: var(--radius-sm, 0.25rem);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.stats-card__skeleton-label {
|
|
247
|
+
width: 60%;
|
|
248
|
+
height: 0.875rem;
|
|
249
|
+
margin-bottom: var(--space-sm, 0.5rem);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.stats-card__skeleton-value {
|
|
253
|
+
width: 40%;
|
|
254
|
+
height: 1.75rem;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
@keyframes skeleton-shimmer {
|
|
258
|
+
0% {
|
|
259
|
+
background-position: 200% 0;
|
|
260
|
+
}
|
|
261
|
+
100% {
|
|
262
|
+
background-position: -200% 0;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* Dark mode support */
|
|
267
|
+
:global([data-theme='dark']) .stats-card {
|
|
268
|
+
background: var(--color-bg-card-dark, #1f2937);
|
|
269
|
+
box-shadow: var(--shadow-sm-dark, 0 1px 2px 0 rgb(0 0 0 / 0.2));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
:global([data-theme='dark']) .stats-card--clickable:hover {
|
|
273
|
+
box-shadow: var(--shadow-md-dark, 0 4px 6px -1px rgb(0 0 0 / 0.3));
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
:global([data-theme='dark']) .stats-card__label {
|
|
277
|
+
color: var(--color-text-muted-dark, #9ca3af);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
:global([data-theme='dark']) .stats-card--default .stats-card__value {
|
|
281
|
+
color: var(--color-text-dark, #f9fafb);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
:global([data-theme='dark']) .stats-card__skeleton-label,
|
|
285
|
+
:global([data-theme='dark']) .stats-card__skeleton-value {
|
|
286
|
+
background: linear-gradient(
|
|
287
|
+
90deg,
|
|
288
|
+
var(--color-bg-muted-dark, #374151) 25%,
|
|
289
|
+
var(--color-bg-skeleton-shine-dark, #4b5563) 50%,
|
|
290
|
+
var(--color-bg-muted-dark, #374151) 75%
|
|
291
|
+
);
|
|
292
|
+
background-size: 200% 100%;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/* Touch target for mobile - ensure 44px minimum */
|
|
296
|
+
@media (pointer: coarse) {
|
|
297
|
+
.stats-card {
|
|
298
|
+
min-height: 2.75rem;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatsCard - A clickable card for displaying statistics with optional trends
|
|
3
|
+
* Commonly used for dashboard stat summaries and filter toggles
|
|
4
|
+
*/
|
|
5
|
+
type ColorVariant = 'default' | 'primary' | 'success' | 'warning' | 'error';
|
|
6
|
+
interface TrendData {
|
|
7
|
+
value: number;
|
|
8
|
+
direction: 'up' | 'down';
|
|
9
|
+
}
|
|
10
|
+
interface Props {
|
|
11
|
+
/** Label text displayed above the value */
|
|
12
|
+
label: string;
|
|
13
|
+
/** The numeric or string value to display */
|
|
14
|
+
value: number | string;
|
|
15
|
+
/** Color variant for the value text */
|
|
16
|
+
color?: ColorVariant;
|
|
17
|
+
/** Optional icon snippet rendered before the value */
|
|
18
|
+
icon?: import('svelte').Snippet;
|
|
19
|
+
/** Whether this card is in selected/active state */
|
|
20
|
+
selected?: boolean;
|
|
21
|
+
/** Click handler - makes the card interactive */
|
|
22
|
+
onclick?: (event: MouseEvent) => void;
|
|
23
|
+
/** Optional trend indicator showing change direction and percentage */
|
|
24
|
+
trend?: TrendData;
|
|
25
|
+
/** Show loading skeleton instead of content */
|
|
26
|
+
loading?: boolean;
|
|
27
|
+
/** Additional CSS classes */
|
|
28
|
+
class?: string;
|
|
29
|
+
}
|
|
30
|
+
declare const StatsCard: import("svelte").Component<Props, {}, "">;
|
|
31
|
+
type StatsCard = ReturnType<typeof StatsCard>;
|
|
32
|
+
export default StatsCard;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default as StatsCard } from './StatsCard.svelte';
|