@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.
Files changed (72) hide show
  1. package/CLAUDE.md +350 -59
  2. package/README.md +30 -22
  3. package/llms.txt +37 -4
  4. package/package.json +12 -2
  5. package/src/components/docs/CodeBlock.svelte +203 -0
  6. package/src/components/docs/DocSection.svelte +120 -0
  7. package/src/components/docs/PropsTable.svelte +136 -0
  8. package/src/components/docs/SplitPane.svelte +98 -0
  9. package/src/components/docs/index.js +14 -0
  10. package/src/components/feedback/Alert.svelte +234 -0
  11. package/src/components/feedback/EmptyState.svelte +6 -6
  12. package/src/components/feedback/ProgressBar.svelte +8 -8
  13. package/src/components/feedback/Skeleton.svelte +4 -4
  14. package/src/components/feedback/Spinner.svelte +1 -1
  15. package/src/components/feedback/Toast.svelte +137 -173
  16. package/src/components/forms/Checkbox.svelte +10 -10
  17. package/src/components/forms/Dropzone.svelte +14 -14
  18. package/src/components/forms/FileUpload.svelte +16 -16
  19. package/src/components/forms/IconInput.svelte +4 -4
  20. package/src/components/forms/Input.svelte +14 -14
  21. package/src/components/forms/NumberInput.svelte +13 -13
  22. package/src/components/forms/PinInput.svelte +8 -8
  23. package/src/components/forms/Radio.svelte +8 -8
  24. package/src/components/forms/RangeSlider.svelte +12 -12
  25. package/src/components/forms/SearchInput.svelte +10 -10
  26. package/src/components/forms/Select.svelte +156 -158
  27. package/src/components/forms/Switch.svelte +4 -4
  28. package/src/components/forms/Textarea.svelte +9 -9
  29. package/src/components/navigation/Accordion.svelte +1 -1
  30. package/src/components/navigation/AccordionItem.svelte +6 -6
  31. package/src/components/navigation/NavigationContainer.svelte +344 -0
  32. package/src/components/navigation/Sidebar.svelte +334 -0
  33. package/src/components/navigation/SidebarAccountGroup.svelte +495 -0
  34. package/src/components/navigation/SidebarAccountItem.svelte +492 -0
  35. package/src/components/navigation/SidebarGroup.svelte +230 -0
  36. package/src/components/navigation/SidebarGroupSwitcher.svelte +262 -0
  37. package/src/components/navigation/SidebarItem.svelte +210 -0
  38. package/src/components/navigation/SidebarNavigationItem.svelte +470 -0
  39. package/src/components/navigation/SidebarPopover.svelte +145 -0
  40. package/src/components/navigation/SidebarSearch.svelte +236 -0
  41. package/src/components/navigation/SidebarSection.svelte +158 -0
  42. package/src/components/navigation/SidebarToggle.svelte +86 -0
  43. package/src/components/navigation/Tabs.svelte +18 -18
  44. package/src/components/navigation/WorkspaceMenu.svelte +416 -0
  45. package/src/components/navigation/blocks/NavigationAccountGroup.svelte +396 -0
  46. package/src/components/navigation/blocks/NavigationCustomBlock.svelte +74 -0
  47. package/src/components/navigation/blocks/NavigationGroupSwitcher.svelte +277 -0
  48. package/src/components/navigation/blocks/NavigationSearch.svelte +300 -0
  49. package/src/components/navigation/blocks/NavigationSection.svelte +230 -0
  50. package/src/components/navigation/index.js +22 -0
  51. package/src/components/overlays/ConfirmDialog.svelte +18 -18
  52. package/src/components/overlays/Dropdown.svelte +2 -2
  53. package/src/components/overlays/DropdownDivider.svelte +1 -1
  54. package/src/components/overlays/DropdownItem.svelte +5 -5
  55. package/src/components/overlays/Modal.svelte +13 -13
  56. package/src/components/overlays/Popover.svelte +3 -3
  57. package/src/components/primitives/Avatar.svelte +12 -12
  58. package/src/components/primitives/Badge.svelte +7 -7
  59. package/src/components/primitives/Button.svelte +126 -174
  60. package/src/components/primitives/Card.svelte +15 -15
  61. package/src/components/primitives/Divider.svelte +3 -3
  62. package/src/components/primitives/LazyImage.svelte +1 -1
  63. package/src/components/primitives/Link.svelte +2 -2
  64. package/src/components/primitives/Stat.svelte +197 -0
  65. package/src/components/primitives/StatusBadge.svelte +24 -24
  66. package/src/index.js +62 -7
  67. package/src/tokens/colors.css +96 -128
  68. package/src/utils/highlighter.js +124 -0
  69. package/src/utils/index.js +7 -2
  70. package/src/utils/navigation.svelte.js +423 -0
  71. package/src/utils/reactive.svelte.js +126 -37
  72. 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
- buttonStyles({
149
- variant,
150
- size,
151
- fullWidth: fullWidth ? 'true' : 'false',
152
- iconOnly: isIconOnly ? 'true' : 'false',
153
- class: className
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="inline-flex shrink-0">
194
- {@render iconLeft()}
195
- </span>
65
+ <span class="btn-icon">{@render iconLeft()}</span>
196
66
  {/if}
197
-
198
67
  {#if children}
199
- <span class={cn(loading && 'opacity-0')}>
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="inline-flex shrink-0">
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="inline-flex shrink-0">
234
- {@render iconLeft()}
235
- </span>
89
+ <span class="btn-icon">{@render iconLeft()}</span>
236
90
  {/if}
237
-
238
91
  {#if children}
239
- <span class={cn(loading && 'opacity-70')}>
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="inline-flex shrink-0">
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
- @keyframes spin {
254
- to {
255
- transform: rotate(360deg);
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
- .animate-spin {
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-border-muted);
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-border);
49
+ border-color: var(--color-base03);
50
50
  }
51
51
 
52
52
  .card-danger {
53
- border-color: color-mix(in srgb, var(--color-error) 30%, transparent);
54
- background: color-mix(in srgb, var(--color-error) 3%, transparent);
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-error) 40%, transparent);
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-warning) 30%, transparent);
63
- background: color-mix(in srgb, var(--color-warning) 3%, transparent);
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-warning) 40%, transparent);
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-success) 30%, transparent);
72
- background: color-mix(in srgb, var(--color-success) 3%, transparent);
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-success) 40%, transparent);
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-text-strong);
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-error);
90
+ color: var(--color-base08);
91
91
  }
92
92
 
93
93
  .card-warning .card-title {
94
- color: var(--color-warning);
94
+ color: var(--color-base0A);
95
95
  }
96
96
 
97
97
  .card-success .card-title {
98
- color: var(--color-success);
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-text-muted) 30%, transparent);
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-text-muted) 30%, transparent);
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-text-muted);
100
+ color: var(--color-base04);
101
101
  text-transform: uppercase;
102
102
  letter-spacing: 0.05em;
103
103
  white-space: nowrap;
@@ -95,7 +95,7 @@
95
95
 
96
96
  .lazy-loading {
97
97
  opacity: 0;
98
- background: var(--color-surface);
98
+ background: var(--color-base01);
99
99
  }
100
100
 
101
101
  .lazy-loaded {
@@ -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-primary);
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-primary) 80%, transparent);
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>