@mrintel/villain-ui 0.3.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +3490 -1296
  3. package/dist/components/buttons/Button.svelte +27 -33
  4. package/dist/components/buttons/Button.svelte.d.ts +4 -1
  5. package/dist/components/buttons/ButtonGroup.svelte +17 -30
  6. package/dist/components/buttons/FloatingActionButton.svelte +20 -44
  7. package/dist/components/buttons/FloatingActionButton.svelte.d.ts +2 -1
  8. package/dist/components/buttons/IconButton.svelte +23 -53
  9. package/dist/components/buttons/IconButton.svelte.d.ts +2 -1
  10. package/dist/components/buttons/LinkButton.svelte +24 -37
  11. package/dist/components/buttons/LinkButton.svelte.d.ts +4 -1
  12. package/dist/components/buttons/buttonClasses.d.ts +5 -0
  13. package/dist/components/buttons/buttonClasses.js +8 -3
  14. package/dist/components/cards/Card.svelte +54 -46
  15. package/dist/components/cards/Card.svelte.d.ts +9 -2
  16. package/dist/components/cards/Container.svelte +17 -33
  17. package/dist/components/cards/Divider.svelte +36 -52
  18. package/dist/components/cards/Divider.svelte.d.ts +2 -0
  19. package/dist/components/cards/Grid.svelte +55 -44
  20. package/dist/components/cards/Panel.svelte +18 -32
  21. package/dist/components/cards/Panel.svelte.d.ts +2 -1
  22. package/dist/components/cards/SectionHeader.svelte +24 -38
  23. package/dist/components/cards/SectionHeader.svelte.d.ts +1 -0
  24. package/dist/components/data/Avatar.svelte +48 -67
  25. package/dist/components/data/Badge.svelte +45 -32
  26. package/dist/components/data/Badge.svelte.d.ts +7 -1
  27. package/dist/components/data/CalendarGrid.svelte +433 -0
  28. package/dist/components/data/CalendarGrid.svelte.d.ts +25 -0
  29. package/dist/components/data/CalendarGrid.types.d.ts +7 -0
  30. package/dist/components/data/CalendarGrid.types.js +1 -0
  31. package/dist/components/data/CodeBlock.svelte +119 -121
  32. package/dist/components/data/CodeBlock.svelte.d.ts +8 -0
  33. package/dist/components/data/List.svelte +87 -64
  34. package/dist/components/data/List.svelte.d.ts +7 -0
  35. package/dist/components/data/Pagination.svelte +121 -123
  36. package/dist/components/data/Pagination.svelte.d.ts +5 -0
  37. package/dist/components/data/Sparkline.svelte +117 -0
  38. package/dist/components/data/Sparkline.svelte.d.ts +43 -0
  39. package/dist/components/data/Stat.svelte +92 -103
  40. package/dist/components/data/Table.svelte +443 -76
  41. package/dist/components/data/Table.svelte.d.ts +23 -2
  42. package/dist/components/data/Table.types.d.ts +14 -0
  43. package/dist/components/data/Table.types.js +1 -0
  44. package/dist/components/data/Tag.svelte +51 -53
  45. package/dist/components/data/Tag.svelte.d.ts +5 -1
  46. package/dist/components/data/index.d.ts +4 -0
  47. package/dist/components/data/index.js +2 -0
  48. package/dist/components/forms/Checkbox.svelte +39 -51
  49. package/dist/components/forms/Checkbox.svelte.d.ts +3 -1
  50. package/dist/components/forms/DatePicker.svelte +61 -0
  51. package/dist/components/forms/DatePicker.svelte.d.ts +15 -0
  52. package/dist/components/forms/DateTimePicker.svelte +63 -0
  53. package/dist/components/forms/DateTimePicker.svelte.d.ts +16 -0
  54. package/dist/components/forms/FileUpload.svelte +136 -164
  55. package/dist/components/forms/FileUpload.svelte.d.ts +1 -0
  56. package/dist/components/forms/Input.svelte +284 -57
  57. package/dist/components/forms/Input.svelte.d.ts +10 -3
  58. package/dist/components/forms/InputGroup.svelte +7 -7
  59. package/dist/components/forms/RadioGroup.svelte +77 -87
  60. package/dist/components/forms/RadioGroup.svelte.d.ts +3 -1
  61. package/dist/components/forms/RangeSlider.svelte +90 -116
  62. package/dist/components/forms/Select.svelte +106 -71
  63. package/dist/components/forms/Select.svelte.d.ts +3 -1
  64. package/dist/components/forms/Step.svelte +25 -0
  65. package/dist/components/forms/Step.svelte.d.ts +12 -0
  66. package/dist/components/forms/StepContext.d.ts +3 -0
  67. package/dist/components/forms/StepContext.js +5 -0
  68. package/dist/components/forms/Stepper.types.d.ts +37 -0
  69. package/dist/components/forms/Stepper.types.js +1 -0
  70. package/dist/components/forms/StepperForm.svelte +183 -0
  71. package/dist/components/forms/StepperForm.svelte.d.ts +17 -0
  72. package/dist/components/forms/Switch.svelte +44 -56
  73. package/dist/components/forms/Switch.svelte.d.ts +3 -1
  74. package/dist/components/forms/Textarea.svelte +52 -57
  75. package/dist/components/forms/Textarea.svelte.d.ts +3 -1
  76. package/dist/components/forms/TimePicker.svelte +63 -0
  77. package/dist/components/forms/TimePicker.svelte.d.ts +16 -0
  78. package/dist/components/forms/formClasses.d.ts +3 -0
  79. package/dist/components/forms/formClasses.js +3 -0
  80. package/dist/components/forms/index.d.ts +6 -0
  81. package/dist/components/forms/index.js +5 -0
  82. package/dist/components/navigation/Breadcrumbs.svelte +56 -59
  83. package/dist/components/navigation/Breadcrumbs.svelte.d.ts +1 -0
  84. package/dist/components/navigation/ContextMenu.svelte +133 -83
  85. package/dist/components/navigation/ContextMenu.svelte.d.ts +8 -1
  86. package/dist/components/navigation/DropdownMenu.svelte +139 -80
  87. package/dist/components/navigation/DropdownMenu.svelte.d.ts +8 -1
  88. package/dist/components/navigation/Menu.svelte +72 -48
  89. package/dist/components/navigation/Navbar.svelte +111 -32
  90. package/dist/components/navigation/Navbar.svelte.d.ts +6 -0
  91. package/dist/components/navigation/Sidebar.svelte +236 -35
  92. package/dist/components/navigation/Sidebar.svelte.d.ts +2 -0
  93. package/dist/components/navigation/Stepper.svelte +204 -0
  94. package/dist/components/navigation/Stepper.svelte.d.ts +34 -0
  95. package/dist/components/navigation/Tabs.svelte +86 -54
  96. package/dist/components/navigation/Tabs.svelte.d.ts +5 -1
  97. package/dist/components/navigation/index.d.ts +1 -0
  98. package/dist/components/navigation/index.js +1 -0
  99. package/dist/components/overlays/Alert.svelte +81 -99
  100. package/dist/components/overlays/Alert.svelte.d.ts +5 -1
  101. package/dist/components/overlays/CommandPalette.svelte +182 -217
  102. package/dist/components/overlays/Drawer.svelte +158 -167
  103. package/dist/components/overlays/Drawer.svelte.d.ts +3 -1
  104. package/dist/components/overlays/Dropdown.svelte +62 -30
  105. package/dist/components/overlays/Dropdown.svelte.d.ts +2 -0
  106. package/dist/components/overlays/Modal.svelte +125 -130
  107. package/dist/components/overlays/Modal.svelte.d.ts +3 -1
  108. package/dist/components/overlays/Popover.svelte +106 -131
  109. package/dist/components/overlays/ProgressBar.svelte +29 -45
  110. package/dist/components/overlays/SkeletonLoader.svelte +66 -82
  111. package/dist/components/overlays/Spinner.svelte +33 -43
  112. package/dist/components/overlays/Toast.svelte +111 -140
  113. package/dist/components/overlays/Toast.svelte.d.ts +3 -0
  114. package/dist/components/overlays/Tooltip.svelte +94 -115
  115. package/dist/components/overlays/Tooltip.svelte.d.ts +3 -1
  116. package/dist/components/typography/Code.svelte +10 -14
  117. package/dist/components/typography/Heading.svelte +15 -22
  118. package/dist/components/typography/Heading.svelte.d.ts +1 -0
  119. package/dist/components/typography/Text.svelte +21 -24
  120. package/dist/components/typography/Text.svelte.d.ts +2 -1
  121. package/dist/components/utilities/Accordion.svelte +54 -67
  122. package/dist/components/utilities/Accordion.svelte.d.ts +4 -1
  123. package/dist/components/utilities/Carousel.svelte +124 -152
  124. package/dist/components/utilities/Collapse.svelte +46 -60
  125. package/dist/components/utilities/Hero.svelte +42 -0
  126. package/dist/components/utilities/Hero.svelte.d.ts +10 -0
  127. package/dist/components/utilities/Portal.svelte +47 -72
  128. package/dist/components/utilities/ScrollArea.svelte +33 -41
  129. package/dist/components/utilities/SystemConsole.svelte +310 -0
  130. package/dist/components/utilities/SystemConsole.svelte.d.ts +20 -0
  131. package/dist/components/utilities/SystemInterface.svelte +726 -0
  132. package/dist/components/utilities/SystemInterface.svelte.d.ts +19 -0
  133. package/dist/components/utilities/index.d.ts +4 -0
  134. package/dist/components/utilities/index.js +3 -0
  135. package/dist/components/utilities/utilities.types.d.ts +46 -0
  136. package/dist/components/utilities/utilities.types.js +4 -0
  137. package/dist/index.d.ts +57 -5
  138. package/dist/index.js +5 -5
  139. package/dist/theme.css +2889 -218
  140. package/package.json +83 -76
