@miozu/jera 0.3.0 → 0.4.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/CLAUDE.md +350 -59
- package/README.md +30 -22
- package/llms.txt +37 -4
- package/package.json +12 -2
- package/src/components/docs/CodeBlock.svelte +203 -0
- package/src/components/docs/DocSection.svelte +120 -0
- package/src/components/docs/PropsTable.svelte +136 -0
- package/src/components/docs/SplitPane.svelte +98 -0
- package/src/components/docs/index.js +14 -0
- package/src/components/feedback/Alert.svelte +234 -0
- package/src/components/feedback/EmptyState.svelte +6 -6
- package/src/components/feedback/ProgressBar.svelte +8 -8
- package/src/components/feedback/Skeleton.svelte +4 -4
- package/src/components/feedback/Spinner.svelte +1 -1
- package/src/components/feedback/Toast.svelte +137 -173
- package/src/components/forms/Checkbox.svelte +10 -10
- package/src/components/forms/Dropzone.svelte +14 -14
- package/src/components/forms/FileUpload.svelte +16 -16
- package/src/components/forms/IconInput.svelte +4 -4
- package/src/components/forms/Input.svelte +14 -14
- package/src/components/forms/NumberInput.svelte +13 -13
- package/src/components/forms/PinInput.svelte +8 -8
- package/src/components/forms/Radio.svelte +8 -8
- package/src/components/forms/RangeSlider.svelte +12 -12
- package/src/components/forms/SearchInput.svelte +10 -10
- package/src/components/forms/Select.svelte +156 -158
- package/src/components/forms/Switch.svelte +4 -4
- package/src/components/forms/Textarea.svelte +9 -9
- package/src/components/navigation/Accordion.svelte +1 -1
- package/src/components/navigation/AccordionItem.svelte +6 -6
- package/src/components/navigation/NavigationContainer.svelte +344 -0
- package/src/components/navigation/Sidebar.svelte +334 -0
- package/src/components/navigation/SidebarAccountGroup.svelte +495 -0
- package/src/components/navigation/SidebarAccountItem.svelte +492 -0
- package/src/components/navigation/SidebarGroup.svelte +230 -0
- package/src/components/navigation/SidebarGroupSwitcher.svelte +262 -0
- package/src/components/navigation/SidebarItem.svelte +210 -0
- package/src/components/navigation/SidebarNavigationItem.svelte +470 -0
- package/src/components/navigation/SidebarPopover.svelte +145 -0
- package/src/components/navigation/SidebarSearch.svelte +236 -0
- package/src/components/navigation/SidebarSection.svelte +158 -0
- package/src/components/navigation/SidebarToggle.svelte +86 -0
- package/src/components/navigation/Tabs.svelte +18 -18
- package/src/components/navigation/WorkspaceMenu.svelte +416 -0
- package/src/components/navigation/blocks/NavigationAccountGroup.svelte +396 -0
- package/src/components/navigation/blocks/NavigationCustomBlock.svelte +74 -0
- package/src/components/navigation/blocks/NavigationGroupSwitcher.svelte +277 -0
- package/src/components/navigation/blocks/NavigationSearch.svelte +300 -0
- package/src/components/navigation/blocks/NavigationSection.svelte +230 -0
- package/src/components/navigation/index.js +22 -0
- package/src/components/overlays/ConfirmDialog.svelte +18 -18
- package/src/components/overlays/Dropdown.svelte +2 -2
- package/src/components/overlays/DropdownDivider.svelte +1 -1
- package/src/components/overlays/DropdownItem.svelte +5 -5
- package/src/components/overlays/Modal.svelte +13 -13
- package/src/components/overlays/Popover.svelte +3 -3
- package/src/components/primitives/Avatar.svelte +12 -12
- package/src/components/primitives/Badge.svelte +7 -7
- package/src/components/primitives/Button.svelte +126 -174
- package/src/components/primitives/Card.svelte +15 -15
- package/src/components/primitives/Divider.svelte +3 -3
- package/src/components/primitives/LazyImage.svelte +1 -1
- package/src/components/primitives/Link.svelte +2 -2
- package/src/components/primitives/Stat.svelte +197 -0
- package/src/components/primitives/StatusBadge.svelte +24 -24
- package/src/index.js +62 -7
- package/src/tokens/colors.css +96 -128
- package/src/utils/highlighter.js +124 -0
- package/src/utils/index.js +7 -2
- package/src/utils/navigation.svelte.js +423 -0
- package/src/utils/reactive.svelte.js +126 -37
- package/src/utils/sidebar.svelte.js +211 -0
|
@@ -2,126 +2,16 @@
|
|
|
2
2
|
@component Button
|
|
3
3
|
|
|
4
4
|
A polymorphic button component with variants, sizes, and loading states.
|
|
5
|
-
Demonstrates advanced Svelte 5 patterns:
|
|
6
|
-
|
|
7
|
-
- Polymorphic rendering (as button, a, or custom element)
|
|
8
|
-
- Reactive class composition with cv()
|
|
9
|
-
- Snippets for icon slots
|
|
10
|
-
- $derived for computed properties
|
|
11
|
-
- TypeScript-style prop definitions
|
|
12
5
|
|
|
13
6
|
@example
|
|
14
7
|
<Button>Click me</Button>
|
|
15
8
|
<Button variant="secondary" size="lg">Large Secondary</Button>
|
|
16
9
|
<Button href="/about">Link Button</Button>
|
|
17
10
|
<Button loading>Loading...</Button>
|
|
18
|
-
<Button disabled>Disabled</Button>
|
|
19
|
-
<Button>
|
|
20
|
-
{#snippet iconLeft()}<IconPlus />{/snippet}
|
|
21
|
-
Add Item
|
|
22
|
-
</Button>
|
|
23
11
|
-->
|
|
24
|
-
<script module>
|
|
25
|
-
import { cv } from '../../utils/cn.svelte.js';
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Button style variants using cv() for type-safe composition
|
|
29
|
-
*/
|
|
30
|
-
export const buttonStyles = cv({
|
|
31
|
-
base: [
|
|
32
|
-
'inline-flex items-center justify-center gap-2',
|
|
33
|
-
'font-medium rounded-lg',
|
|
34
|
-
'transition-all duration-150 ease-out',
|
|
35
|
-
'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
|
|
36
|
-
'disabled:opacity-50 disabled:cursor-not-allowed disabled:pointer-events-none',
|
|
37
|
-
'select-none'
|
|
38
|
-
].join(' '),
|
|
39
|
-
|
|
40
|
-
variants: {
|
|
41
|
-
variant: {
|
|
42
|
-
primary: [
|
|
43
|
-
'bg-primary text-white',
|
|
44
|
-
'hover:brightness-110 active:brightness-95',
|
|
45
|
-
'focus-visible:ring-primary/50'
|
|
46
|
-
].join(' '),
|
|
47
|
-
|
|
48
|
-
secondary: [
|
|
49
|
-
'bg-surface-alt text-text-strong border border-border',
|
|
50
|
-
'hover:bg-hover hover:border-muted',
|
|
51
|
-
'focus-visible:ring-border'
|
|
52
|
-
].join(' '),
|
|
53
|
-
|
|
54
|
-
ghost: [
|
|
55
|
-
'bg-transparent text-text',
|
|
56
|
-
'hover:bg-hover',
|
|
57
|
-
'focus-visible:ring-border'
|
|
58
|
-
].join(' '),
|
|
59
|
-
|
|
60
|
-
outline: [
|
|
61
|
-
'bg-transparent text-primary border border-primary/40',
|
|
62
|
-
'hover:bg-primary/5 hover:border-primary',
|
|
63
|
-
'focus-visible:ring-primary/50'
|
|
64
|
-
].join(' '),
|
|
65
|
-
|
|
66
|
-
danger: [
|
|
67
|
-
'bg-error text-white',
|
|
68
|
-
'hover:brightness-110 active:brightness-95',
|
|
69
|
-
'focus-visible:ring-error/50'
|
|
70
|
-
].join(' '),
|
|
71
|
-
|
|
72
|
-
success: [
|
|
73
|
-
'bg-success text-white',
|
|
74
|
-
'hover:brightness-110 active:brightness-95',
|
|
75
|
-
'focus-visible:ring-success/50'
|
|
76
|
-
].join(' ')
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
size: {
|
|
80
|
-
xs: 'h-7 px-2.5 text-xs',
|
|
81
|
-
sm: 'h-8 px-3 text-sm',
|
|
82
|
-
md: 'h-10 px-4 text-sm',
|
|
83
|
-
lg: 'h-12 px-6 text-base',
|
|
84
|
-
xl: 'h-14 px-8 text-lg'
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
fullWidth: {
|
|
88
|
-
true: 'w-full',
|
|
89
|
-
false: ''
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
iconOnly: {
|
|
93
|
-
true: 'aspect-square p-0',
|
|
94
|
-
false: ''
|
|
95
|
-
}
|
|
96
|
-
},
|
|
97
|
-
|
|
98
|
-
compounds: [
|
|
99
|
-
// Icon-only buttons need adjusted padding
|
|
100
|
-
{ condition: { iconOnly: 'true', size: 'xs' }, class: 'w-7' },
|
|
101
|
-
{ condition: { iconOnly: 'true', size: 'sm' }, class: 'w-8' },
|
|
102
|
-
{ condition: { iconOnly: 'true', size: 'md' }, class: 'w-10' },
|
|
103
|
-
{ condition: { iconOnly: 'true', size: 'lg' }, class: 'w-12' },
|
|
104
|
-
{ condition: { iconOnly: 'true', size: 'xl' }, class: 'w-14' }
|
|
105
|
-
],
|
|
106
|
-
|
|
107
|
-
defaults: {
|
|
108
|
-
variant: 'primary',
|
|
109
|
-
size: 'md',
|
|
110
|
-
fullWidth: 'false',
|
|
111
|
-
iconOnly: 'false'
|
|
112
|
-
}
|
|
113
|
-
});
|
|
114
|
-
</script>
|
|
115
|
-
|
|
116
12
|
<script>
|
|
117
13
|
import { cn } from '../../utils/cn.svelte.js';
|
|
118
14
|
|
|
119
|
-
/**
|
|
120
|
-
* @typedef {import('svelte').Snippet} Snippet
|
|
121
|
-
* @typedef {'primary' | 'secondary' | 'ghost' | 'outline' | 'danger' | 'success'} Variant
|
|
122
|
-
* @typedef {'xs' | 'sm' | 'md' | 'lg' | 'xl'} Size
|
|
123
|
-
*/
|
|
124
|
-
|
|
125
15
|
let {
|
|
126
16
|
children,
|
|
127
17
|
iconLeft,
|
|
@@ -138,36 +28,26 @@
|
|
|
138
28
|
...restProps
|
|
139
29
|
} = $props();
|
|
140
30
|
|
|
141
|
-
// Derived state
|
|
142
31
|
const isLink = $derived(!!href);
|
|
143
32
|
const isIconOnly = $derived(!children && (!!iconLeft || !!iconRight));
|
|
144
33
|
const isDisabled = $derived(disabled || loading);
|
|
145
34
|
|
|
146
|
-
// Reactive class composition
|
|
147
35
|
const buttonClass = $derived(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
36
|
+
cn(
|
|
37
|
+
'btn',
|
|
38
|
+
`btn-${variant}`,
|
|
39
|
+
`btn-${size}`,
|
|
40
|
+
fullWidth && 'btn-full',
|
|
41
|
+
isIconOnly && 'btn-icon-only',
|
|
42
|
+
className
|
|
43
|
+
)
|
|
155
44
|
);
|
|
156
45
|
|
|
157
|
-
// Loading spinner
|
|
158
46
|
const spinnerSize = $derived({
|
|
159
|
-
xs: 12,
|
|
160
|
-
sm: 14,
|
|
161
|
-
md: 16,
|
|
162
|
-
lg: 18,
|
|
163
|
-
xl: 20
|
|
47
|
+
xs: 12, sm: 14, md: 16, lg: 18, xl: 20
|
|
164
48
|
}[size]);
|
|
165
49
|
</script>
|
|
166
50
|
|
|
167
|
-
<!--
|
|
168
|
-
Polymorphic rendering: renders as <a> if href provided, otherwise <button>
|
|
169
|
-
Uses Svelte 5's element binding with spread props
|
|
170
|
-
-->
|
|
171
51
|
{#if isLink}
|
|
172
52
|
<a
|
|
173
53
|
{href}
|
|
@@ -177,34 +57,18 @@
|
|
|
177
57
|
{...restProps}
|
|
178
58
|
>
|
|
179
59
|
{#if loading}
|
|
180
|
-
<svg
|
|
181
|
-
class="animate-spin"
|
|
182
|
-
width={spinnerSize}
|
|
183
|
-
height={spinnerSize}
|
|
184
|
-
viewBox="0 0 24 24"
|
|
185
|
-
fill="none"
|
|
186
|
-
stroke="currentColor"
|
|
187
|
-
stroke-width="2"
|
|
188
|
-
>
|
|
60
|
+
<svg class="btn-spinner" width={spinnerSize} height={spinnerSize} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
189
61
|
<circle cx="12" cy="12" r="10" opacity="0.25" />
|
|
190
62
|
<path d="M12 2a10 10 0 0 1 10 10" opacity="0.75" />
|
|
191
63
|
</svg>
|
|
192
64
|
{:else if iconLeft}
|
|
193
|
-
<span class="
|
|
194
|
-
{@render iconLeft()}
|
|
195
|
-
</span>
|
|
65
|
+
<span class="btn-icon">{@render iconLeft()}</span>
|
|
196
66
|
{/if}
|
|
197
|
-
|
|
198
67
|
{#if children}
|
|
199
|
-
<span class={
|
|
200
|
-
{@render children()}
|
|
201
|
-
</span>
|
|
68
|
+
<span class={loading ? 'btn-content-loading' : ''}>{@render children()}</span>
|
|
202
69
|
{/if}
|
|
203
|
-
|
|
204
70
|
{#if iconRight && !loading}
|
|
205
|
-
<span class="
|
|
206
|
-
{@render iconRight()}
|
|
207
|
-
</span>
|
|
71
|
+
<span class="btn-icon">{@render iconRight()}</span>
|
|
208
72
|
{/if}
|
|
209
73
|
</a>
|
|
210
74
|
{:else}
|
|
@@ -217,46 +81,134 @@
|
|
|
217
81
|
{...restProps}
|
|
218
82
|
>
|
|
219
83
|
{#if loading}
|
|
220
|
-
<svg
|
|
221
|
-
class="animate-spin"
|
|
222
|
-
width={spinnerSize}
|
|
223
|
-
height={spinnerSize}
|
|
224
|
-
viewBox="0 0 24 24"
|
|
225
|
-
fill="none"
|
|
226
|
-
stroke="currentColor"
|
|
227
|
-
stroke-width="2"
|
|
228
|
-
>
|
|
84
|
+
<svg class="btn-spinner" width={spinnerSize} height={spinnerSize} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
229
85
|
<circle cx="12" cy="12" r="10" opacity="0.25" />
|
|
230
86
|
<path d="M12 2a10 10 0 0 1 10 10" opacity="0.75" />
|
|
231
87
|
</svg>
|
|
232
88
|
{:else if iconLeft}
|
|
233
|
-
<span class="
|
|
234
|
-
{@render iconLeft()}
|
|
235
|
-
</span>
|
|
89
|
+
<span class="btn-icon">{@render iconLeft()}</span>
|
|
236
90
|
{/if}
|
|
237
|
-
|
|
238
91
|
{#if children}
|
|
239
|
-
<span class={
|
|
240
|
-
{@render children()}
|
|
241
|
-
</span>
|
|
92
|
+
<span class={loading ? 'btn-content-loading' : ''}>{@render children()}</span>
|
|
242
93
|
{/if}
|
|
243
|
-
|
|
244
94
|
{#if iconRight && !loading}
|
|
245
|
-
<span class="
|
|
246
|
-
{@render iconRight()}
|
|
247
|
-
</span>
|
|
95
|
+
<span class="btn-icon">{@render iconRight()}</span>
|
|
248
96
|
{/if}
|
|
249
97
|
</button>
|
|
250
98
|
{/if}
|
|
251
99
|
|
|
252
100
|
<style>
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
101
|
+
/* Base button styles */
|
|
102
|
+
.btn {
|
|
103
|
+
display: inline-flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
gap: 0.5rem;
|
|
107
|
+
font-weight: 500;
|
|
108
|
+
border-radius: var(--radius-lg, 0.5rem);
|
|
109
|
+
border: 1px solid transparent;
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
user-select: none;
|
|
112
|
+
transition: all 150ms ease-out;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.btn:focus-visible {
|
|
116
|
+
outline: none;
|
|
117
|
+
box-shadow: 0 0 0 2px var(--color-base00), 0 0 0 4px var(--color-base0D);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.btn:disabled {
|
|
121
|
+
opacity: 0.5;
|
|
122
|
+
cursor: not-allowed;
|
|
123
|
+
pointer-events: none;
|
|
257
124
|
}
|
|
258
125
|
|
|
259
|
-
|
|
126
|
+
/* Variants */
|
|
127
|
+
.btn-primary {
|
|
128
|
+
background-color: var(--color-base0D);
|
|
129
|
+
color: var(--color-base07);
|
|
130
|
+
}
|
|
131
|
+
.btn-primary:hover {
|
|
132
|
+
filter: brightness(1.1);
|
|
133
|
+
}
|
|
134
|
+
.btn-primary:active {
|
|
135
|
+
filter: brightness(0.95);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.btn-secondary {
|
|
139
|
+
background-color: var(--color-base02);
|
|
140
|
+
color: var(--color-base07);
|
|
141
|
+
border-color: var(--color-base03);
|
|
142
|
+
}
|
|
143
|
+
.btn-secondary:hover {
|
|
144
|
+
background-color: var(--color-base03);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.btn-ghost {
|
|
148
|
+
background-color: transparent;
|
|
149
|
+
color: var(--color-base05);
|
|
150
|
+
}
|
|
151
|
+
.btn-ghost:hover {
|
|
152
|
+
background-color: var(--color-base02);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.btn-outline {
|
|
156
|
+
background-color: transparent;
|
|
157
|
+
color: var(--color-base0D);
|
|
158
|
+
border-color: color-mix(in srgb, var(--color-base0D) 40%, transparent);
|
|
159
|
+
}
|
|
160
|
+
.btn-outline:hover {
|
|
161
|
+
background-color: color-mix(in srgb, var(--color-base0D) 5%, transparent);
|
|
162
|
+
border-color: var(--color-base0D);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.btn-danger {
|
|
166
|
+
background-color: var(--color-base08);
|
|
167
|
+
color: var(--color-base07);
|
|
168
|
+
}
|
|
169
|
+
.btn-danger:hover {
|
|
170
|
+
filter: brightness(1.1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.btn-success {
|
|
174
|
+
background-color: var(--color-base0B);
|
|
175
|
+
color: var(--color-base07);
|
|
176
|
+
}
|
|
177
|
+
.btn-success:hover {
|
|
178
|
+
filter: brightness(1.1);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/* Sizes */
|
|
182
|
+
.btn-xs { height: 1.75rem; padding: 0 0.625rem; font-size: 0.75rem; }
|
|
183
|
+
.btn-sm { height: 2rem; padding: 0 0.75rem; font-size: 0.875rem; }
|
|
184
|
+
.btn-md { height: 2.5rem; padding: 0 1rem; font-size: 0.875rem; }
|
|
185
|
+
.btn-lg { height: 3rem; padding: 0 1.5rem; font-size: 1rem; }
|
|
186
|
+
.btn-xl { height: 3.5rem; padding: 0 2rem; font-size: 1.125rem; }
|
|
187
|
+
|
|
188
|
+
/* Modifiers */
|
|
189
|
+
.btn-full { width: 100%; }
|
|
190
|
+
.btn-icon-only { aspect-ratio: 1; padding: 0; }
|
|
191
|
+
.btn-icon-only.btn-xs { width: 1.75rem; }
|
|
192
|
+
.btn-icon-only.btn-sm { width: 2rem; }
|
|
193
|
+
.btn-icon-only.btn-md { width: 2.5rem; }
|
|
194
|
+
.btn-icon-only.btn-lg { width: 3rem; }
|
|
195
|
+
.btn-icon-only.btn-xl { width: 3.5rem; }
|
|
196
|
+
|
|
197
|
+
/* Icon and spinner */
|
|
198
|
+
.btn-icon {
|
|
199
|
+
display: inline-flex;
|
|
200
|
+
flex-shrink: 0;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.btn-spinner {
|
|
260
204
|
animation: spin 1s linear infinite;
|
|
261
205
|
}
|
|
206
|
+
|
|
207
|
+
.btn-content-loading {
|
|
208
|
+
opacity: 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@keyframes spin {
|
|
212
|
+
to { transform: rotate(360deg); }
|
|
213
|
+
}
|
|
262
214
|
</style>
|
|
@@ -39,63 +39,63 @@
|
|
|
39
39
|
<style>
|
|
40
40
|
.card {
|
|
41
41
|
background: transparent;
|
|
42
|
-
border: 1px solid var(--color-
|
|
42
|
+
border: 1px solid var(--color-base02);
|
|
43
43
|
border-radius: var(--radius-xl);
|
|
44
44
|
padding: var(--space-6);
|
|
45
45
|
transition: border-color 0.2s ease;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
.card:hover {
|
|
49
|
-
border-color: var(--color-
|
|
49
|
+
border-color: var(--color-base03);
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
.card-danger {
|
|
53
|
-
border-color: color-mix(in srgb, var(--color-
|
|
54
|
-
background: color-mix(in srgb, var(--color-
|
|
53
|
+
border-color: color-mix(in srgb, var(--color-base08) 30%, transparent);
|
|
54
|
+
background: color-mix(in srgb, var(--color-base08) 3%, transparent);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
.card-danger:hover {
|
|
58
|
-
border-color: color-mix(in srgb, var(--color-
|
|
58
|
+
border-color: color-mix(in srgb, var(--color-base08) 40%, transparent);
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
.card-warning {
|
|
62
|
-
border-color: color-mix(in srgb, var(--color-
|
|
63
|
-
background: color-mix(in srgb, var(--color-
|
|
62
|
+
border-color: color-mix(in srgb, var(--color-base0A) 30%, transparent);
|
|
63
|
+
background: color-mix(in srgb, var(--color-base0A) 3%, transparent);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
.card-warning:hover {
|
|
67
|
-
border-color: color-mix(in srgb, var(--color-
|
|
67
|
+
border-color: color-mix(in srgb, var(--color-base0A) 40%, transparent);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
.card-success {
|
|
71
|
-
border-color: color-mix(in srgb, var(--color-
|
|
72
|
-
background: color-mix(in srgb, var(--color-
|
|
71
|
+
border-color: color-mix(in srgb, var(--color-base0B) 30%, transparent);
|
|
72
|
+
background: color-mix(in srgb, var(--color-base0B) 3%, transparent);
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
.card-success:hover {
|
|
76
|
-
border-color: color-mix(in srgb, var(--color-
|
|
76
|
+
border-color: color-mix(in srgb, var(--color-base0B) 40%, transparent);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
.card-title {
|
|
80
80
|
margin: 0 0 var(--space-5) 0;
|
|
81
81
|
font-size: var(--text-base);
|
|
82
82
|
font-weight: 500;
|
|
83
|
-
color: var(--color-
|
|
83
|
+
color: var(--color-base07);
|
|
84
84
|
display: flex;
|
|
85
85
|
align-items: center;
|
|
86
86
|
gap: var(--space-2);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
.card-danger .card-title {
|
|
90
|
-
color: var(--color-
|
|
90
|
+
color: var(--color-base08);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
.card-warning .card-title {
|
|
94
|
-
color: var(--color-
|
|
94
|
+
color: var(--color-base0A);
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
.card-success .card-title {
|
|
98
|
-
color: var(--color-
|
|
98
|
+
color: var(--color-base0B);
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
.card-content {
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
|
|
51
51
|
<style>
|
|
52
52
|
.divider {
|
|
53
|
-
background: color-mix(in srgb, var(--color-
|
|
53
|
+
background: color-mix(in srgb, var(--color-base04) 30%, transparent);
|
|
54
54
|
flex-shrink: 0;
|
|
55
55
|
}
|
|
56
56
|
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
|
|
85
85
|
.divider-line {
|
|
86
86
|
flex: 1;
|
|
87
|
-
background: color-mix(in srgb, var(--color-
|
|
87
|
+
background: color-mix(in srgb, var(--color-base04) 30%, transparent);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
.divider-horizontal .divider-line {
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
|
|
98
98
|
.divider-label {
|
|
99
99
|
font-size: 0.75rem;
|
|
100
|
-
color: var(--color-
|
|
100
|
+
color: var(--color-base04);
|
|
101
101
|
text-transform: uppercase;
|
|
102
102
|
letter-spacing: 0.05em;
|
|
103
103
|
white-space: nowrap;
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
gap: 0.375rem;
|
|
99
99
|
font-size: var(--text-xs);
|
|
100
100
|
font-weight: 500;
|
|
101
|
-
color: var(--color-
|
|
101
|
+
color: var(--color-base0D);
|
|
102
102
|
text-decoration: none;
|
|
103
103
|
background: transparent;
|
|
104
104
|
border: none;
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
.link:hover {
|
|
111
|
-
color: color-mix(in srgb, var(--color-
|
|
111
|
+
color: color-mix(in srgb, var(--color-base0D) 80%, transparent);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
.link-icon {
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component Stat
|
|
3
|
+
|
|
4
|
+
A metric display component showing a value and label with optional status indicator.
|
|
5
|
+
|
|
6
|
+
@example Basic
|
|
7
|
+
<Stat value="42" label="Users" />
|
|
8
|
+
|
|
9
|
+
@example With status
|
|
10
|
+
<Stat value="95%" label="Uptime" status="success" />
|
|
11
|
+
|
|
12
|
+
@example With bar
|
|
13
|
+
<Stat value="75" max={100} label="Progress" showBar />
|
|
14
|
+
|
|
15
|
+
@example With unit and secondary value
|
|
16
|
+
<Stat value="12" unit="/15" label="Tasks" status="warning" />
|
|
17
|
+
|
|
18
|
+
@example As link
|
|
19
|
+
<Stat value="8" label="Errors" href="/errors" status="error" />
|
|
20
|
+
-->
|
|
21
|
+
<script>
|
|
22
|
+
let {
|
|
23
|
+
value,
|
|
24
|
+
label,
|
|
25
|
+
unit = '',
|
|
26
|
+
secondary = '',
|
|
27
|
+
status = '',
|
|
28
|
+
size = 'md',
|
|
29
|
+
showBar = false,
|
|
30
|
+
barValue = 0,
|
|
31
|
+
max = 100,
|
|
32
|
+
href = '',
|
|
33
|
+
class: className = '',
|
|
34
|
+
children
|
|
35
|
+
} = $props();
|
|
36
|
+
|
|
37
|
+
const barPercent = $derived(Math.min((barValue || value) / max * 100, 100));
|
|
38
|
+
const Tag = $derived(href ? 'a' : 'div');
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<svelte:element
|
|
42
|
+
this={Tag}
|
|
43
|
+
{href}
|
|
44
|
+
class="stat stat-{size} {status ? `stat-${status}` : ''} {href ? 'stat-link' : ''} {className}"
|
|
45
|
+
>
|
|
46
|
+
{#if status}
|
|
47
|
+
<span class="stat-dot stat-dot-{status}"></span>
|
|
48
|
+
{/if}
|
|
49
|
+
|
|
50
|
+
<div class="stat-content">
|
|
51
|
+
<div class="stat-value">
|
|
52
|
+
{value}{#if unit}<span class="stat-unit">{unit}</span>{/if}
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
{#if secondary}
|
|
56
|
+
<div class="stat-secondary">{secondary}</div>
|
|
57
|
+
{/if}
|
|
58
|
+
|
|
59
|
+
<div class="stat-label">{label}</div>
|
|
60
|
+
|
|
61
|
+
{#if showBar}
|
|
62
|
+
<div class="stat-bar">
|
|
63
|
+
<div
|
|
64
|
+
class="stat-bar-fill {status ? `stat-bar-${status}` : ''}"
|
|
65
|
+
style="width: {barPercent}%"
|
|
66
|
+
></div>
|
|
67
|
+
</div>
|
|
68
|
+
{/if}
|
|
69
|
+
|
|
70
|
+
{#if children}
|
|
71
|
+
<div class="stat-extra">
|
|
72
|
+
{@render children()}
|
|
73
|
+
</div>
|
|
74
|
+
{/if}
|
|
75
|
+
</div>
|
|
76
|
+
</svelte:element>
|
|
77
|
+
|
|
78
|
+
<style>
|
|
79
|
+
.stat {
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
gap: var(--space-2);
|
|
83
|
+
text-decoration: none;
|
|
84
|
+
color: inherit;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.stat-link {
|
|
88
|
+
cursor: pointer;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.stat-link:hover .stat-value {
|
|
92
|
+
color: var(--color-base07);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* Size variants */
|
|
96
|
+
.stat-sm .stat-value {
|
|
97
|
+
font-size: var(--text-lg);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.stat-sm .stat-label {
|
|
101
|
+
font-size: 10px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.stat-md .stat-value {
|
|
105
|
+
font-size: var(--text-2xl);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.stat-md .stat-label {
|
|
109
|
+
font-size: var(--text-xs);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.stat-lg .stat-value {
|
|
113
|
+
font-size: var(--text-3xl);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.stat-lg .stat-label {
|
|
117
|
+
font-size: var(--text-sm);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* Content */
|
|
121
|
+
.stat-content {
|
|
122
|
+
display: flex;
|
|
123
|
+
flex-direction: column;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.stat-value {
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
color: var(--color-base06);
|
|
129
|
+
letter-spacing: -0.02em;
|
|
130
|
+
line-height: 1.2;
|
|
131
|
+
transition: color 0.2s ease;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.stat-unit {
|
|
135
|
+
font-size: 0.6em;
|
|
136
|
+
font-weight: 400;
|
|
137
|
+
color: var(--color-base04);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.stat-secondary {
|
|
141
|
+
font-size: var(--text-sm);
|
|
142
|
+
color: var(--color-base04);
|
|
143
|
+
margin-top: var(--space-1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.stat-label {
|
|
147
|
+
color: var(--color-base04);
|
|
148
|
+
text-transform: uppercase;
|
|
149
|
+
letter-spacing: 0.05em;
|
|
150
|
+
margin-top: var(--space-1);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.stat-extra {
|
|
154
|
+
margin-top: var(--space-2);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/* Status dot */
|
|
158
|
+
.stat-dot {
|
|
159
|
+
width: 6px;
|
|
160
|
+
height: 6px;
|
|
161
|
+
border-radius: 50%;
|
|
162
|
+
flex-shrink: 0;
|
|
163
|
+
position: absolute;
|
|
164
|
+
top: var(--space-3);
|
|
165
|
+
right: var(--space-3);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.stat-dot-success { background: var(--color-base0B); }
|
|
169
|
+
.stat-dot-warning { background: var(--color-base0A); }
|
|
170
|
+
.stat-dot-error { background: var(--color-base08); }
|
|
171
|
+
.stat-dot-info { background: var(--color-base0D); }
|
|
172
|
+
|
|
173
|
+
/* Status value colors */
|
|
174
|
+
.stat-error .stat-value { color: var(--color-base08); }
|
|
175
|
+
.stat-warning .stat-value { color: var(--color-base0A); }
|
|
176
|
+
.stat-success .stat-value { color: var(--color-base0B); }
|
|
177
|
+
|
|
178
|
+
/* Progress bar */
|
|
179
|
+
.stat-bar {
|
|
180
|
+
height: 4px;
|
|
181
|
+
background: var(--color-base02);
|
|
182
|
+
border-radius: var(--radius-full);
|
|
183
|
+
margin-top: var(--space-3);
|
|
184
|
+
overflow: hidden;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.stat-bar-fill {
|
|
188
|
+
height: 100%;
|
|
189
|
+
background: var(--color-base0D);
|
|
190
|
+
border-radius: var(--radius-full);
|
|
191
|
+
transition: width 0.3s ease;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.stat-bar-success { background: var(--color-base0B); }
|
|
195
|
+
.stat-bar-warning { background: var(--color-base0A); }
|
|
196
|
+
.stat-bar-error { background: var(--color-base08); }
|
|
197
|
+
</style>
|