@mrintel/villain-ui 0.2.2 → 0.6.3
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/LICENSE +21 -21
- package/README.md +3490 -1296
- package/dist/components/buttons/Button.svelte +27 -0
- package/dist/components/buttons/Button.svelte.d.ts +14 -0
- package/dist/components/buttons/ButtonGroup.svelte +17 -0
- package/dist/components/buttons/ButtonGroup.svelte.d.ts +8 -0
- package/dist/components/buttons/FloatingActionButton.svelte +20 -0
- package/dist/components/buttons/FloatingActionButton.svelte.d.ts +12 -0
- package/dist/components/buttons/IconButton.svelte +23 -0
- package/dist/components/buttons/IconButton.svelte.d.ts +14 -0
- package/dist/components/buttons/LinkButton.svelte +24 -0
- package/dist/components/buttons/LinkButton.svelte.d.ts +15 -0
- package/dist/components/buttons/buttonClasses.d.ts +15 -0
- package/dist/components/buttons/buttonClasses.js +15 -0
- package/dist/components/buttons/index.d.ts +5 -0
- package/dist/components/buttons/index.js +5 -0
- package/dist/components/cards/Card.svelte +60 -0
- package/dist/components/cards/Card.svelte.d.ts +15 -0
- package/dist/components/cards/Container.svelte +17 -0
- package/dist/components/cards/Container.svelte.d.ts +10 -0
- package/dist/components/cards/Divider.svelte +36 -0
- package/dist/components/cards/Divider.svelte.d.ts +11 -0
- package/dist/components/cards/Grid.svelte +55 -0
- package/dist/components/cards/Grid.svelte.d.ts +10 -0
- package/dist/components/cards/Panel.svelte +18 -0
- package/dist/components/cards/Panel.svelte.d.ts +11 -0
- package/dist/components/cards/SectionHeader.svelte +24 -0
- package/dist/components/cards/SectionHeader.svelte.d.ts +12 -0
- package/dist/components/cards/index.d.ts +6 -0
- package/dist/components/cards/index.js +6 -0
- package/dist/components/data/Avatar.svelte +48 -0
- package/dist/components/data/Avatar.svelte.d.ts +10 -0
- package/dist/components/data/Badge.svelte +45 -0
- package/dist/components/data/Badge.svelte.d.ts +14 -0
- package/dist/components/data/CalendarGrid.svelte +433 -0
- package/dist/components/data/CalendarGrid.svelte.d.ts +25 -0
- package/dist/components/data/CalendarGrid.types.d.ts +7 -0
- package/dist/components/data/CalendarGrid.types.js +1 -0
- package/dist/components/data/CodeBlock.svelte +119 -0
- package/dist/components/data/CodeBlock.svelte.d.ts +40 -0
- package/dist/components/data/List.svelte +87 -0
- package/dist/components/data/List.svelte.d.ts +15 -0
- package/dist/components/data/Pagination.svelte +121 -0
- package/dist/components/data/Pagination.svelte.d.ts +14 -0
- package/dist/components/data/Sparkline.svelte +117 -0
- package/dist/components/data/Sparkline.svelte.d.ts +43 -0
- package/dist/components/data/Stat.svelte +92 -0
- package/dist/components/data/Stat.svelte.d.ts +11 -0
- package/dist/components/data/Table.svelte +443 -0
- package/dist/components/data/Table.svelte.d.ts +30 -0
- package/dist/components/data/Table.types.d.ts +14 -0
- package/dist/components/data/Table.types.js +1 -0
- package/dist/components/data/Tag.svelte +51 -0
- package/dist/components/data/Tag.svelte.d.ts +13 -0
- package/dist/components/data/index.d.ts +12 -0
- package/dist/components/data/index.js +10 -0
- package/dist/components/forms/Checkbox.svelte +39 -0
- package/dist/components/forms/Checkbox.svelte.d.ts +12 -0
- package/dist/components/forms/DatePicker.svelte +61 -0
- package/dist/components/forms/DatePicker.svelte.d.ts +15 -0
- package/dist/components/forms/DateTimePicker.svelte +63 -0
- package/dist/components/forms/DateTimePicker.svelte.d.ts +16 -0
- package/dist/components/forms/FileUpload.svelte +136 -0
- package/dist/components/forms/FileUpload.svelte.d.ts +23 -0
- package/dist/components/forms/Input.svelte +282 -0
- package/dist/components/forms/Input.svelte.d.ts +19 -0
- package/dist/components/forms/InputGroup.svelte +7 -0
- package/dist/components/forms/InputGroup.svelte.d.ts +20 -0
- package/dist/components/forms/RadioGroup.svelte +77 -0
- package/dist/components/forms/RadioGroup.svelte.d.ts +17 -0
- package/dist/components/forms/RangeSlider.svelte +90 -0
- package/dist/components/forms/RangeSlider.svelte.d.ts +14 -0
- package/dist/components/forms/Select.svelte +106 -0
- package/dist/components/forms/Select.svelte.d.ts +18 -0
- package/dist/components/forms/Switch.svelte +44 -0
- package/dist/components/forms/Switch.svelte.d.ts +12 -0
- package/dist/components/forms/Textarea.svelte +52 -0
- package/dist/components/forms/Textarea.svelte.d.ts +15 -0
- package/dist/components/forms/TimePicker.svelte +63 -0
- package/dist/components/forms/TimePicker.svelte.d.ts +16 -0
- package/dist/components/forms/formClasses.d.ts +3 -0
- package/dist/components/forms/formClasses.js +3 -0
- package/dist/components/forms/index.d.ts +12 -0
- package/dist/components/forms/index.js +12 -0
- package/dist/components/navigation/Breadcrumbs.svelte +56 -0
- package/dist/components/navigation/Breadcrumbs.svelte.d.ts +15 -0
- package/dist/components/navigation/ContextMenu.svelte +133 -0
- package/dist/components/navigation/ContextMenu.svelte.d.ts +18 -0
- package/dist/components/navigation/DropdownMenu.svelte +139 -0
- package/dist/components/navigation/DropdownMenu.svelte.d.ts +17 -0
- package/dist/components/navigation/Menu.svelte +72 -0
- package/dist/components/navigation/Menu.svelte.d.ts +15 -0
- package/dist/components/navigation/Navbar.svelte +111 -0
- package/dist/components/navigation/Navbar.svelte.d.ts +15 -0
- package/dist/components/navigation/Sidebar.svelte +236 -0
- package/dist/components/navigation/Sidebar.svelte.d.ts +12 -0
- package/dist/components/navigation/Tabs.svelte +86 -0
- package/dist/components/navigation/Tabs.svelte.d.ts +19 -0
- package/dist/components/navigation/index.d.ts +7 -0
- package/dist/components/navigation/index.js +7 -0
- package/dist/components/overlays/Alert.svelte +81 -0
- package/dist/components/overlays/Alert.svelte.d.ts +15 -0
- package/dist/components/overlays/CommandPalette.svelte +182 -0
- package/dist/components/overlays/CommandPalette.svelte.d.ts +16 -0
- package/dist/components/overlays/Drawer.svelte +158 -0
- package/dist/components/overlays/Drawer.svelte.d.ts +16 -0
- package/dist/components/overlays/Dropdown.svelte +62 -0
- package/dist/components/overlays/Dropdown.svelte.d.ts +11 -0
- package/dist/components/overlays/Modal.svelte +125 -0
- package/dist/components/overlays/Modal.svelte.d.ts +15 -0
- package/dist/components/overlays/Popover.svelte +106 -0
- package/dist/components/overlays/Popover.svelte.d.ts +11 -0
- package/dist/components/overlays/ProgressBar.svelte +29 -0
- package/dist/components/overlays/ProgressBar.svelte.d.ts +10 -0
- package/dist/components/overlays/SkeletonLoader.svelte +66 -0
- package/dist/components/overlays/SkeletonLoader.svelte.d.ts +9 -0
- package/dist/components/overlays/Spinner.svelte +33 -0
- package/dist/components/overlays/Spinner.svelte.d.ts +7 -0
- package/dist/components/overlays/Toast.svelte +111 -0
- package/dist/components/overlays/Toast.svelte.d.ts +16 -0
- package/dist/components/overlays/Tooltip.svelte +94 -0
- package/dist/components/overlays/Tooltip.svelte.d.ts +12 -0
- package/dist/components/overlays/index.d.ts +11 -0
- package/dist/components/overlays/index.js +11 -0
- package/dist/components/typography/Code.svelte +10 -0
- package/dist/components/typography/Code.svelte.d.ts +6 -0
- package/dist/components/typography/Heading.svelte +15 -0
- package/dist/components/typography/Heading.svelte.d.ts +10 -0
- package/dist/components/typography/Text.svelte +21 -0
- package/dist/components/typography/Text.svelte.d.ts +10 -0
- package/dist/components/typography/index.d.ts +3 -0
- package/dist/components/typography/index.js +3 -0
- package/dist/components/utilities/Accordion.svelte +54 -0
- package/dist/components/utilities/Accordion.svelte.d.ts +17 -0
- package/dist/components/utilities/Carousel.svelte +124 -0
- package/dist/components/utilities/Carousel.svelte.d.ts +16 -0
- package/dist/components/utilities/Collapse.svelte +46 -0
- package/dist/components/utilities/Collapse.svelte.d.ts +10 -0
- package/dist/components/utilities/Hero.svelte +42 -0
- package/dist/components/utilities/Hero.svelte.d.ts +10 -0
- package/dist/components/utilities/Portal.svelte +47 -0
- package/dist/components/utilities/Portal.svelte.d.ts +21 -0
- package/dist/components/utilities/ScrollArea.svelte +33 -0
- package/dist/components/utilities/ScrollArea.svelte.d.ts +8 -0
- package/dist/components/utilities/SystemConsole.svelte +310 -0
- package/dist/components/utilities/SystemConsole.svelte.d.ts +20 -0
- package/dist/components/utilities/SystemInterface.svelte +726 -0
- package/dist/components/utilities/SystemInterface.svelte.d.ts +19 -0
- package/dist/components/utilities/index.d.ts +9 -0
- package/dist/components/utilities/index.js +8 -0
- package/dist/components/utilities/utilities.types.d.ts +46 -0
- package/dist/components/utilities/utilities.types.js +4 -0
- package/dist/index.d.ts +60 -175
- package/dist/index.js +24 -4560
- package/dist/lib/internal/id.d.ts +12 -0
- package/dist/lib/internal/id.js +15 -0
- package/dist/theme.css +2821 -0
- package/package.json +83 -75
- package/dist/index.css +0 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script lang="ts">let { variant = 'plain', hoverable = false, items, children } = $props();
|
|
2
|
+
const containerClasses = $derived(variant === 'bordered'
|
|
3
|
+
? 'panel-raised rounded-[var(--radius-lg)] border border-[var(--color-border)]'
|
|
4
|
+
: '');
|
|
5
|
+
const listClasses = $derived(`list-none ${variant} ${hoverable ? 'hoverable' : ''}`);
|
|
6
|
+
export {};
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
{#if variant === 'bordered'}
|
|
10
|
+
<div class={containerClasses}>
|
|
11
|
+
<ul class={listClasses}>
|
|
12
|
+
{#if items}
|
|
13
|
+
{#each items as item (item.id)}
|
|
14
|
+
<li class="flex items-center gap-2">
|
|
15
|
+
{#if item.icon}
|
|
16
|
+
<span class="inline-flex items-center justify-center">
|
|
17
|
+
{@render item.icon()}
|
|
18
|
+
</span>
|
|
19
|
+
{/if}
|
|
20
|
+
{#if typeof item.content === 'string'}
|
|
21
|
+
{item.content}
|
|
22
|
+
{:else}
|
|
23
|
+
{@render item.content()}
|
|
24
|
+
{/if}
|
|
25
|
+
</li>
|
|
26
|
+
{/each}
|
|
27
|
+
{:else}
|
|
28
|
+
{@render children?.()}
|
|
29
|
+
{/if}
|
|
30
|
+
</ul>
|
|
31
|
+
</div>
|
|
32
|
+
{:else}
|
|
33
|
+
<ul class={listClasses}>
|
|
34
|
+
{#if items}
|
|
35
|
+
{#each items as item (item.id)}
|
|
36
|
+
<li class="flex items-center gap-2">
|
|
37
|
+
{#if item.icon}
|
|
38
|
+
<span class="inline-flex items-center justify-center">
|
|
39
|
+
{@render item.icon()}
|
|
40
|
+
</span>
|
|
41
|
+
{/if}
|
|
42
|
+
{#if typeof item.content === 'string'}
|
|
43
|
+
{item.content}
|
|
44
|
+
{:else}
|
|
45
|
+
{@render item.content()}
|
|
46
|
+
{/if}
|
|
47
|
+
</li>
|
|
48
|
+
{/each}
|
|
49
|
+
{:else}
|
|
50
|
+
{@render children?.()}
|
|
51
|
+
{/if}
|
|
52
|
+
</ul>
|
|
53
|
+
{/if}
|
|
54
|
+
|
|
55
|
+
<style>
|
|
56
|
+
ul {
|
|
57
|
+
margin: 0;
|
|
58
|
+
padding: 0;
|
|
59
|
+
color: var(--color-text);
|
|
60
|
+
font-family: var(--font-body);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
:global(ul.plain > li) {
|
|
64
|
+
padding: 0.5rem 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
:global(ul.bordered > li) {
|
|
68
|
+
padding: 0.75rem 1rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
:global(ul.divided > li) {
|
|
72
|
+
padding: 0.75rem 1rem;
|
|
73
|
+
border-bottom: 1px solid var(--color-border);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
:global(ul.divided > li:last-child) {
|
|
77
|
+
border-bottom: none;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
:global(ul.hoverable > li) {
|
|
81
|
+
transition: all var(--ease-luxe);
|
|
82
|
+
cursor: pointer;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
:global(ul.hoverable > li:hover) {
|
|
86
|
+
background: var(--color-accent-overlay-5);
|
|
87
|
+
}</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
interface ListItem {
|
|
3
|
+
id: string;
|
|
4
|
+
content: string | Snippet;
|
|
5
|
+
icon?: Snippet;
|
|
6
|
+
}
|
|
7
|
+
interface Props {
|
|
8
|
+
variant?: 'plain' | 'bordered' | 'divided';
|
|
9
|
+
hoverable?: boolean;
|
|
10
|
+
items?: ListItem[];
|
|
11
|
+
children?: import('svelte').Snippet;
|
|
12
|
+
}
|
|
13
|
+
declare const List: import("svelte").Component<Props, {}, "">;
|
|
14
|
+
type List = ReturnType<typeof List>;
|
|
15
|
+
export default List;
|
|
@@ -0,0 +1,121 @@
|
|
|
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>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
currentPage: number;
|
|
3
|
+
totalPages: number;
|
|
4
|
+
maxVisible?: number;
|
|
5
|
+
onPageChange?: (page: number) => void;
|
|
6
|
+
/** @deprecated Use onPageChange */
|
|
7
|
+
onpagechange?: (page: number) => void;
|
|
8
|
+
prevIcon?: import('svelte').Snippet;
|
|
9
|
+
nextIcon?: import('svelte').Snippet;
|
|
10
|
+
showLabels?: boolean;
|
|
11
|
+
}
|
|
12
|
+
declare const Pagination: import("svelte").Component<Props, {}, "currentPage">;
|
|
13
|
+
type Pagination = ReturnType<typeof Pagination>;
|
|
14
|
+
export default 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;
|
|
@@ -0,0 +1,92 @@
|
|
|
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>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
label: string;
|
|
3
|
+
value: string | number;
|
|
4
|
+
change?: number;
|
|
5
|
+
trend?: 'up' | 'down' | 'neutral';
|
|
6
|
+
icon?: import('svelte').Snippet;
|
|
7
|
+
description?: string;
|
|
8
|
+
}
|
|
9
|
+
declare const Stat: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type Stat = ReturnType<typeof Stat>;
|
|
11
|
+
export default Stat;
|