@mrintel/villain-ui 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/buttons/Button.svelte +33 -0
- package/dist/components/buttons/Button.svelte.d.ts +11 -0
- package/dist/components/buttons/ButtonGroup.svelte +30 -0
- package/dist/components/buttons/ButtonGroup.svelte.d.ts +8 -0
- package/dist/components/buttons/FloatingActionButton.svelte +44 -0
- package/dist/components/buttons/FloatingActionButton.svelte.d.ts +11 -0
- package/dist/components/buttons/IconButton.svelte +53 -0
- package/dist/components/buttons/IconButton.svelte.d.ts +13 -0
- package/dist/components/buttons/LinkButton.svelte +37 -0
- package/dist/components/buttons/LinkButton.svelte.d.ts +12 -0
- package/dist/components/buttons/buttonClasses.d.ts +10 -0
- package/dist/components/buttons/buttonClasses.js +10 -0
- package/dist/components/buttons/index.d.ts +5 -0
- package/dist/components/buttons/index.js +5 -0
- package/dist/components/cards/Card.svelte +46 -0
- package/dist/components/cards/Card.svelte.d.ts +11 -0
- package/dist/components/cards/Container.svelte +33 -0
- package/dist/components/cards/Container.svelte.d.ts +10 -0
- package/dist/components/cards/Divider.svelte +52 -0
- package/dist/components/cards/Divider.svelte.d.ts +9 -0
- package/dist/components/cards/Grid.svelte +44 -0
- package/dist/components/cards/Grid.svelte.d.ts +10 -0
- package/dist/components/cards/Panel.svelte +32 -0
- package/dist/components/cards/Panel.svelte.d.ts +10 -0
- package/dist/components/cards/SectionHeader.svelte +38 -0
- package/dist/components/cards/SectionHeader.svelte.d.ts +11 -0
- package/dist/components/cards/index.d.ts +6 -0
- package/dist/components/cards/index.js +6 -0
- package/dist/components/data/Avatar.svelte +67 -0
- package/dist/components/data/Avatar.svelte.d.ts +10 -0
- package/dist/components/data/Badge.svelte +32 -0
- package/dist/components/data/Badge.svelte.d.ts +8 -0
- package/dist/components/data/CodeBlock.svelte +121 -0
- package/dist/components/data/CodeBlock.svelte.d.ts +32 -0
- package/dist/components/data/List.svelte +64 -0
- package/dist/components/data/List.svelte.d.ts +8 -0
- package/dist/components/data/Pagination.svelte +123 -0
- package/dist/components/data/Pagination.svelte.d.ts +9 -0
- package/dist/components/data/Stat.svelte +103 -0
- package/dist/components/data/Stat.svelte.d.ts +11 -0
- package/dist/components/data/Table.svelte +76 -0
- package/dist/components/data/Table.svelte.d.ts +9 -0
- package/dist/components/data/Tag.svelte +53 -0
- package/dist/components/data/Tag.svelte.d.ts +9 -0
- package/dist/components/data/index.d.ts +8 -0
- package/dist/components/data/index.js +8 -0
- package/dist/components/forms/Checkbox.svelte +51 -0
- package/dist/components/forms/Checkbox.svelte.d.ts +10 -0
- package/dist/components/forms/FileUpload.svelte +164 -0
- package/dist/components/forms/FileUpload.svelte.d.ts +22 -0
- package/dist/components/forms/Input.svelte +57 -0
- package/dist/components/forms/Input.svelte.d.ts +13 -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 +87 -0
- package/dist/components/forms/RadioGroup.svelte.d.ts +15 -0
- package/dist/components/forms/RangeSlider.svelte +116 -0
- package/dist/components/forms/RangeSlider.svelte.d.ts +14 -0
- package/dist/components/forms/Select.svelte +71 -0
- package/dist/components/forms/Select.svelte.d.ts +16 -0
- package/dist/components/forms/Switch.svelte +56 -0
- package/dist/components/forms/Switch.svelte.d.ts +10 -0
- package/dist/components/forms/Textarea.svelte +57 -0
- package/dist/components/forms/Textarea.svelte.d.ts +13 -0
- package/dist/components/forms/index.d.ts +9 -0
- package/dist/components/forms/index.js +9 -0
- package/dist/components/navigation/Breadcrumbs.svelte +59 -0
- package/dist/components/navigation/Breadcrumbs.svelte.d.ts +14 -0
- package/dist/components/navigation/ContextMenu.svelte +83 -0
- package/dist/components/navigation/ContextMenu.svelte.d.ts +11 -0
- package/dist/components/navigation/DropdownMenu.svelte +80 -0
- package/dist/components/navigation/DropdownMenu.svelte.d.ts +10 -0
- package/dist/components/navigation/Menu.svelte +48 -0
- package/dist/components/navigation/Menu.svelte.d.ts +15 -0
- package/dist/components/navigation/Navbar.svelte +32 -0
- package/dist/components/navigation/Navbar.svelte.d.ts +9 -0
- package/dist/components/navigation/Sidebar.svelte +35 -0
- package/dist/components/navigation/Sidebar.svelte.d.ts +10 -0
- package/dist/components/navigation/Tabs.svelte +54 -0
- package/dist/components/navigation/Tabs.svelte.d.ts +15 -0
- package/dist/components/navigation/index.d.ts +7 -0
- package/dist/components/navigation/index.js +7 -0
- package/dist/components/overlays/Alert.svelte +99 -0
- package/dist/components/overlays/Alert.svelte.d.ts +11 -0
- package/dist/components/overlays/CommandPalette.svelte +217 -0
- package/dist/components/overlays/CommandPalette.svelte.d.ts +16 -0
- package/dist/components/overlays/Drawer.svelte +167 -0
- package/dist/components/overlays/Drawer.svelte.d.ts +14 -0
- package/dist/components/overlays/Dropdown.svelte +30 -0
- package/dist/components/overlays/Dropdown.svelte.d.ts +9 -0
- package/dist/components/overlays/Modal.svelte +130 -0
- package/dist/components/overlays/Modal.svelte.d.ts +13 -0
- package/dist/components/overlays/Popover.svelte +131 -0
- package/dist/components/overlays/Popover.svelte.d.ts +11 -0
- package/dist/components/overlays/ProgressBar.svelte +45 -0
- package/dist/components/overlays/ProgressBar.svelte.d.ts +10 -0
- package/dist/components/overlays/SkeletonLoader.svelte +82 -0
- package/dist/components/overlays/SkeletonLoader.svelte.d.ts +9 -0
- package/dist/components/overlays/Spinner.svelte +43 -0
- package/dist/components/overlays/Spinner.svelte.d.ts +7 -0
- package/dist/components/overlays/Toast.svelte +140 -0
- package/dist/components/overlays/Toast.svelte.d.ts +13 -0
- package/dist/components/overlays/Tooltip.svelte +115 -0
- package/dist/components/overlays/Tooltip.svelte.d.ts +10 -0
- package/dist/components/overlays/index.d.ts +11 -0
- package/dist/components/overlays/index.js +11 -0
- package/dist/components/typography/Code.svelte +14 -0
- package/dist/components/typography/Code.svelte.d.ts +6 -0
- package/dist/components/typography/Heading.svelte +22 -0
- package/dist/components/typography/Heading.svelte.d.ts +9 -0
- package/dist/components/typography/Text.svelte +24 -0
- package/dist/components/typography/Text.svelte.d.ts +9 -0
- package/dist/components/typography/index.d.ts +3 -0
- package/dist/components/typography/index.js +3 -0
- package/dist/components/utilities/Accordion.svelte +67 -0
- package/dist/components/utilities/Accordion.svelte.d.ts +14 -0
- package/dist/components/utilities/Carousel.svelte +152 -0
- package/dist/components/utilities/Carousel.svelte.d.ts +16 -0
- package/dist/components/utilities/Collapse.svelte +60 -0
- package/dist/components/utilities/Collapse.svelte.d.ts +10 -0
- package/dist/components/utilities/Portal.svelte +72 -0
- package/dist/components/utilities/Portal.svelte.d.ts +21 -0
- package/dist/components/utilities/ScrollArea.svelte +41 -0
- package/dist/components/utilities/ScrollArea.svelte.d.ts +8 -0
- package/dist/components/utilities/index.d.ts +5 -0
- package/dist/components/utilities/index.js +5 -0
- package/dist/index.d.ts +15 -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 +218 -0
- package/package.json +6 -5
- package/dist/index.css +0 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
src?: string;
|
|
4
|
+
alt: string;
|
|
5
|
+
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
6
|
+
fallback?: string;
|
|
7
|
+
glow?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
let { src, alt, size = 'md', fallback, glow = false }: Props = $props();
|
|
11
|
+
|
|
12
|
+
let imageLoaded = $state(!!src);
|
|
13
|
+
let imageError = $state(false);
|
|
14
|
+
|
|
15
|
+
const sizeClasses = {
|
|
16
|
+
sm: 'w-8 h-8',
|
|
17
|
+
md: 'w-12 h-12',
|
|
18
|
+
lg: 'w-16 h-16',
|
|
19
|
+
xl: 'w-24 h-24'
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const fontSizes = {
|
|
23
|
+
sm: '0.75rem',
|
|
24
|
+
md: '1rem',
|
|
25
|
+
lg: '1.25rem',
|
|
26
|
+
xl: '1.5rem'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const classes = $derived(
|
|
30
|
+
`${sizeClasses[size]} rounded-full overflow-hidden inline-flex items-center justify-center transition-all duration-300 hover:scale-105 ${glow ? 'border-2 border-[var(--color-accent)] accent-glow' : ''}`
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const fallbackText = $derived(
|
|
34
|
+
fallback || alt.charAt(0).toUpperCase()
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
function handleImageError() {
|
|
38
|
+
imageError = true;
|
|
39
|
+
imageLoaded = false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function handleImageLoad() {
|
|
43
|
+
imageLoaded = true;
|
|
44
|
+
imageError = false;
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
{#if src && !imageError}
|
|
49
|
+
<div class={classes}>
|
|
50
|
+
<img
|
|
51
|
+
{src}
|
|
52
|
+
{alt}
|
|
53
|
+
class="w-full h-full object-cover"
|
|
54
|
+
onerror={handleImageError}
|
|
55
|
+
onload={handleImageLoad}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
{:else}
|
|
59
|
+
<div
|
|
60
|
+
class={classes}
|
|
61
|
+
style="background: linear-gradient(135deg, var(--color-accent) 0%, rgba(127, 61, 255, 0.7) 100%); color: var(--color-text); font-weight: 600; text-transform: uppercase; font-size: {fontSizes[size]};"
|
|
62
|
+
role="img"
|
|
63
|
+
aria-label={alt}
|
|
64
|
+
>
|
|
65
|
+
{fallbackText}
|
|
66
|
+
</div>
|
|
67
|
+
{/if}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
src?: string;
|
|
3
|
+
alt: string;
|
|
4
|
+
size?: 'sm' | 'md' | 'lg' | 'xl';
|
|
5
|
+
fallback?: string;
|
|
6
|
+
glow?: boolean;
|
|
7
|
+
}
|
|
8
|
+
declare const Avatar: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type Avatar = ReturnType<typeof Avatar>;
|
|
10
|
+
export default Avatar;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant?: 'default' | 'success' | 'warning' | 'error' | 'accent';
|
|
4
|
+
size?: 'sm' | 'md';
|
|
5
|
+
children?: import('svelte').Snippet;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let { variant = 'default', size = 'md', children }: Props = $props();
|
|
9
|
+
|
|
10
|
+
const variantClasses = {
|
|
11
|
+
default: 'bg-[var(--color-base-3)] text-[var(--color-text-soft)] border-[var(--color-base-3)]',
|
|
12
|
+
success:
|
|
13
|
+
'bg-[rgba(0,232,151,0.15)] text-[var(--color-success)] border-[var(--color-success)]',
|
|
14
|
+
warning:
|
|
15
|
+
'bg-[rgba(255,200,97,0.15)] text-[var(--color-warning)] border-[var(--color-warning)]',
|
|
16
|
+
error: 'bg-[rgba(255,74,106,0.15)] text-[var(--color-error)] border-[var(--color-error)]',
|
|
17
|
+
accent: 'bg-[rgba(127,61,255,0.15)] text-[var(--color-accent-soft)] border-[var(--color-accent)] accent-glow'
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const sizeClasses = {
|
|
21
|
+
sm: 'px-2 py-0.5 text-xs',
|
|
22
|
+
md: 'px-2.5 py-1 text-sm'
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const classes = $derived(
|
|
26
|
+
`inline-flex items-center justify-center rounded-[var(--radius-pill)] border font-[var(--font-body)] font-medium transition-all duration-300 ${variantClasses[variant]} ${sizeClasses[size]}`
|
|
27
|
+
);
|
|
28
|
+
</script>
|
|
29
|
+
|
|
30
|
+
<span class={classes}>
|
|
31
|
+
{@render children?.()}
|
|
32
|
+
</span>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
variant?: 'default' | 'success' | 'warning' | 'error' | 'accent';
|
|
3
|
+
size?: 'sm' | 'md';
|
|
4
|
+
children?: import('svelte').Snippet;
|
|
5
|
+
}
|
|
6
|
+
declare const Badge: import("svelte").Component<Props, {}, "">;
|
|
7
|
+
type Badge = ReturnType<typeof Badge>;
|
|
8
|
+
export default Badge;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Props for the CodeBlock component.
|
|
4
|
+
*
|
|
5
|
+
* This is a presentational component that provides layout and styling for syntax-highlighted code.
|
|
6
|
+
* Consumers are responsible for providing pre-highlighted HTML via the default slot and ensuring
|
|
7
|
+
* the content is properly sanitized to prevent XSS attacks.
|
|
8
|
+
*/
|
|
9
|
+
interface Props {
|
|
10
|
+
/**
|
|
11
|
+
* Optional filename to display in the header.
|
|
12
|
+
*/
|
|
13
|
+
filename?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Whether to show line numbers in the gutter.
|
|
16
|
+
*/
|
|
17
|
+
showLineNumbers?: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Total number of lines in the code. Required when showLineNumbers is true.
|
|
20
|
+
*/
|
|
21
|
+
lineCount?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Array of line numbers (1-indexed) to highlight in the gutter.
|
|
24
|
+
*/
|
|
25
|
+
highlightLines?: number[];
|
|
26
|
+
/**
|
|
27
|
+
* Slot for pre-highlighted code HTML.
|
|
28
|
+
*/
|
|
29
|
+
children?: import('svelte').Snippet;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let {
|
|
33
|
+
filename,
|
|
34
|
+
showLineNumbers = false,
|
|
35
|
+
lineCount = 0,
|
|
36
|
+
highlightLines = [],
|
|
37
|
+
children
|
|
38
|
+
}: Props = $props();
|
|
39
|
+
|
|
40
|
+
// Development-only validation
|
|
41
|
+
</script>
|
|
42
|
+
|
|
43
|
+
<div class="glass-panel rounded-lg overflow-hidden">
|
|
44
|
+
{#if filename}
|
|
45
|
+
<div
|
|
46
|
+
class="px-4 py-2 border-b border-border"
|
|
47
|
+
style="background: var(--color-base-2); color: var(--color-text-soft); font-family: var(--font-mono); font-size: 0.875rem;"
|
|
48
|
+
>
|
|
49
|
+
{filename}
|
|
50
|
+
</div>
|
|
51
|
+
{/if}
|
|
52
|
+
|
|
53
|
+
<div
|
|
54
|
+
class="p-4 overflow-x-auto"
|
|
55
|
+
style="background: var(--color-base-1); font-family: var(--font-mono); font-size: 0.875rem; line-height: 1.6;"
|
|
56
|
+
>
|
|
57
|
+
{#if showLineNumbers}
|
|
58
|
+
<div class="flex">
|
|
59
|
+
<div
|
|
60
|
+
class="pr-4 border-r border-border select-none"
|
|
61
|
+
style="color: var(--color-text-muted);"
|
|
62
|
+
>
|
|
63
|
+
{#each Array.from({ length: lineCount }, (_, i) => i + 1) as lineNum}
|
|
64
|
+
<div
|
|
65
|
+
class:highlighted={highlightLines.includes(lineNum)}
|
|
66
|
+
style={highlightLines.includes(lineNum)
|
|
67
|
+
? 'background: rgba(127, 61, 255, 0.1);'
|
|
68
|
+
: ''}
|
|
69
|
+
>
|
|
70
|
+
{lineNum}
|
|
71
|
+
</div>
|
|
72
|
+
{/each}
|
|
73
|
+
</div>
|
|
74
|
+
<div class="pl-4 flex-1">
|
|
75
|
+
{@render children?.()}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
{:else}
|
|
79
|
+
<div>
|
|
80
|
+
{@render children?.()}
|
|
81
|
+
</div>
|
|
82
|
+
{/if}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<style>
|
|
87
|
+
/* Custom scrollbar styling */
|
|
88
|
+
div::-webkit-scrollbar {
|
|
89
|
+
height: 8px;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
div::-webkit-scrollbar-track {
|
|
93
|
+
background: var(--color-base-1);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
div::-webkit-scrollbar-thumb {
|
|
97
|
+
background: var(--color-accent);
|
|
98
|
+
border-radius: var(--radius-sm);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
div::-webkit-scrollbar-thumb:hover {
|
|
102
|
+
background: var(--color-accent-soft);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Highlight gutter numbers */
|
|
106
|
+
.highlighted {
|
|
107
|
+
background: rgba(127, 61, 255, 0.1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/*
|
|
111
|
+
* Consumer-provided line highlighting:
|
|
112
|
+
* Consumers should apply the `.line` class to each code line element
|
|
113
|
+
* and the `.highlighted` class to lines that should be highlighted.
|
|
114
|
+
* This ensures consistent styling with the component's luxury aesthetic.
|
|
115
|
+
*/
|
|
116
|
+
:global(.line.highlighted) {
|
|
117
|
+
background: rgba(127, 61, 255, 0.1);
|
|
118
|
+
display: inline-block;
|
|
119
|
+
width: 100%;
|
|
120
|
+
}
|
|
121
|
+
</style>
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Props for the CodeBlock component.
|
|
3
|
+
*
|
|
4
|
+
* This is a presentational component that provides layout and styling for syntax-highlighted code.
|
|
5
|
+
* Consumers are responsible for providing pre-highlighted HTML via the default slot and ensuring
|
|
6
|
+
* the content is properly sanitized to prevent XSS attacks.
|
|
7
|
+
*/
|
|
8
|
+
interface Props {
|
|
9
|
+
/**
|
|
10
|
+
* Optional filename to display in the header.
|
|
11
|
+
*/
|
|
12
|
+
filename?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Whether to show line numbers in the gutter.
|
|
15
|
+
*/
|
|
16
|
+
showLineNumbers?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Total number of lines in the code. Required when showLineNumbers is true.
|
|
19
|
+
*/
|
|
20
|
+
lineCount?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Array of line numbers (1-indexed) to highlight in the gutter.
|
|
23
|
+
*/
|
|
24
|
+
highlightLines?: number[];
|
|
25
|
+
/**
|
|
26
|
+
* Slot for pre-highlighted code HTML.
|
|
27
|
+
*/
|
|
28
|
+
children?: import('svelte').Snippet;
|
|
29
|
+
}
|
|
30
|
+
declare const CodeBlock: import("svelte").Component<Props, {}, "">;
|
|
31
|
+
type CodeBlock = ReturnType<typeof CodeBlock>;
|
|
32
|
+
export default CodeBlock;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
variant?: 'plain' | 'bordered' | 'divided';
|
|
4
|
+
hoverable?: boolean;
|
|
5
|
+
children?: import('svelte').Snippet;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let { variant = 'plain', hoverable = false, children }: Props = $props();
|
|
9
|
+
|
|
10
|
+
const containerClasses = $derived(
|
|
11
|
+
variant === 'bordered'
|
|
12
|
+
? 'glass-panel rounded-[var(--radius-lg)] border border-[var(--color-border)]'
|
|
13
|
+
: ''
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const listClasses = $derived(`list-none ${variant} ${hoverable ? 'hoverable' : ''}`);
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
{#if variant === 'bordered'}
|
|
20
|
+
<div class={containerClasses}>
|
|
21
|
+
<ul class={listClasses}>
|
|
22
|
+
{@render children?.()}
|
|
23
|
+
</ul>
|
|
24
|
+
</div>
|
|
25
|
+
{:else}
|
|
26
|
+
<ul class={listClasses}>
|
|
27
|
+
{@render children?.()}
|
|
28
|
+
</ul>
|
|
29
|
+
{/if}
|
|
30
|
+
|
|
31
|
+
<style>
|
|
32
|
+
ul {
|
|
33
|
+
margin: 0;
|
|
34
|
+
padding: 0;
|
|
35
|
+
color: var(--color-text);
|
|
36
|
+
font-family: var(--font-body);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
:global(ul.plain > li) {
|
|
40
|
+
padding: 0.5rem 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
:global(ul.bordered > li) {
|
|
44
|
+
padding: 0.75rem 1rem;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
:global(ul.divided > li) {
|
|
48
|
+
padding: 0.75rem 1rem;
|
|
49
|
+
border-bottom: 1px solid var(--color-border);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
:global(ul.divided > li:last-child) {
|
|
53
|
+
border-bottom: none;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
:global(ul.hoverable > li) {
|
|
57
|
+
transition: all var(--ease-luxe);
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
:global(ul.hoverable > li:hover) {
|
|
62
|
+
background: rgba(127, 61, 255, 0.05);
|
|
63
|
+
}
|
|
64
|
+
</style>
|
|
@@ -0,0 +1,123 @@
|
|
|
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>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
currentPage: number;
|
|
3
|
+
totalPages: number;
|
|
4
|
+
maxVisible?: number;
|
|
5
|
+
onpagechange?: (page: number) => void;
|
|
6
|
+
}
|
|
7
|
+
declare const Pagination: import("svelte").Component<Props, {}, "currentPage">;
|
|
8
|
+
type Pagination = ReturnType<typeof Pagination>;
|
|
9
|
+
export default Pagination;
|
|
@@ -0,0 +1,103 @@
|
|
|
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>
|
|
@@ -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;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
striped?: boolean;
|
|
4
|
+
hoverable?: boolean;
|
|
5
|
+
compact?: boolean;
|
|
6
|
+
children?: import('svelte').Snippet;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { striped = false, hoverable = true, compact = false, children }: Props = $props();
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<div class="glass-panel rounded-[var(--radius-xl)] overflow-hidden">
|
|
13
|
+
<table class="mrdv-table w-full" class:striped class:hoverable class:compact>
|
|
14
|
+
{@render children?.()}
|
|
15
|
+
</table>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<style>
|
|
19
|
+
.mrdv-table :global(thead) {
|
|
20
|
+
background: var(--color-base-2);
|
|
21
|
+
border-bottom: 2px solid var(--color-border-strong);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.mrdv-table :global(thead th) {
|
|
25
|
+
color: var(--color-text);
|
|
26
|
+
font-weight: 600;
|
|
27
|
+
text-transform: uppercase;
|
|
28
|
+
letter-spacing: 0.05em;
|
|
29
|
+
text-align: left;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.mrdv-table.compact :global(th),
|
|
33
|
+
.mrdv-table.compact :global(td) {
|
|
34
|
+
padding: 0.5rem 1rem;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.mrdv-table:not(.compact) :global(th),
|
|
38
|
+
.mrdv-table:not(.compact) :global(td) {
|
|
39
|
+
padding: 1rem 1.5rem;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.mrdv-table :global(tbody) {
|
|
43
|
+
position: relative;
|
|
44
|
+
z-index: 1;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.mrdv-table :global(tbody tr) {
|
|
48
|
+
border-bottom: 1px solid var(--color-border);
|
|
49
|
+
transition: all var(--ease-luxe);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.mrdv-table.hoverable :global(tbody tr:hover) {
|
|
53
|
+
background: rgba(127, 61, 255, 0.05);
|
|
54
|
+
transform: translateY(-1px);
|
|
55
|
+
box-shadow:
|
|
56
|
+
0 2px 8px rgba(0, 0, 0, 0.2),
|
|
57
|
+
var(--shadow-accent-glow);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.mrdv-table.striped :global(tbody tr:nth-child(even)) {
|
|
61
|
+
background: rgba(255, 255, 255, 0.02);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.mrdv-table.striped.hoverable :global(tbody tr:nth-child(even):hover) {
|
|
65
|
+
background: rgba(127, 61, 255, 0.05);
|
|
66
|
+
transform: translateY(-1px);
|
|
67
|
+
box-shadow:
|
|
68
|
+
0 2px 8px rgba(0, 0, 0, 0.2),
|
|
69
|
+
var(--shadow-accent-glow);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.mrdv-table :global(th),
|
|
73
|
+
.mrdv-table :global(td) {
|
|
74
|
+
text-align: left;
|
|
75
|
+
}
|
|
76
|
+
</style>
|