@mrintel/villain-ui 0.7.8 → 0.8.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.d.ts +1 -1
- package/dist/components/buttons/IconButton.svelte.d.ts +1 -1
- package/dist/components/buttons/LinkButton.svelte.d.ts +1 -1
- package/dist/components/buttons/buttonClasses.d.ts +2 -0
- package/dist/components/buttons/buttonClasses.js +2 -1
- package/dist/components/cards/SectionHeader.svelte +8 -3
- package/dist/components/cards/SectionHeader.svelte.d.ts +1 -0
- package/dist/components/data/Data.types.d.ts +10 -0
- package/dist/components/data/EmptyState.svelte +42 -0
- package/dist/components/data/EmptyState.svelte.d.ts +13 -0
- package/dist/components/data/Stat.svelte +30 -2
- package/dist/components/data/Stat.svelte.d.ts +9 -0
- package/dist/components/data/WeekHeatmap.svelte +150 -0
- package/dist/components/data/WeekHeatmap.svelte.d.ts +47 -0
- package/dist/components/data/index.d.ts +3 -1
- package/dist/components/data/index.js +2 -0
- package/dist/components/forms/Checkbox.svelte +27 -21
- package/dist/components/forms/Checkbox.svelte.d.ts +2 -0
- package/dist/components/forms/DatePicker.svelte +7 -2
- package/dist/components/forms/DatePicker.svelte.d.ts +1 -0
- package/dist/components/forms/DateTimePicker.svelte +7 -2
- package/dist/components/forms/DateTimePicker.svelte.d.ts +1 -0
- package/dist/components/forms/FileUpload.svelte +7 -2
- package/dist/components/forms/FileUpload.svelte.d.ts +2 -0
- package/dist/components/forms/Input.svelte +95 -10
- package/dist/components/forms/Input.svelte.d.ts +8 -0
- package/dist/components/forms/RadioGroup.svelte +35 -25
- package/dist/components/forms/RadioGroup.svelte.d.ts +2 -0
- package/dist/components/forms/RangeSlider.svelte +6 -2
- package/dist/components/forms/RangeSlider.svelte.d.ts +2 -0
- package/dist/components/forms/Select.svelte +39 -30
- package/dist/components/forms/Select.svelte.d.ts +1 -0
- package/dist/components/forms/SelectMenu.svelte +7 -2
- package/dist/components/forms/SelectMenu.svelte.d.ts +1 -0
- package/dist/components/forms/Switch.svelte +31 -23
- package/dist/components/forms/Switch.svelte.d.ts +2 -0
- package/dist/components/forms/Textarea.svelte +28 -19
- package/dist/components/forms/Textarea.svelte.d.ts +1 -0
- package/dist/components/forms/TimePicker.svelte +7 -2
- package/dist/components/forms/TimePicker.svelte.d.ts +1 -0
- package/dist/components/overlays/ToastHost.svelte +19 -0
- package/dist/components/overlays/ToastHost.svelte.d.ts +8 -0
- package/dist/components/overlays/index.d.ts +3 -0
- package/dist/components/overlays/index.js +2 -0
- package/dist/components/overlays/toast.store.d.ts +32 -0
- package/dist/components/overlays/toast.store.js +34 -0
- package/dist/components/typography/Heading.svelte +3 -3
- package/dist/components/typography/Heading.svelte.d.ts +1 -0
- package/dist/components/typography/Text.svelte +2 -2
- package/dist/components/typography/Text.svelte.d.ts +1 -0
- package/dist/components/wizard/StepForm.svelte +22 -1
- package/dist/components/wizard/StepForm.svelte.d.ts +8 -1
- package/dist/components/wizard/step.service.js +89 -21
- package/dist/components/wizard/step.types.d.ts +32 -0
- package/dist/index.d.ts +7 -4
- package/dist/index.js +3 -3
- package/dist/theme.css +125 -0
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export const variantClasses = {
|
|
2
2
|
primary: 'bg-[var(--color-accent)] text-[var(--color-text)] accent-glow hover-lift',
|
|
3
3
|
secondary: 'bg-transparent text-[var(--color-text)] border border-[var(--color-border-strong)] hover:border-[var(--color-accent)] hover-lift',
|
|
4
|
-
ghost: 'bg-transparent text-[var(--color-text)] hover:bg-[var(--color-base-2)] hover-lift'
|
|
4
|
+
ghost: 'bg-transparent text-[var(--color-text)] hover:bg-[var(--color-base-2)] hover-lift',
|
|
5
|
+
danger: 'bg-[var(--color-error)] text-[var(--color-text)] shadow-[0_0_20px_var(--color-error-overlay-30)] hover-lift'
|
|
5
6
|
};
|
|
6
7
|
export const sizeClasses = {
|
|
7
8
|
sm: 'px-3 py-1.5 text-sm font-medium',
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
<script lang="ts">let { title, level = 2, glow = true, subtitle, align = 'left', children } = $props();
|
|
1
|
+
<script lang="ts">let { variant = 'default', title, level = 2, glow = true, subtitle, align = 'left', children } = $props();
|
|
2
|
+
const variantClasses = {
|
|
3
|
+
default: '',
|
|
4
|
+
accent: 'text-accent',
|
|
5
|
+
gradient: 'text-gradient'
|
|
6
|
+
};
|
|
2
7
|
const headingTag = $derived(`h${level}`);
|
|
3
|
-
const
|
|
8
|
+
const baseClasses = $derived(`transition-all duration-300 ${glow ? 'text-glow' : ''} ${variantClasses[variant]}`);
|
|
4
9
|
const alignClasses = $derived(`text-${align}`);
|
|
5
|
-
const headingClasses = $derived(`font-[var(--font-heading)] text-[length:var(--text-h${level}-size)] font-[number:var(--text-h${level}-weight)] leading-[var(--text-h${level}-line-height)] text-[var(--color-text)] ${
|
|
10
|
+
const headingClasses = $derived(`font-[var(--font-heading)] text-[length:var(--text-h${level}-size)] font-[number:var(--text-h${level}-weight)] leading-[var(--text-h${level}-line-height)] text-[var(--color-text)] ${baseClasses} ${alignClasses}`);
|
|
6
11
|
const subtitleClasses = $derived(`mt-2 font-[var(--font-body)] text-[length:var(--text-body-size)] text-[var(--color-text-soft)] ${alignClasses}`);
|
|
7
12
|
export {};
|
|
8
13
|
</script>
|
|
@@ -25,3 +25,13 @@ export interface SelectionState {
|
|
|
25
25
|
allSelected: boolean;
|
|
26
26
|
someSelected: boolean;
|
|
27
27
|
}
|
|
28
|
+
export interface WeekHeatmapCell {
|
|
29
|
+
/** Matrix row index (0 = Sunday, matching the SDK PeakTimesHeatmap contract) */
|
|
30
|
+
day: number;
|
|
31
|
+
/** Hour of day, 0–23 */
|
|
32
|
+
hour: number;
|
|
33
|
+
/** Raw matrix value */
|
|
34
|
+
value: number;
|
|
35
|
+
/** 0–100, after normalization */
|
|
36
|
+
intensity: number;
|
|
37
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script lang="ts">"use strict";
|
|
2
|
+
let { title, message, icon, action, size = 'md', class: className = '' } = $props();
|
|
3
|
+
const sizeClasses = {
|
|
4
|
+
sm: 'py-6 gap-2',
|
|
5
|
+
md: 'py-12 gap-3',
|
|
6
|
+
lg: 'py-20 gap-4'
|
|
7
|
+
};
|
|
8
|
+
const iconSizeClasses = {
|
|
9
|
+
sm: 'text-3xl',
|
|
10
|
+
md: 'text-5xl',
|
|
11
|
+
lg: 'text-6xl'
|
|
12
|
+
};
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<div class="flex flex-col items-center justify-center text-center {sizeClasses[size]} {className}">
|
|
16
|
+
{#if icon}
|
|
17
|
+
<div class="{iconSizeClasses[size]} text-[var(--color-text-muted)]" aria-hidden="true">
|
|
18
|
+
{@render icon()}
|
|
19
|
+
</div>
|
|
20
|
+
{/if}
|
|
21
|
+
|
|
22
|
+
{#if title}
|
|
23
|
+
<p
|
|
24
|
+
class="font-semibold"
|
|
25
|
+
style="color: var(--color-text); font-family: var(--font-heading); font-size: var(--text-h5-size);"
|
|
26
|
+
>
|
|
27
|
+
{title}
|
|
28
|
+
</p>
|
|
29
|
+
{/if}
|
|
30
|
+
|
|
31
|
+
{#if message}
|
|
32
|
+
<p class="max-w-sm text-sm" style="color: var(--color-text-muted); font-family: var(--font-body);">
|
|
33
|
+
{message}
|
|
34
|
+
</p>
|
|
35
|
+
{/if}
|
|
36
|
+
|
|
37
|
+
{#if action}
|
|
38
|
+
<div class="mt-2">
|
|
39
|
+
{@render action()}
|
|
40
|
+
</div>
|
|
41
|
+
{/if}
|
|
42
|
+
</div>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
title?: string;
|
|
3
|
+
message?: string;
|
|
4
|
+
/** Icon or illustration shown above the title. */
|
|
5
|
+
icon?: import('svelte').Snippet;
|
|
6
|
+
/** Call-to-action area (typically a Button or LinkButton). */
|
|
7
|
+
action?: import('svelte').Snippet;
|
|
8
|
+
size?: 'sm' | 'md' | 'lg';
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
declare const EmptyState: import("svelte").Component<Props, {}, "">;
|
|
12
|
+
type EmptyState = ReturnType<typeof EmptyState>;
|
|
13
|
+
export default EmptyState;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">"use strict";
|
|
2
|
-
let { label, value, change, trend, icon, description } = $props();
|
|
2
|
+
let { label, value, change, trend, icon, description, countUp = false, countUpDuration = 800, format } = $props();
|
|
3
3
|
const trendColors = {
|
|
4
4
|
up: 'var(--color-success)',
|
|
5
5
|
down: 'var(--color-error)',
|
|
@@ -7,6 +7,34 @@ const trendColors = {
|
|
|
7
7
|
};
|
|
8
8
|
const trendColor = $derived(trend ? trendColors[trend] : 'var(--color-text-muted)');
|
|
9
9
|
const showGlow = $derived(trend === 'up');
|
|
10
|
+
// Count-up: animate toward the current numeric value; re-runs when value changes.
|
|
11
|
+
let animatedValue = $state(0);
|
|
12
|
+
$effect(() => {
|
|
13
|
+
if (!countUp || typeof value !== 'number')
|
|
14
|
+
return;
|
|
15
|
+
const target = value;
|
|
16
|
+
const reduced = typeof window !== 'undefined' &&
|
|
17
|
+
window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
18
|
+
if (reduced) {
|
|
19
|
+
animatedValue = target;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
const from = animatedValue;
|
|
23
|
+
const start = performance.now();
|
|
24
|
+
let raf = 0;
|
|
25
|
+
const tick = (t) => {
|
|
26
|
+
const p = Math.min(1, (t - start) / countUpDuration);
|
|
27
|
+
const eased = 1 - Math.pow(1 - p, 3);
|
|
28
|
+
animatedValue = Math.round(from + (target - from) * eased);
|
|
29
|
+
if (p < 1)
|
|
30
|
+
raf = requestAnimationFrame(tick);
|
|
31
|
+
};
|
|
32
|
+
raf = requestAnimationFrame(tick);
|
|
33
|
+
return () => cancelAnimationFrame(raf);
|
|
34
|
+
});
|
|
35
|
+
const displayValue = $derived(countUp && typeof value === 'number'
|
|
36
|
+
? (format ? format(animatedValue) : animatedValue)
|
|
37
|
+
: value);
|
|
10
38
|
</script>
|
|
11
39
|
|
|
12
40
|
<div class="panel-spectral rounded-[var(--radius-xl)] p-6">
|
|
@@ -34,7 +62,7 @@ const showGlow = $derived(trend === 'up');
|
|
|
34
62
|
class:text-glow={showGlow}
|
|
35
63
|
style="color: var(--color-text); font-family: var(--font-heading); font-size: 2.5rem; font-weight: 700; line-height: 1.2;"
|
|
36
64
|
>
|
|
37
|
-
{
|
|
65
|
+
{displayValue}
|
|
38
66
|
</div>
|
|
39
67
|
|
|
40
68
|
<!-- Change Indicator & Description -->
|
|
@@ -5,6 +5,15 @@ interface Props {
|
|
|
5
5
|
trend?: 'up' | 'down' | 'neutral';
|
|
6
6
|
icon?: import('svelte').Snippet;
|
|
7
7
|
description?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Animate numeric values counting up from 0 on mount (easeOutCubic).
|
|
10
|
+
* Ignored for string values; disabled under prefers-reduced-motion.
|
|
11
|
+
*/
|
|
12
|
+
countUp?: boolean;
|
|
13
|
+
/** Count-up duration in ms. @default 800 */
|
|
14
|
+
countUpDuration?: number;
|
|
15
|
+
/** Formatter for the displayed number while counting (e.g. toLocaleString). */
|
|
16
|
+
format?: (value: number) => string;
|
|
8
17
|
}
|
|
9
18
|
declare const Stat: import("svelte").Component<Props, {}, "">;
|
|
10
19
|
type Stat = ReturnType<typeof Stat>;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
<script lang="ts">let { matrix, rowOrder, dayLabels, hourLabel = (h) => String(h), hourLabelEvery = 1, normalize = true, highlight, markNow = false, cellLabel, onCellSelect, selected = null, ariaLabel = 'Weekly traffic heatmap', class: className = '' } = $props();
|
|
2
|
+
const BASE_DAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
3
|
+
const hours = Array.from({ length: 24 }, (_, h) => h);
|
|
4
|
+
// Captured once at init — a long-lived page drifts; accepted (matches hand-rolled versions).
|
|
5
|
+
const now = new Date();
|
|
6
|
+
const nowDay = now.getDay();
|
|
7
|
+
const nowHour = now.getHours();
|
|
8
|
+
const order = $derived(rowOrder ?? [0, 1, 2, 3, 4, 5, 6]);
|
|
9
|
+
const labels = $derived(dayLabels ?? order.map((d) => BASE_DAY_NAMES[d] ?? String(d)));
|
|
10
|
+
const rows = $derived(order.map((day, i) => ({ day, label: labels[i] ?? String(day) })));
|
|
11
|
+
const maxValue = $derived(normalize ? Math.max(1, ...matrix.flat()) : 100);
|
|
12
|
+
function getCell(day, hour) {
|
|
13
|
+
const value = matrix[day]?.[hour] ?? 0;
|
|
14
|
+
const intensity = Math.min(100, Math.max(0, Math.round((value / maxValue) * 100)));
|
|
15
|
+
return { day, hour, value, intensity };
|
|
16
|
+
}
|
|
17
|
+
function labelFor(cell, dayLabel, isNow) {
|
|
18
|
+
const text = cellLabel ? cellLabel(cell) : `${dayLabel} ${hourLabel(cell.hour)} — ${cell.value}`;
|
|
19
|
+
return isNow ? `${text} (now)` : text;
|
|
20
|
+
}
|
|
21
|
+
const fill = (intensity) => `color-mix(in srgb, var(--color-accent) ${intensity}%, var(--color-base-2))`;
|
|
22
|
+
export {};
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
{#if matrix.length > 0}
|
|
26
|
+
<div class="heatmap-scroll {className}">
|
|
27
|
+
<div
|
|
28
|
+
class="heatmap-grid"
|
|
29
|
+
role={onCellSelect ? undefined : 'img'}
|
|
30
|
+
aria-label={onCellSelect ? undefined : ariaLabel}
|
|
31
|
+
>
|
|
32
|
+
<!-- Corner + hour axis -->
|
|
33
|
+
<div class="heatmap-corner" aria-hidden="true"></div>
|
|
34
|
+
{#each hours as hour (hour)}
|
|
35
|
+
<div class="heatmap-axis heatmap-hour" aria-hidden="true">
|
|
36
|
+
{hour % hourLabelEvery === 0 ? hourLabel(hour) : ''}
|
|
37
|
+
</div>
|
|
38
|
+
{/each}
|
|
39
|
+
|
|
40
|
+
<!-- Day rows -->
|
|
41
|
+
{#each rows as row (row.day)}
|
|
42
|
+
<div class="heatmap-axis heatmap-day" aria-hidden="true">{row.label}</div>
|
|
43
|
+
{#each hours as hour (hour)}
|
|
44
|
+
{@const cell = getCell(row.day, hour)}
|
|
45
|
+
{@const hl = highlight?.(row.day, hour) ?? false}
|
|
46
|
+
{@const isNow = markNow && row.day === nowDay && hour === nowHour}
|
|
47
|
+
{@const isSel = !!selected && selected.day === row.day && selected.hour === hour}
|
|
48
|
+
{@const label = labelFor(cell, row.label, isNow)}
|
|
49
|
+
{#if onCellSelect}
|
|
50
|
+
<button
|
|
51
|
+
type="button"
|
|
52
|
+
class="heatmap-cell"
|
|
53
|
+
class:is-highlight={hl}
|
|
54
|
+
class:is-ring={isNow || isSel}
|
|
55
|
+
style="background-color: {fill(cell.intensity)}"
|
|
56
|
+
aria-label={label}
|
|
57
|
+
aria-pressed={isSel}
|
|
58
|
+
onclick={() => onCellSelect(cell)}
|
|
59
|
+
></button>
|
|
60
|
+
{:else}
|
|
61
|
+
<div
|
|
62
|
+
class="heatmap-cell"
|
|
63
|
+
class:is-highlight={hl}
|
|
64
|
+
class:is-ring={isNow || isSel}
|
|
65
|
+
style="background-color: {fill(cell.intensity)}"
|
|
66
|
+
title={label}
|
|
67
|
+
aria-hidden="true"
|
|
68
|
+
></div>
|
|
69
|
+
{/if}
|
|
70
|
+
{/each}
|
|
71
|
+
{/each}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
{/if}
|
|
75
|
+
|
|
76
|
+
<style>
|
|
77
|
+
.heatmap-scroll {
|
|
78
|
+
overflow-x: auto;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.heatmap-grid {
|
|
82
|
+
display: grid;
|
|
83
|
+
grid-template-columns: 3rem repeat(24, 1fr);
|
|
84
|
+
gap: 2px;
|
|
85
|
+
min-width: 640px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.heatmap-axis {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
justify-content: center;
|
|
92
|
+
font-size: 0.6rem;
|
|
93
|
+
color: var(--color-text-muted);
|
|
94
|
+
text-align: center;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.heatmap-corner {
|
|
98
|
+
min-width: 3rem;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.heatmap-cell {
|
|
102
|
+
height: var(--heatmap-cell-size, 1.5rem);
|
|
103
|
+
min-width: var(--heatmap-cell-size, 1.5rem);
|
|
104
|
+
border-radius: 2px;
|
|
105
|
+
border: 0;
|
|
106
|
+
padding: 0;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
button.heatmap-cell {
|
|
110
|
+
cursor: pointer;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
button.heatmap-cell:focus-visible {
|
|
114
|
+
outline: 2px solid var(--color-accent);
|
|
115
|
+
outline-offset: 2px;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.heatmap-cell.is-highlight {
|
|
119
|
+
outline: 1.5px solid color-mix(in srgb, var(--color-text) 85%, transparent);
|
|
120
|
+
outline-offset: -1.5px;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* Ring (now / selected) wins over the highlight outline. */
|
|
124
|
+
.heatmap-cell.is-ring {
|
|
125
|
+
box-shadow: 0 0 0 2px var(--color-accent);
|
|
126
|
+
position: relative;
|
|
127
|
+
z-index: 1;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
@media (hover: hover) {
|
|
131
|
+
.heatmap-cell {
|
|
132
|
+
transition: transform 150ms var(--ease-sharp);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.heatmap-cell:hover {
|
|
136
|
+
transform: scale(1.2);
|
|
137
|
+
position: relative;
|
|
138
|
+
z-index: 1;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@media (prefers-reduced-motion: reduce) {
|
|
143
|
+
.heatmap-cell {
|
|
144
|
+
transition: none;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.heatmap-cell:hover {
|
|
148
|
+
transform: none;
|
|
149
|
+
}
|
|
150
|
+
}</style>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { WeekHeatmapCell } from './Data.types';
|
|
2
|
+
interface Props {
|
|
3
|
+
/** 7 rows × 24 cols. Row index 0 = Sunday. */
|
|
4
|
+
matrix: number[][];
|
|
5
|
+
/**
|
|
6
|
+
* Display order of matrix row indices. Default Sunday-first [0..6].
|
|
7
|
+
* Monday-first = [1, 2, 3, 4, 5, 6, 0].
|
|
8
|
+
*/
|
|
9
|
+
rowOrder?: number[];
|
|
10
|
+
/** Labels aligned to rowOrder. Default ['Sun','Mon',...] reordered by rowOrder. */
|
|
11
|
+
dayLabels?: string[];
|
|
12
|
+
/** Hour column label formatter. Default String(hour). E.g. h => '12a'/'6a'/'12p'/'6p'. */
|
|
13
|
+
hourLabel?: (hour: number) => string;
|
|
14
|
+
/**
|
|
15
|
+
* Show a label only every N hour columns (others render empty) to reduce axis noise.
|
|
16
|
+
* Default 1 (label every hour).
|
|
17
|
+
*/
|
|
18
|
+
hourLabelEvery?: number;
|
|
19
|
+
/**
|
|
20
|
+
* true (default): scale intensity to max(matrix) — raw-count matrices.
|
|
21
|
+
* false: values are already 0–100 — percentage matrices.
|
|
22
|
+
*/
|
|
23
|
+
normalize?: boolean;
|
|
24
|
+
/** Outline overlay predicate (e.g. member's frequent cells). */
|
|
25
|
+
highlight?: (day: number, hour: number) => boolean;
|
|
26
|
+
/** Ring the current weekday+hour cell. Default false. */
|
|
27
|
+
markNow?: boolean;
|
|
28
|
+
/** Tooltip/aria text per cell. Default: `${dayLabel} ${hourLabel} — ${value}`. */
|
|
29
|
+
cellLabel?: (cell: WeekHeatmapCell) => string;
|
|
30
|
+
/**
|
|
31
|
+
* When provided, cells render as <button> and clicking/tapping calls this —
|
|
32
|
+
* the consumer renders its own readout. When absent, cells are inert divs
|
|
33
|
+
* and the grid gets role="img".
|
|
34
|
+
*/
|
|
35
|
+
onCellSelect?: (cell: WeekHeatmapCell) => void;
|
|
36
|
+
/** Currently selected cell, ring-highlighted. Consumer-managed (pairs with onCellSelect). */
|
|
37
|
+
selected?: {
|
|
38
|
+
day: number;
|
|
39
|
+
hour: number;
|
|
40
|
+
} | null;
|
|
41
|
+
/** Accessible name for the grid. Default "Weekly traffic heatmap". */
|
|
42
|
+
ariaLabel?: string;
|
|
43
|
+
class?: string;
|
|
44
|
+
}
|
|
45
|
+
declare const WeekHeatmap: import("svelte").Component<Props, {}, "">;
|
|
46
|
+
type WeekHeatmap = ReturnType<typeof WeekHeatmap>;
|
|
47
|
+
export default WeekHeatmap;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { default as Table } from './Table.svelte';
|
|
2
|
-
export type { Column as TableColumn, SortDirection, RowKey, SelectionState, CalendarEvent, ListItem } from './Data.types';
|
|
2
|
+
export type { Column as TableColumn, SortDirection, RowKey, SelectionState, CalendarEvent, ListItem, WeekHeatmapCell } from './Data.types';
|
|
3
3
|
export { default as Pagination } from './Pagination.svelte';
|
|
4
4
|
export { default as Badge } from './Badge.svelte';
|
|
5
5
|
export { default as Tag } from './Tag.svelte';
|
|
@@ -9,3 +9,5 @@ export { default as CodeBlock } from './CodeBlock.svelte';
|
|
|
9
9
|
export { default as Stat } from './Stat.svelte';
|
|
10
10
|
export { default as CalendarGrid } from './CalendarGrid.svelte';
|
|
11
11
|
export { default as Sparkline } from './Sparkline.svelte';
|
|
12
|
+
export { default as EmptyState } from './EmptyState.svelte';
|
|
13
|
+
export { default as WeekHeatmap } from './WeekHeatmap.svelte';
|
|
@@ -8,3 +8,5 @@ export { default as CodeBlock } from './CodeBlock.svelte';
|
|
|
8
8
|
export { default as Stat } from './Stat.svelte';
|
|
9
9
|
export { default as CalendarGrid } from './CalendarGrid.svelte';
|
|
10
10
|
export { default as Sparkline } from './Sparkline.svelte';
|
|
11
|
+
export { default as EmptyState } from './EmptyState.svelte';
|
|
12
|
+
export { default as WeekHeatmap } from './WeekHeatmap.svelte';
|
|
@@ -1,28 +1,34 @@
|
|
|
1
1
|
<script lang="ts">import { createId } from '../../lib/internal/id.js';
|
|
2
|
-
let { checked = $bindable(), disabled = false, label, id = createId('checkbox'), name, onchange, iconBefore, class: className = '' } = $props();
|
|
2
|
+
let { checked = $bindable(), disabled = false, error = false, label, id = createId('checkbox'), name, onchange, iconBefore, validationMessage, class: className = '' } = $props();
|
|
3
|
+
const hasError = $derived(error || !!validationMessage);
|
|
3
4
|
</script>
|
|
4
5
|
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
6
|
+
<div>
|
|
7
|
+
<label for={id} class="flex items-center gap-2 cursor-pointer {disabled ? 'opacity-50 cursor-not-allowed' : ''} {className}">
|
|
8
|
+
<input
|
|
9
|
+
type="checkbox"
|
|
10
|
+
{id}
|
|
11
|
+
name={name}
|
|
12
|
+
{disabled}
|
|
13
|
+
bind:checked
|
|
14
|
+
onchange={onchange}
|
|
15
|
+
class="w-6 h-6 rounded-sm border-2 bg-transparent appearance-none transition-all duration-200 ease-luxe cursor-pointer checked:bg-accent checked:border-accent checked:accent-glow focus:outline-none focus:ring-2 focus:ring-accent focus:ring-offset-2 focus:ring-offset-base-1 relative {hasError ? 'border-error' : 'border-border-strong'} {disabled ? 'cursor-not-allowed' : ''}"
|
|
16
|
+
/>
|
|
17
|
+
{#if iconBefore}
|
|
18
|
+
<span class="inline-flex items-center justify-center text-text-soft">
|
|
19
|
+
{@render iconBefore()}
|
|
20
|
+
</span>
|
|
21
|
+
{/if}
|
|
22
|
+
{#if label}
|
|
23
|
+
<span class="text-text text-sm select-none">
|
|
24
|
+
{label}
|
|
25
|
+
</span>
|
|
26
|
+
{/if}
|
|
27
|
+
</label>
|
|
28
|
+
{#if validationMessage}
|
|
29
|
+
<p class="text-error text-xs mt-1.5 ml-8">{validationMessage}</p>
|
|
19
30
|
{/if}
|
|
20
|
-
|
|
21
|
-
<span class="text-text text-sm select-none">
|
|
22
|
-
{label}
|
|
23
|
-
</span>
|
|
24
|
-
{/if}
|
|
25
|
-
</label>
|
|
31
|
+
</div>
|
|
26
32
|
|
|
27
33
|
<style>
|
|
28
34
|
input[type="checkbox"]:checked::after {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export interface Props {
|
|
2
2
|
checked?: boolean;
|
|
3
3
|
disabled?: boolean;
|
|
4
|
+
error?: boolean;
|
|
4
5
|
label?: string;
|
|
5
6
|
id?: string;
|
|
6
7
|
name?: string;
|
|
7
8
|
onchange?: (event: Event) => void;
|
|
8
9
|
iconBefore?: import('svelte').Snippet;
|
|
10
|
+
validationMessage?: string;
|
|
9
11
|
class?: string;
|
|
10
12
|
}
|
|
11
13
|
declare const Checkbox: import("svelte").Component<Props, {}, "checked">;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { baseInputClasses, focusClasses, disabledClasses, } from './formClasses';
|
|
3
3
|
import CalendarGrid from '../data/CalendarGrid.svelte';
|
|
4
4
|
import Dropdown from '../overlays/Dropdown.svelte';
|
|
5
|
-
let { variant = 'glass', value = $bindable(), min, max, placeholder = 'Select date', disabled = false, error = false, label, id = createId('datepicker'), name, autocomplete, onchange, class: className = '', } = $props();
|
|
5
|
+
let { variant = 'glass', value = $bindable(), min, max, placeholder = 'Select date', disabled = false, error = false, label, id = createId('datepicker'), name, autocomplete, onchange, validationMessage, class: className = '', } = $props();
|
|
6
6
|
let open = $state(false);
|
|
7
7
|
let calendarElement = $state();
|
|
8
8
|
let inputElement = $state();
|
|
@@ -183,7 +183,8 @@ $effect(() => {
|
|
|
183
183
|
});
|
|
184
184
|
}
|
|
185
185
|
});
|
|
186
|
-
const
|
|
186
|
+
const hasError = $derived(error || !!validationMessage);
|
|
187
|
+
const errorClasses = $derived(hasError ? 'error-state' : '');
|
|
187
188
|
</script>
|
|
188
189
|
|
|
189
190
|
<div class="date-picker-wrapper w-full">
|
|
@@ -257,6 +258,10 @@ const errorClasses = $derived(error ? 'error-state' : '');
|
|
|
257
258
|
/>
|
|
258
259
|
</div>
|
|
259
260
|
</Dropdown>
|
|
261
|
+
|
|
262
|
+
{#if validationMessage}
|
|
263
|
+
<p class="text-error text-xs mt-1.5">{validationMessage}</p>
|
|
264
|
+
{/if}
|
|
260
265
|
</div>
|
|
261
266
|
|
|
262
267
|
<style>
|
|
@@ -11,6 +11,7 @@ export interface Props {
|
|
|
11
11
|
name?: string;
|
|
12
12
|
autocomplete?: HTMLInputElement['autocomplete'];
|
|
13
13
|
onchange?: (event: Event) => void;
|
|
14
|
+
validationMessage?: string;
|
|
14
15
|
class?: string;
|
|
15
16
|
}
|
|
16
17
|
declare const DatePicker: import("svelte").Component<Props, {}, "value">;
|
|
@@ -3,7 +3,7 @@ import { baseInputClasses, focusClasses, disabledClasses, } from './formClasses'
|
|
|
3
3
|
import CalendarGrid from '../data/CalendarGrid.svelte';
|
|
4
4
|
import TimePickerGrid from './TimePickerGrid.svelte';
|
|
5
5
|
import Dropdown from '../overlays/Dropdown.svelte';
|
|
6
|
-
let { variant = 'glass', value = $bindable(), min, max, step = 60, placeholder = 'Select date and time', disabled = false, error = false, label, id = createId('datetimepicker'), name, autocomplete, is24Hour = true, onchange, class: className = '', } = $props();
|
|
6
|
+
let { variant = 'glass', value = $bindable(), min, max, step = 60, placeholder = 'Select date and time', disabled = false, error = false, label, id = createId('datetimepicker'), name, autocomplete, is24Hour = true, onchange, validationMessage, class: className = '', } = $props();
|
|
7
7
|
let open = $state(false);
|
|
8
8
|
let activeTab = $state('date');
|
|
9
9
|
let pickerElement = $state();
|
|
@@ -191,7 +191,8 @@ function handleInputKeyDown(event) {
|
|
|
191
191
|
}
|
|
192
192
|
event.preventDefault();
|
|
193
193
|
}
|
|
194
|
-
const
|
|
194
|
+
const hasError = $derived(error || !!validationMessage);
|
|
195
|
+
const errorClasses = $derived(hasError ? 'error-state' : '');
|
|
195
196
|
</script>
|
|
196
197
|
|
|
197
198
|
<div class="datetime-picker-wrapper w-full">
|
|
@@ -323,6 +324,10 @@ const errorClasses = $derived(error ? 'error-state' : '');
|
|
|
323
324
|
</div>
|
|
324
325
|
</div>
|
|
325
326
|
</Dropdown>
|
|
327
|
+
|
|
328
|
+
{#if validationMessage}
|
|
329
|
+
<p class="text-error text-xs mt-1.5">{validationMessage}</p>
|
|
330
|
+
{/if}
|
|
326
331
|
</div>
|
|
327
332
|
|
|
328
333
|
<style>
|
|
@@ -13,6 +13,7 @@ export interface Props {
|
|
|
13
13
|
autocomplete?: HTMLInputElement['autocomplete'];
|
|
14
14
|
is24Hour?: boolean;
|
|
15
15
|
onchange?: (event: Event) => void;
|
|
16
|
+
validationMessage?: string;
|
|
16
17
|
class?: string;
|
|
17
18
|
}
|
|
18
19
|
declare const DateTimePicker: import("svelte").Component<Props, {}, "value">;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">import { createId } from '../../lib/internal/id.js';
|
|
2
|
-
let { files = $bindable(null), accept, multiple = false, disabled = false, label, id = createId('file-upload'), name, onchange, icon } = $props();
|
|
2
|
+
let { files = $bindable(null), accept, multiple = false, disabled = false, error = false, label, id = createId('file-upload'), name, onchange, icon, validationMessage } = $props();
|
|
3
|
+
const hasError = $derived(error || !!validationMessage);
|
|
3
4
|
let isDragging = $state(false);
|
|
4
5
|
let inputElement;
|
|
5
6
|
function handleDragOver(event) {
|
|
@@ -66,7 +67,7 @@ const fileList = $derived(files ? Array.from(files) : []);
|
|
|
66
67
|
ondrop={handleDrop}
|
|
67
68
|
onclick={handleClick}
|
|
68
69
|
onkeydown={(e) => e.key === 'Enter' && handleClick()}
|
|
69
|
-
class="panel-raised rounded-[var(--radius-lg)] p-8 text-center cursor-pointer transition-all duration-300 ease-luxe {isDragging ? 'border-2 border-accent accent-glow bg-base-2' : ''} {disabled ? 'opacity-50 cursor-not-allowed pointer-events-none' : ''}"
|
|
70
|
+
class="panel-raised rounded-[var(--radius-lg)] p-8 text-center cursor-pointer transition-all duration-300 ease-luxe {isDragging ? 'border-2 border-accent accent-glow bg-base-2' : ''} {hasError && !isDragging ? 'border-2 border-error' : ''} {disabled ? 'opacity-50 cursor-not-allowed pointer-events-none' : ''}"
|
|
70
71
|
>
|
|
71
72
|
<div class="flex flex-col items-center gap-2">
|
|
72
73
|
{#if icon}
|
|
@@ -134,4 +135,8 @@ const fileList = $derived(files ? Array.from(files) : []);
|
|
|
134
135
|
</ul>
|
|
135
136
|
</div>
|
|
136
137
|
{/if}
|
|
138
|
+
|
|
139
|
+
{#if validationMessage}
|
|
140
|
+
<p class="text-error text-xs mt-1.5">{validationMessage}</p>
|
|
141
|
+
{/if}
|
|
137
142
|
</div>
|
|
@@ -9,6 +9,7 @@ interface Props {
|
|
|
9
9
|
accept?: string;
|
|
10
10
|
multiple?: boolean;
|
|
11
11
|
disabled?: boolean;
|
|
12
|
+
error?: boolean;
|
|
12
13
|
label?: string;
|
|
13
14
|
id?: string;
|
|
14
15
|
name?: string;
|
|
@@ -18,6 +19,7 @@ interface Props {
|
|
|
18
19
|
};
|
|
19
20
|
}) => void;
|
|
20
21
|
icon?: import('svelte').Snippet;
|
|
22
|
+
validationMessage?: string;
|
|
21
23
|
}
|
|
22
24
|
declare const FileUpload: import("svelte").Component<Props, {}, "files">;
|
|
23
25
|
type FileUpload = ReturnType<typeof FileUpload>;
|