@@ -1,123 +1,121 @@
1
- <script lang="ts">
2
- interface Props {
3
- currentPage: number;
4
- totalPages: number;
5
- maxVisible?: number;
6
- onpagechange?: (page: number) => void;
7
- }
8
-
9
- let { currentPage = $bindable(1), totalPages, maxVisible = 7, onpagechange }: Props = $props();
10
-
11
- const visiblePages = $derived((() => {
12
- const pages: (number | 'ellipsis')[] = [];
13
-
14
- if (totalPages <= maxVisible) {
15
- // Show all pages
16
- for (let i = 1; i <= totalPages; i++) {
17
- pages.push(i);
18
- }
19
- } else {
20
- // Always show first page
21
- pages.push(1);
22
-
23
- const halfVisible = Math.floor((maxVisible - 2) / 2);
24
- let startPage = Math.max(2, currentPage - halfVisible);
25
- let endPage = Math.min(totalPages - 1, currentPage + halfVisible);
26
-
27
- // Adjust range if at boundaries
28
- if (currentPage <= halfVisible + 1) {
29
- endPage = maxVisible - 1;
30
- } else if (currentPage >= totalPages - halfVisible) {
31
- startPage = totalPages - maxVisible + 2;
32
- }
33
-
34
- // Add ellipsis before start if needed
35
- if (startPage > 2) {
36
- pages.push('ellipsis');
37
- }
38
-
39
- // Add middle pages
40
- for (let i = startPage; i <= endPage; i++) {
41
- pages.push(i);
42
- }
43
-
44
- // Add ellipsis after end if needed
45
- if (endPage < totalPages - 1) {
46
- pages.push('ellipsis');
47
- }
48
-
49
- // Always show last page
50
- pages.push(totalPages);
51
- }
52
-
53
- return pages;
54
- })());
55
-
56
- function goToPage(page: number) {
57
- if (page < 1 || page > totalPages || page === currentPage) return;
58
- currentPage = page;
59
- onpagechange?.(page);
60
- }
61
- </script>
62
-
63
- <nav role="navigation" aria-label="Pagination" class="flex items-center gap-2">
64
- <!-- Previous Button -->
65
- <button
66
- class="px-3 py-2 rounded-[var(--radius-md)] border border-[var(--color-border)] bg-transparent transition-all duration-300 font-[var(--font-body)] hover:bg-[rgba(127,61,255,0.1)] hover:border-[var(--color-accent)] disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:border-[var(--color-border)]"
67
- onclick={() => goToPage(currentPage - 1)}
68
- disabled={currentPage === 1}
69
- aria-disabled={currentPage === 1}
70
- >
71
- Previous
72
- </button>
73
-
74
- <!-- Page Numbers -->
75
- {#each visiblePages as page}
76
- {#if page === 'ellipsis'}
77
- <span class="px-3 py-2 text-[var(--color-text-muted)]">...</span>
78
- {:else}
79
- <button
80
- class="px-3 py-2 rounded-[var(--radius-md)] transition-all duration-300 font-[var(--font-body)]"
81
- class:active={page === currentPage}
82
- class:inactive={page !== currentPage}
83
- onclick={() => goToPage(page)}
84
- aria-current={page === currentPage ? 'page' : undefined}
85
- >
86
- {page}
87
- </button>
88
- {/if}
89
- {/each}
90
-
91
- <!-- Next Button -->
92
- <button
93
- class="px-3 py-2 rounded-[var(--radius-md)] border border-[var(--color-border)] bg-transparent transition-all duration-300 font-[var(--font-body)] hover:bg-[rgba(127,61,255,0.1)] hover:border-[var(--color-accent)] disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:border-[var(--color-border)]"
94
- onclick={() => goToPage(currentPage + 1)}
95
- disabled={currentPage === totalPages}
96
- aria-disabled={currentPage === totalPages}
97
- >
98
- Next
99
- </button>
100
- </nav>
101
-
102
- <style>
103
- button.active {
104
- background: var(--color-accent);
105
- color: var(--color-text);
106
- cursor: default;
107
- }
108
-
109
- button.active {
110
- box-shadow: 0 0 20px rgba(127, 61, 255, 0.5);
111
- }
112
-
113
- button.inactive {
114
- background: transparent;
115
- border: 1px solid var(--color-border);
116
- color: var(--color-text);
117
- }
118
-
119
- button.inactive:hover {
120
- background: rgba(127, 61, 255, 0.1);
121
- border-color: var(--color-accent);
122
- }
123
- </style>
1
+ <script lang="ts">"use strict";
2
+ let { currentPage = $bindable(1), totalPages, maxVisible = 7, onPageChange, onpagechange, prevIcon, nextIcon, showLabels } = $props();
3
+ const onPageChangeCallback = $derived(onPageChange ?? onpagechange);
4
+ const displayPrevLabel = $derived(showLabels !== false);
5
+ const displayNextLabel = $derived(showLabels !== false);
6
+ const visiblePages = $derived((() => {
7
+ const pages = [];
8
+ if (totalPages <= maxVisible) {
9
+ // Show all pages
10
+ for (let i = 1; i <= totalPages; i++) {
11
+ pages.push(i);
12
+ }
13
+ }
14
+ else {
15
+ // Always show first page
16
+ pages.push(1);
17
+ const halfVisible = Math.floor((maxVisible - 2) / 2);
18
+ let startPage = Math.max(2, currentPage - halfVisible);
19
+ let endPage = Math.min(totalPages - 1, currentPage + halfVisible);
20
+ // Adjust range if at boundaries
21
+ if (currentPage <= halfVisible + 1) {
22
+ endPage = maxVisible - 1;
23
+ }
24
+ else if (currentPage >= totalPages - halfVisible) {
25
+ startPage = totalPages - maxVisible + 2;
26
+ }
27
+ // Add ellipsis before start if needed
28
+ if (startPage > 2) {
29
+ pages.push('ellipsis');
30
+ }
31
+ // Add middle pages
32
+ for (let i = startPage; i <= endPage; i++) {
33
+ pages.push(i);
34
+ }
35
+ // Add ellipsis after end if needed
36
+ if (endPage < totalPages - 1) {
37
+ pages.push('ellipsis');
38
+ }
39
+ // Always show last page
40
+ pages.push(totalPages);
41
+ }
42
+ return pages;
43
+ })());
44
+ function goToPage(page) {
45
+ if (page < 1 || page > totalPages || page === currentPage)
46
+ return;
47
+ currentPage = page;
48
+ onPageChangeCallback?.(page);
49
+ }
50
+ </script>
51
+
52
+ <nav role="navigation" aria-label="Pagination" class="flex items-center gap-2">
53
+ <!-- Previous Button -->
54
+ <button
55
+ class="flex items-center gap-2 px-3 py-2 rounded-[var(--radius-md)] border border-[var(--color-border)] bg-transparent transition-all duration-300 font-[var(--font-body)] hover:bg-[var(--color-secondary-overlay-10)] hover:border-[var(--color-accent)] disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:border-[var(--color-border)]"
56
+ onclick={() => goToPage(currentPage - 1)}
57
+ disabled={currentPage === 1}
58
+ aria-disabled={currentPage === 1}
59
+ >
60
+ {#if prevIcon}
61
+ <span class="inline-flex items-center justify-center">
62
+ {@render prevIcon()}
63
+ </span>
64
+ {/if}
65
+ {#if displayPrevLabel}Previous{/if}
66
+ </button>
67
+
68
+ <!-- Page Numbers -->
69
+ {#each visiblePages as page}
70
+ {#if page === 'ellipsis'}
71
+ <span class="px-3 py-2 text-[var(--color-text-muted)]">...</span>
72
+ {:else}
73
+ <button
74
+ class="px-3 py-2 rounded-[var(--radius-md)] transition-all duration-300 font-[var(--font-body)]"
75
+ class:active={page === currentPage}
76
+ class:inactive={page !== currentPage}
77
+ onclick={() => goToPage(page)}
78
+ aria-current={page === currentPage ? 'page' : undefined}
79
+ >
80
+ {page}
81
+ </button>
82
+ {/if}
83
+ {/each}
84
+
85
+ <!-- Next Button -->
86
+ <button
87
+ class="flex items-center gap-2 px-3 py-2 rounded-[var(--radius-md)] border border-[var(--color-border)] bg-transparent transition-all duration-300 font-[var(--font-body)] hover:bg-[var(--color-secondary-overlay-10)] hover:border-[var(--color-accent)] disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-transparent disabled:hover:border-[var(--color-border)]"
88
+ onclick={() => goToPage(currentPage + 1)}
89
+ disabled={currentPage === totalPages}
90
+ aria-disabled={currentPage === totalPages}
91
+ >
92
+ {#if displayNextLabel}Next{/if}
93
+ {#if nextIcon}
94
+ <span class="inline-flex items-center justify-center">
95
+ {@render nextIcon()}
96
+ </span>
97
+ {/if}
98
+ </button>
99
+ </nav>
100
+
101
+ <style>
102
+ button.active {
103
+ background: var(--color-accent);
104
+ color: var(--color-text);
105
+ cursor: default;
106
+ }
107
+
108
+ button.active {
109
+ box-shadow: 0 0 20px var(--color-accent-overlay-50);
110
+ }
111
+
112
+ button.inactive {
113
+ background: transparent;
114
+ border: 1px solid var(--color-border);
115
+ color: var(--color-text);
116
+ }
117
+
118
+ button.inactive:hover {
119
+ background: var(--color-accent-overlay-10);
120
+ border-color: var(--color-accent);
121
+ }</style>
@@ -2,7 +2,12 @@ interface Props {
2
2
  currentPage: number;
3
3
  totalPages: number;
4
4
  maxVisible?: number;
5
+ onPageChange?: (page: number) => void;
6
+ /** @deprecated Use onPageChange */
5
7
  onpagechange?: (page: number) => void;
8
+ prevIcon?: import('svelte').Snippet;
9
+ nextIcon?: import('svelte').Snippet;
10
+ showLabels?: boolean;
6
11
  }
7
12
  declare const Pagination: import("svelte").Component<Props, {}, "currentPage">;
8
13
  type Pagination = ReturnType<typeof Pagination>;
@@ -0,0 +1,117 @@
1
+ <script lang="ts">import { onMount } from 'svelte';
2
+ let { data, color = 'var(--color-accent-soft)', height = 40, width = 200, showDots = false, showFill = false, strokeWidth = 2, animationDuration = 500 } = $props();
3
+ let pathElement = $state();
4
+ let mounted = $state(false);
5
+ // Calculate min/max for scaling
6
+ const min = $derived(Math.min(...data));
7
+ const max = $derived(Math.max(...data));
8
+ const range = $derived(max - min || 1); // Avoid division by zero
9
+ // Padding for dots and stroke
10
+ const padding = $derived(showDots ? 4 : strokeWidth);
11
+ // Calculate viewBox dimensions
12
+ const viewBoxWidth = $derived(width);
13
+ const viewBoxHeight = $derived(height);
14
+ // Generate SVG path from data points
15
+ const points = $derived(data.map((value, index) => {
16
+ const x = padding + (index / (data.length - 1)) * (viewBoxWidth - padding * 2);
17
+ const y = viewBoxHeight - padding - ((value - min) / range) * (viewBoxHeight - padding * 2);
18
+ return { x, y, value };
19
+ }));
20
+ const pathData = $derived(points.length > 0
21
+ ? points.reduce((path, point, index) => {
22
+ if (index === 0) {
23
+ return `M ${point.x},${point.y}`;
24
+ }
25
+ return `${path} L ${point.x},${point.y}`;
26
+ }, '')
27
+ : '');
28
+ // Generate fill path (line + close to bottom)
29
+ const fillPathData = $derived(points.length > 0
30
+ ? `${pathData} L ${viewBoxWidth - padding},${viewBoxHeight} L ${padding},${viewBoxHeight} Z`
31
+ : '');
32
+ // Gradient ID for unique identification
33
+ const gradientId = $derived(`sparkline-gradient-${Math.random().toString(36).slice(2, 9)}`);
34
+ // Animation
35
+ onMount(() => {
36
+ if (animationDuration > 0 && pathElement) {
37
+ const length = pathElement.getTotalLength();
38
+ pathElement.style.strokeDasharray = `${length}`;
39
+ pathElement.style.strokeDashoffset = `${length}`;
40
+ // Check for reduced motion preference
41
+ const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
42
+ if (!prefersReducedMotion) {
43
+ // Animate the stroke
44
+ requestAnimationFrame(() => {
45
+ if (pathElement) {
46
+ pathElement.style.transition = `stroke-dashoffset ${animationDuration}ms var(--ease-luxe)`;
47
+ pathElement.style.strokeDashoffset = '0';
48
+ }
49
+ });
50
+ }
51
+ else {
52
+ // Skip animation if reduced motion is preferred
53
+ pathElement.style.strokeDashoffset = '0';
54
+ }
55
+ }
56
+ mounted = true;
57
+ });
58
+ </script>
59
+
60
+ <svg
61
+ {width}
62
+ {height}
63
+ viewBox="0 0 {viewBoxWidth} {viewBoxHeight}"
64
+ class="sparkline"
65
+ role="img"
66
+ aria-label="Trend sparkline visualization"
67
+ style="overflow: visible;"
68
+ >
69
+ <!-- Gradient definition for fill -->
70
+ {#if showFill}
71
+ <defs>
72
+ <linearGradient id={gradientId} x1="0%" y1="0%" x2="0%" y2="100%">
73
+ <stop offset="0%" style="stop-color: {color}; stop-opacity: 0.3;" />
74
+ <stop offset="100%" style="stop-color: {color}; stop-opacity: 0.05;" />
75
+ </linearGradient>
76
+ </defs>
77
+ {/if}
78
+
79
+ <!-- Gradient fill area -->
80
+ {#if showFill && mounted}
81
+ <path
82
+ d={fillPathData}
83
+ fill="url(#{gradientId})"
84
+ style="opacity: 0; animation: fade-in {animationDuration}ms var(--ease-luxe) forwards;"
85
+ />
86
+ {/if}
87
+
88
+ <!-- Line path -->
89
+ <path
90
+ bind:this={pathElement}
91
+ d={pathData}
92
+ fill="none"
93
+ stroke={color}
94
+ stroke-width={strokeWidth}
95
+ stroke-linecap="round"
96
+ stroke-linejoin="round"
97
+ />
98
+
99
+ <!-- Data point dots -->
100
+ {#if showDots && mounted}
101
+ {#each points as point, i}
102
+ <circle
103
+ cx={point.x}
104
+ cy={point.y}
105
+ r="3"
106
+ fill={color}
107
+ style="opacity: 0; animation: fade-in {animationDuration}ms var(--ease-luxe) {i *
108
+ 50}ms forwards;"
109
+ />
110
+ {/each}
111
+ {/if}
112
+ </svg>
113
+
114
+ <style>
115
+ .sparkline {
116
+ display: block;
117
+ }</style>
@@ -0,0 +1,43 @@
1
+ interface Props {
2
+ /**
3
+ * Array of numeric data points to plot
4
+ */
5
+ data: number[];
6
+ /**
7
+ * Line color (defaults to accent primary)
8
+ */
9
+ color?: string;
10
+ /**
11
+ * Chart height in pixels
12
+ * @default 40
13
+ */
14
+ height?: number;
15
+ /**
16
+ * Chart width in pixels
17
+ * @default 200
18
+ */
19
+ width?: number;
20
+ /**
21
+ * Show dots at each data point
22
+ * @default false
23
+ */
24
+ showDots?: boolean;
25
+ /**
26
+ * Show gradient fill below the line
27
+ * @default false
28
+ */
29
+ showFill?: boolean;
30
+ /**
31
+ * Line thickness in pixels
32
+ * @default 2
33
+ */
34
+ strokeWidth?: number;
35
+ /**
36
+ * Animation duration in milliseconds (set to 0 to disable)
37
+ * @default 500
38
+ */
39
+ animationDuration?: number;
40
+ }
41
+ declare const Sparkline: import("svelte").Component<Props, {}, "">;
42
+ type Sparkline = ReturnType<typeof Sparkline>;
43
+ export default Sparkline;
@@ -1,103 +1,92 @@
1
- <script lang="ts">
2
- interface Props {
3
- label: string;
4
- value: string | number;
5
- change?: number;
6
- trend?: 'up' | 'down' | 'neutral';
7
- icon?: import('svelte').Snippet;
8
- description?: string;
9
- }
10
-
11
- let { label, value, change, trend, icon, description }: Props = $props();
12
-
13
- const trendColors = {
14
- up: 'var(--color-success)',
15
- down: 'var(--color-error)',
16
- neutral: 'var(--color-text-muted)'
17
- };
18
-
19
- const trendColor = $derived(trend ? trendColors[trend] : 'var(--color-text-muted)');
20
- const showGlow = $derived(trend === 'up');
21
- </script>
22
-
23
- <div class="glass-panel rounded-[var(--radius-xl)] p-6">
24
- <!-- Top Row: Icon & Label -->
25
- <div class="flex items-start justify-between mb-4">
26
- <div class="flex-1">
27
- <div
28
- class="uppercase tracking-wider"
29
- style="color: var(--color-text-soft); font-family: var(--font-body); font-size: 0.875rem; font-weight: 500;"
30
- >
31
- {label}
32
- </div>
33
- </div>
34
-
35
- {#if icon}
36
- <div class="w-10 h-10" style="color: var(--color-accent-soft);">
37
- {@render icon()}
38
- </div>
39
- {/if}
40
- </div>
41
-
42
- <!-- Value Display -->
43
- <div
44
- class="mb-2"
45
- class:text-glow={showGlow}
46
- style="color: var(--color-text); font-family: var(--font-heading); font-size: 2.5rem; font-weight: 700; line-height: 1.2;"
47
- >
48
- {value}
49
- </div>
50
-
51
- <!-- Change Indicator & Description -->
52
- {#if change !== undefined || description}
53
- <div class="flex items-center gap-2 flex-wrap">
54
- {#if change !== undefined}
55
- <div
56
- class="inline-flex items-center gap-1 font-semibold text-sm"
57
- style="color: {trendColor};"
58
- >
59
- {#if trend === 'up'}
60
- <svg
61
- width="16"
62
- height="16"
63
- viewBox="0 0 16 16"
64
- fill="none"
65
- xmlns="http://www.w3.org/2000/svg"
66
- >
67
- <path
68
- d="M8 12V4M8 4L4 8M8 4L12 8"
69
- stroke="currentColor"
70
- stroke-width="2"
71
- stroke-linecap="round"
72
- stroke-linejoin="round"
73
- />
74
- </svg>
75
- {:else if trend === 'down'}
76
- <svg
77
- width="16"
78
- height="16"
79
- viewBox="0 0 16 16"
80
- fill="none"
81
- xmlns="http://www.w3.org/2000/svg"
82
- >
83
- <path
84
- d="M8 4V12M8 12L4 8M8 12L12 8"
85
- stroke="currentColor"
86
- stroke-width="2"
87
- stroke-linecap="round"
88
- stroke-linejoin="round"
89
- />
90
- </svg>
91
- {/if}
92
- {change > 0 ? '+' : ''}{change}%
93
- </div>
94
- {/if}
95
-
96
- {#if description}
97
- <div class="text-xs" style="color: var(--color-text-muted);">
98
- {description}
99
- </div>
100
- {/if}
101
- </div>
102
- {/if}
103
- </div>
1
+ <script lang="ts">"use strict";
2
+ let { label, value, change, trend, icon, description } = $props();
3
+ const trendColors = {
4
+ up: 'var(--color-success)',
5
+ down: 'var(--color-error)',
6
+ neutral: 'var(--color-text-muted)'
7
+ };
8
+ const trendColor = $derived(trend ? trendColors[trend] : 'var(--color-text-muted)');
9
+ const showGlow = $derived(trend === 'up');
10
+ </script>
11
+
12
+ <div class="panel-spectral rounded-[var(--radius-xl)] p-6">
13
+ <!-- Top Row: Icon & Label -->
14
+ <div class="flex items-start justify-between mb-4">
15
+ <div class="flex-1">
16
+ <div
17
+ class="uppercase tracking-wider"
18
+ style="color: var(--color-text-soft); font-family: var(--font-body); font-size: 0.875rem; font-weight: 500;"
19
+ >
20
+ {label}
21
+ </div>
22
+ </div>
23
+
24
+ {#if icon}
25
+ <div class="w-10 h-10" style="color: var(--color-accent-soft);">
26
+ {@render icon()}
27
+ </div>
28
+ {/if}
29
+ </div>
30
+
31
+ <!-- Value Display -->
32
+ <div
33
+ class="mb-2"
34
+ class:text-glow={showGlow}
35
+ style="color: var(--color-text); font-family: var(--font-heading); font-size: 2.5rem; font-weight: 700; line-height: 1.2;"
36
+ >
37
+ {value}
38
+ </div>
39
+
40
+ <!-- Change Indicator & Description -->
41
+ {#if change !== undefined || description}
42
+ <div class="flex items-center gap-2 flex-wrap">
43
+ {#if change !== undefined}
44
+ <div
45
+ class="inline-flex items-center gap-1 font-semibold text-sm"
46
+ style="color: {trendColor};"
47
+ >
48
+ {#if trend === 'up'}
49
+ <svg
50
+ width="16"
51
+ height="16"
52
+ viewBox="0 0 16 16"
53
+ fill="none"
54
+ xmlns="http://www.w3.org/2000/svg"
55
+ >
56
+ <path
57
+ d="M8 12V4M8 4L4 8M8 4L12 8"
58
+ stroke="currentColor"
59
+ stroke-width="2"
60
+ stroke-linecap="round"
61
+ stroke-linejoin="round"
62
+ />
63
+ </svg>
64
+ {:else if trend === 'down'}
65
+ <svg
66
+ width="16"
67
+ height="16"
68
+ viewBox="0 0 16 16"
69
+ fill="none"
70
+ xmlns="http://www.w3.org/2000/svg"
71
+ >
72
+ <path
73
+ d="M8 4V12M8 12L4 8M8 12L12 8"
74
+ stroke="currentColor"
75
+ stroke-width="2"
76
+ stroke-linecap="round"
77
+ stroke-linejoin="round"
78
+ />
79
+ </svg>
80
+ {/if}
81
+ {change > 0 ? '+' : ''}{change}%
82
+ </div>
83
+ {/if}
84
+
85
+ {#if description}
86
+ <div class="text-xs" style="color: var(--color-text-muted);">
87
+ {description}
88
+ </div>
89
+ {/if}
90
+ </div>
91
+ {/if}
92
+ </div>