@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,14 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
variant?: 'default' | 'success' | 'warning' | 'error' | 'accent' | 'feature';
|
|
3
|
+
size?: 'sm' | 'md';
|
|
4
|
+
icon?: import('svelte').Snippet;
|
|
5
|
+
children?: import('svelte').Snippet;
|
|
6
|
+
hover?: boolean;
|
|
7
|
+
glow?: boolean;
|
|
8
|
+
statusDot?: boolean;
|
|
9
|
+
uppercase?: boolean;
|
|
10
|
+
class?: string;
|
|
11
|
+
}
|
|
12
|
+
declare const Badge: import("svelte").Component<Props, {}, "">;
|
|
13
|
+
type Badge = ReturnType<typeof Badge>;
|
|
14
|
+
export default Badge;
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
<script lang="ts">let { month = $bindable(new Date()), events = [], selectedDate = $bindable(undefined), onDateSelect, onMonthChange, renderCell, weekStartsOn = 0, showWeekNumbers = false, highlightToday = true, class: className = '' } = $props();
|
|
2
|
+
const today = new Date();
|
|
3
|
+
today.setHours(0, 0, 0, 0);
|
|
4
|
+
// Get calendar days for the current month
|
|
5
|
+
const calendarDays = $derived.by(() => {
|
|
6
|
+
const year = month.getFullYear();
|
|
7
|
+
const monthIndex = month.getMonth();
|
|
8
|
+
const firstDay = new Date(year, monthIndex, 1);
|
|
9
|
+
const lastDay = new Date(year, monthIndex + 1, 0);
|
|
10
|
+
const startingDayOfWeek = firstDay.getDay();
|
|
11
|
+
const daysInMonth = lastDay.getDate();
|
|
12
|
+
// Adjust for week start day
|
|
13
|
+
const offset = weekStartsOn === 1 ? (startingDayOfWeek === 0 ? 6 : startingDayOfWeek - 1) : startingDayOfWeek;
|
|
14
|
+
const days = [];
|
|
15
|
+
// Previous month days
|
|
16
|
+
const prevMonthLastDay = new Date(year, monthIndex, 0);
|
|
17
|
+
for (let i = offset - 1; i >= 0; i--) {
|
|
18
|
+
days.push(new Date(year, monthIndex - 1, prevMonthLastDay.getDate() - i));
|
|
19
|
+
}
|
|
20
|
+
// Current month days
|
|
21
|
+
for (let i = 1; i <= daysInMonth; i++) {
|
|
22
|
+
days.push(new Date(year, monthIndex, i));
|
|
23
|
+
}
|
|
24
|
+
// Next month days to fill grid
|
|
25
|
+
const remainingCells = 42 - days.length; // 6 weeks * 7 days
|
|
26
|
+
for (let i = 1; i <= remainingCells; i++) {
|
|
27
|
+
days.push(new Date(year, monthIndex + 1, i));
|
|
28
|
+
}
|
|
29
|
+
return days;
|
|
30
|
+
});
|
|
31
|
+
// Group events by date string
|
|
32
|
+
const eventsByDate = $derived.by(() => {
|
|
33
|
+
const map = new Map();
|
|
34
|
+
events.forEach((event) => {
|
|
35
|
+
const eventDate = typeof event.date === 'string' ? new Date(event.date) : event.date;
|
|
36
|
+
const key = eventDate.toISOString().split('T')[0];
|
|
37
|
+
if (!map.has(key)) {
|
|
38
|
+
map.set(key, []);
|
|
39
|
+
}
|
|
40
|
+
map.get(key).push(event);
|
|
41
|
+
});
|
|
42
|
+
return map;
|
|
43
|
+
});
|
|
44
|
+
function isSameDay(date1, date2) {
|
|
45
|
+
return (date1.getFullYear() === date2.getFullYear() &&
|
|
46
|
+
date1.getMonth() === date2.getMonth() &&
|
|
47
|
+
date1.getDate() === date2.getDate());
|
|
48
|
+
}
|
|
49
|
+
function isSameMonth(date) {
|
|
50
|
+
return date.getMonth() === month.getMonth() && date.getFullYear() === month.getFullYear();
|
|
51
|
+
}
|
|
52
|
+
function getCellData(date) {
|
|
53
|
+
const dateKey = date.toISOString().split('T')[0];
|
|
54
|
+
const cellEvents = eventsByDate.get(dateKey) || [];
|
|
55
|
+
return {
|
|
56
|
+
date,
|
|
57
|
+
events: cellEvents,
|
|
58
|
+
isToday: highlightToday && isSameDay(date, today),
|
|
59
|
+
isSelected: selectedDate ? isSameDay(date, selectedDate) : false,
|
|
60
|
+
isCurrentMonth: isSameMonth(date),
|
|
61
|
+
isEmpty: cellEvents.length === 0 && !isSameMonth(date)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function handleDateClick(date, isCurrentMonth) {
|
|
65
|
+
if (!isCurrentMonth) {
|
|
66
|
+
// Navigate to that month
|
|
67
|
+
const newMonth = new Date(date);
|
|
68
|
+
month = newMonth;
|
|
69
|
+
onMonthChange?.(newMonth);
|
|
70
|
+
}
|
|
71
|
+
selectedDate = date;
|
|
72
|
+
onDateSelect?.(date);
|
|
73
|
+
}
|
|
74
|
+
function handleKeyDown(event, date, index) {
|
|
75
|
+
const days = calendarDays;
|
|
76
|
+
let newIndex = index;
|
|
77
|
+
switch (event.key) {
|
|
78
|
+
case 'ArrowLeft':
|
|
79
|
+
event.preventDefault();
|
|
80
|
+
newIndex = index > 0 ? index - 1 : index;
|
|
81
|
+
break;
|
|
82
|
+
case 'ArrowRight':
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
newIndex = index < days.length - 1 ? index + 1 : index;
|
|
85
|
+
break;
|
|
86
|
+
case 'ArrowUp':
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
newIndex = index >= 7 ? index - 7 : index;
|
|
89
|
+
break;
|
|
90
|
+
case 'ArrowDown':
|
|
91
|
+
event.preventDefault();
|
|
92
|
+
newIndex = index + 7 < days.length ? index + 7 : index;
|
|
93
|
+
break;
|
|
94
|
+
case 'Home':
|
|
95
|
+
event.preventDefault();
|
|
96
|
+
newIndex = Math.floor(index / 7) * 7;
|
|
97
|
+
break;
|
|
98
|
+
case 'End':
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
newIndex = Math.floor(index / 7) * 7 + 6;
|
|
101
|
+
break;
|
|
102
|
+
case 'Enter':
|
|
103
|
+
case ' ':
|
|
104
|
+
event.preventDefault();
|
|
105
|
+
handleDateClick(date, isSameMonth(date));
|
|
106
|
+
return;
|
|
107
|
+
default:
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (newIndex !== index) {
|
|
111
|
+
const newDate = days[newIndex];
|
|
112
|
+
requestAnimationFrame(() => {
|
|
113
|
+
const cell = document.querySelector(`[data-date="${newDate.toISOString()}"]`);
|
|
114
|
+
cell?.focus();
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function previousMonth() {
|
|
119
|
+
const newMonth = new Date(month.getFullYear(), month.getMonth() - 1, 1);
|
|
120
|
+
month = newMonth;
|
|
121
|
+
onMonthChange?.(newMonth);
|
|
122
|
+
}
|
|
123
|
+
function nextMonth() {
|
|
124
|
+
const newMonth = new Date(month.getFullYear(), month.getMonth() + 1, 1);
|
|
125
|
+
month = newMonth;
|
|
126
|
+
onMonthChange?.(newMonth);
|
|
127
|
+
}
|
|
128
|
+
const weekDayNames = $derived(weekStartsOn === 1
|
|
129
|
+
? ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
|
|
130
|
+
: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']);
|
|
131
|
+
const monthNames = [
|
|
132
|
+
'January',
|
|
133
|
+
'February',
|
|
134
|
+
'March',
|
|
135
|
+
'April',
|
|
136
|
+
'May',
|
|
137
|
+
'June',
|
|
138
|
+
'July',
|
|
139
|
+
'August',
|
|
140
|
+
'September',
|
|
141
|
+
'October',
|
|
142
|
+
'November',
|
|
143
|
+
'December'
|
|
144
|
+
];
|
|
145
|
+
const currentMonthYear = $derived(`${monthNames[month.getMonth()]} ${month.getFullYear()}`);
|
|
146
|
+
const variantColors = {
|
|
147
|
+
default: 'var(--color-base-3)',
|
|
148
|
+
success: 'var(--color-success)',
|
|
149
|
+
warning: 'var(--color-warning)',
|
|
150
|
+
error: 'var(--color-error)',
|
|
151
|
+
accent: 'var(--color-accent)'
|
|
152
|
+
};
|
|
153
|
+
export {};
|
|
154
|
+
</script>
|
|
155
|
+
|
|
156
|
+
<div class="calendar-grid {className}">
|
|
157
|
+
<!-- Header with month navigation -->
|
|
158
|
+
<div class="calendar-header">
|
|
159
|
+
<button
|
|
160
|
+
type="button"
|
|
161
|
+
onclick={previousMonth}
|
|
162
|
+
class="nav-button"
|
|
163
|
+
aria-label="Previous month"
|
|
164
|
+
>
|
|
165
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
166
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
|
167
|
+
</svg>
|
|
168
|
+
</button>
|
|
169
|
+
|
|
170
|
+
<h2 class="month-title">{currentMonthYear}</h2>
|
|
171
|
+
|
|
172
|
+
<button
|
|
173
|
+
type="button"
|
|
174
|
+
onclick={nextMonth}
|
|
175
|
+
class="nav-button"
|
|
176
|
+
aria-label="Next month"
|
|
177
|
+
>
|
|
178
|
+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
179
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
|
180
|
+
</svg>
|
|
181
|
+
</button>
|
|
182
|
+
</div>
|
|
183
|
+
|
|
184
|
+
<!-- Calendar grid -->
|
|
185
|
+
<div role="grid" aria-label="Calendar" class="calendar-body">
|
|
186
|
+
<!-- Week day headers -->
|
|
187
|
+
<div role="row" class="week-header">
|
|
188
|
+
{#if showWeekNumbers}
|
|
189
|
+
<div class="week-number-header"></div>
|
|
190
|
+
{/if}
|
|
191
|
+
{#each weekDayNames as day}
|
|
192
|
+
<div role="columnheader" class="day-header">
|
|
193
|
+
{day}
|
|
194
|
+
</div>
|
|
195
|
+
{/each}
|
|
196
|
+
</div>
|
|
197
|
+
|
|
198
|
+
<!-- Date cells -->
|
|
199
|
+
{#each { length: 6 } as _, weekIndex}
|
|
200
|
+
<div role="row" class="calendar-week">
|
|
201
|
+
{#if showWeekNumbers}
|
|
202
|
+
<div class="week-number">W{weekIndex + 1}</div>
|
|
203
|
+
{/if}
|
|
204
|
+
{#each calendarDays.slice(weekIndex * 7, (weekIndex + 1) * 7) as date, dayIndex}
|
|
205
|
+
{@const cellData = getCellData(date)}
|
|
206
|
+
{@const globalIndex = weekIndex * 7 + dayIndex}
|
|
207
|
+
<button
|
|
208
|
+
role="gridcell"
|
|
209
|
+
type="button"
|
|
210
|
+
data-date={date.toISOString()}
|
|
211
|
+
class="calendar-cell"
|
|
212
|
+
class:current-month={cellData.isCurrentMonth}
|
|
213
|
+
class:other-month={!cellData.isCurrentMonth}
|
|
214
|
+
class:is-today={cellData.isToday}
|
|
215
|
+
class:is-selected={cellData.isSelected}
|
|
216
|
+
class:has-events={cellData.events.length > 0}
|
|
217
|
+
tabindex={globalIndex === 0 ? 0 : -1}
|
|
218
|
+
onclick={() => handleDateClick(date, cellData.isCurrentMonth)}
|
|
219
|
+
onkeydown={(e) => handleKeyDown(e, date, globalIndex)}
|
|
220
|
+
aria-label="{date.getDate()} {monthNames[date.getMonth()]} {date.getFullYear()}"
|
|
221
|
+
aria-selected={cellData.isSelected}
|
|
222
|
+
>
|
|
223
|
+
{#if renderCell}
|
|
224
|
+
{@render renderCell(cellData)}
|
|
225
|
+
{:else}
|
|
226
|
+
<div class="cell-content">
|
|
227
|
+
<span class="day-number">{date.getDate()}</span>
|
|
228
|
+
{#if cellData.events.length > 0}
|
|
229
|
+
<div class="event-indicators">
|
|
230
|
+
{#each cellData.events.slice(0, 3) as event}
|
|
231
|
+
<span
|
|
232
|
+
class="event-dot"
|
|
233
|
+
style="background-color: {variantColors[event.variant || 'default']}"
|
|
234
|
+
title={event.title}
|
|
235
|
+
></span>
|
|
236
|
+
{/each}
|
|
237
|
+
{#if cellData.events.length > 3}
|
|
238
|
+
<span class="event-more">+{cellData.events.length - 3}</span>
|
|
239
|
+
{/if}
|
|
240
|
+
</div>
|
|
241
|
+
{/if}
|
|
242
|
+
</div>
|
|
243
|
+
{/if}
|
|
244
|
+
</button>
|
|
245
|
+
{/each}
|
|
246
|
+
</div>
|
|
247
|
+
{/each}
|
|
248
|
+
</div>
|
|
249
|
+
</div>
|
|
250
|
+
|
|
251
|
+
<style>
|
|
252
|
+
.calendar-grid {
|
|
253
|
+
width: 100%;
|
|
254
|
+
font-family: var(--font-body);
|
|
255
|
+
color: var(--color-text);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.calendar-header {
|
|
259
|
+
display: flex;
|
|
260
|
+
align-items: center;
|
|
261
|
+
justify-content: space-between;
|
|
262
|
+
padding: 1.5rem;
|
|
263
|
+
border-bottom: 1px solid var(--color-border);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
.month-title {
|
|
267
|
+
font-family: var(--font-heading);
|
|
268
|
+
font-size: 1.5rem;
|
|
269
|
+
font-weight: 700;
|
|
270
|
+
color: var(--color-text);
|
|
271
|
+
margin: 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.nav-button {
|
|
275
|
+
display: flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
justify-content: center;
|
|
278
|
+
width: 2.5rem;
|
|
279
|
+
height: 2.5rem;
|
|
280
|
+
background: transparent;
|
|
281
|
+
border: 1px solid var(--color-border);
|
|
282
|
+
border-radius: var(--radius-md);
|
|
283
|
+
color: var(--color-text-soft);
|
|
284
|
+
cursor: pointer;
|
|
285
|
+
transition: all var(--duration-200) var(--ease-sharp);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.nav-button:hover {
|
|
289
|
+
background: var(--color-base-3);
|
|
290
|
+
color: var(--color-accent);
|
|
291
|
+
border-color: var(--color-accent);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.nav-button:active {
|
|
295
|
+
transform: scale(0.95);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.calendar-body {
|
|
299
|
+
padding: 1rem;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.week-header {
|
|
303
|
+
display: grid;
|
|
304
|
+
grid-template-columns: repeat(7, 1fr);
|
|
305
|
+
gap: 0.5rem;
|
|
306
|
+
margin-bottom: 0.5rem;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.week-header.with-numbers {
|
|
310
|
+
grid-template-columns: 2.5rem repeat(7, 1fr);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.day-header {
|
|
314
|
+
text-align: center;
|
|
315
|
+
font-size: 0.875rem;
|
|
316
|
+
font-weight: 600;
|
|
317
|
+
color: var(--color-text-soft);
|
|
318
|
+
text-transform: uppercase;
|
|
319
|
+
letter-spacing: 0.05em;
|
|
320
|
+
padding: 0.5rem;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.calendar-week {
|
|
324
|
+
display: grid;
|
|
325
|
+
grid-template-columns: repeat(7, 1fr);
|
|
326
|
+
gap: 0.5rem;
|
|
327
|
+
margin-bottom: 0.5rem;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.week-number-header,
|
|
331
|
+
.week-number {
|
|
332
|
+
display: flex;
|
|
333
|
+
align-items: center;
|
|
334
|
+
justify-content: center;
|
|
335
|
+
font-size: 0.75rem;
|
|
336
|
+
font-weight: 600;
|
|
337
|
+
color: var(--color-text-muted);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.calendar-cell {
|
|
341
|
+
position: relative;
|
|
342
|
+
aspect-ratio: 1;
|
|
343
|
+
background: var(--color-base-1);
|
|
344
|
+
border: 1px solid var(--color-border);
|
|
345
|
+
border-radius: var(--radius-md);
|
|
346
|
+
padding: 0.5rem;
|
|
347
|
+
cursor: pointer;
|
|
348
|
+
transition: all var(--duration-200) var(--ease-sharp);
|
|
349
|
+
font-family: inherit;
|
|
350
|
+
color: inherit;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.calendar-cell:hover {
|
|
354
|
+
background: var(--color-base-2);
|
|
355
|
+
border-color: var(--color-accent-soft);
|
|
356
|
+
transform: translateY(-2px);
|
|
357
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
.calendar-cell:focus-visible {
|
|
361
|
+
outline: 2px solid var(--color-accent);
|
|
362
|
+
outline-offset: 2px;
|
|
363
|
+
z-index: 1;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
.calendar-cell.other-month {
|
|
367
|
+
opacity: 0.4;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.calendar-cell.is-today {
|
|
371
|
+
background: var(--color-accent-overlay-10);
|
|
372
|
+
border-color: var(--color-accent);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.calendar-cell.is-selected {
|
|
376
|
+
background: var(--color-accent);
|
|
377
|
+
border-color: var(--color-accent);
|
|
378
|
+
color: var(--color-text);
|
|
379
|
+
box-shadow: 0 0 20px var(--color-accent-glow);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.calendar-cell.is-selected .day-number {
|
|
383
|
+
color: var(--color-base-0);
|
|
384
|
+
font-weight: 700;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.cell-content {
|
|
388
|
+
display: flex;
|
|
389
|
+
flex-direction: column;
|
|
390
|
+
align-items: center;
|
|
391
|
+
justify-content: space-between;
|
|
392
|
+
height: 100%;
|
|
393
|
+
gap: 0.25rem;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.day-number {
|
|
397
|
+
font-size: 0.875rem;
|
|
398
|
+
font-weight: 600;
|
|
399
|
+
color: var(--color-text);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.event-indicators {
|
|
403
|
+
display: flex;
|
|
404
|
+
gap: 0.25rem;
|
|
405
|
+
align-items: center;
|
|
406
|
+
flex-wrap: wrap;
|
|
407
|
+
justify-content: center;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
.event-dot {
|
|
411
|
+
width: 0.375rem;
|
|
412
|
+
height: 0.375rem;
|
|
413
|
+
border-radius: 50%;
|
|
414
|
+
background: var(--color-accent);
|
|
415
|
+
box-shadow: 0 0 6px currentColor;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.event-more {
|
|
419
|
+
font-size: 0.625rem;
|
|
420
|
+
color: var(--color-text-muted);
|
|
421
|
+
font-weight: 600;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/* Accessibility: Reduced motion */
|
|
425
|
+
@media (prefers-reduced-motion: reduce) {
|
|
426
|
+
.calendar-cell {
|
|
427
|
+
transition: none;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.calendar-cell:hover {
|
|
431
|
+
transform: none;
|
|
432
|
+
}
|
|
433
|
+
}</style>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Snippet } from 'svelte';
|
|
2
|
+
import type { CalendarEvent } from './CalendarGrid.types';
|
|
3
|
+
interface CellData {
|
|
4
|
+
date: Date;
|
|
5
|
+
events: CalendarEvent[];
|
|
6
|
+
isToday: boolean;
|
|
7
|
+
isSelected: boolean;
|
|
8
|
+
isCurrentMonth: boolean;
|
|
9
|
+
isEmpty: boolean;
|
|
10
|
+
}
|
|
11
|
+
interface Props {
|
|
12
|
+
month?: Date;
|
|
13
|
+
events?: CalendarEvent[];
|
|
14
|
+
selectedDate?: Date;
|
|
15
|
+
onDateSelect?: (date: Date) => void;
|
|
16
|
+
onMonthChange?: (date: Date) => void;
|
|
17
|
+
renderCell?: Snippet<[CellData]>;
|
|
18
|
+
weekStartsOn?: 0 | 1;
|
|
19
|
+
showWeekNumbers?: boolean;
|
|
20
|
+
highlightToday?: boolean;
|
|
21
|
+
class?: string;
|
|
22
|
+
}
|
|
23
|
+
declare const CalendarGrid: import("svelte").Component<Props, {}, "month" | "selectedDate">;
|
|
24
|
+
type CalendarGrid = ReturnType<typeof CalendarGrid>;
|
|
25
|
+
export default CalendarGrid;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
<script lang="ts">"use strict";
|
|
2
|
+
let { filename, showLineNumbers = false, lineCount = 0, highlightLines = [], showCopy = true, code, children } = $props();
|
|
3
|
+
let codeContainer;
|
|
4
|
+
let copied = $state(false);
|
|
5
|
+
async function copyCode() {
|
|
6
|
+
try {
|
|
7
|
+
const textToCopy = code || codeContainer?.textContent || '';
|
|
8
|
+
await navigator.clipboard.writeText(textToCopy);
|
|
9
|
+
copied = true;
|
|
10
|
+
setTimeout(() => {
|
|
11
|
+
copied = false;
|
|
12
|
+
}, 2000);
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
console.error('Failed to copy code:', err);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<div class="panel-raised rounded-[var(--radius-lg)] overflow-hidden relative">
|
|
21
|
+
{#if filename || showCopy}
|
|
22
|
+
<div
|
|
23
|
+
class="px-4 py-2 border-b border-border flex items-center justify-between"
|
|
24
|
+
style="background: var(--color-base-2); color: var(--color-text-soft); font-family: var(--font-mono); font-size: 0.875rem;"
|
|
25
|
+
>
|
|
26
|
+
<span>{filename || ''}</span>
|
|
27
|
+
{#if showCopy}
|
|
28
|
+
<button
|
|
29
|
+
onclick={copyCode}
|
|
30
|
+
class="copy-button px-3 py-1 rounded-sm transition-all text-xs"
|
|
31
|
+
aria-label="Copy code"
|
|
32
|
+
>
|
|
33
|
+
{copied ? 'Copied!' : 'Copy'}
|
|
34
|
+
</button>
|
|
35
|
+
{/if}
|
|
36
|
+
</div>
|
|
37
|
+
{/if} <div
|
|
38
|
+
bind:this={codeContainer}
|
|
39
|
+
class="p-4 overflow-x-auto"
|
|
40
|
+
style="background: var(--color-base-1); font-family: var(--font-mono); font-size: 0.875rem; line-height: 1.6;"
|
|
41
|
+
>
|
|
42
|
+
{#if showLineNumbers}
|
|
43
|
+
<div class="flex">
|
|
44
|
+
<div
|
|
45
|
+
class="pr-4 border-r border-border select-none"
|
|
46
|
+
style="color: var(--color-text-muted);"
|
|
47
|
+
>
|
|
48
|
+
{#each Array.from({ length: lineCount }, (_, i) => i + 1) as lineNum}
|
|
49
|
+
<div
|
|
50
|
+
class:highlighted={highlightLines.includes(lineNum)}
|
|
51
|
+
style={highlightLines.includes(lineNum)
|
|
52
|
+
? 'background: var(--color-accent-overlay-10);'
|
|
53
|
+
: ''}
|
|
54
|
+
>
|
|
55
|
+
{lineNum}
|
|
56
|
+
</div>
|
|
57
|
+
{/each}
|
|
58
|
+
</div>
|
|
59
|
+
<div class="pl-4 flex-1">
|
|
60
|
+
{@render children?.()}
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
{:else}
|
|
64
|
+
<div>
|
|
65
|
+
{@render children?.()}
|
|
66
|
+
</div>
|
|
67
|
+
{/if}
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<style>
|
|
72
|
+
/* Custom scrollbar styling */
|
|
73
|
+
div::-webkit-scrollbar {
|
|
74
|
+
height: 8px;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
div::-webkit-scrollbar-track {
|
|
78
|
+
background: var(--color-base-1);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
div::-webkit-scrollbar-thumb {
|
|
82
|
+
background: var(--color-accent);
|
|
83
|
+
border-radius: var(--radius-sm);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
div::-webkit-scrollbar-thumb:hover {
|
|
87
|
+
background: var(--color-accent-soft);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Copy button styling */
|
|
91
|
+
.copy-button {
|
|
92
|
+
background: var(--color-base-3);
|
|
93
|
+
color: var(--color-text-soft);
|
|
94
|
+
border: 1px solid var(--color-border);
|
|
95
|
+
font-size: 0.75rem;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.copy-button:hover {
|
|
99
|
+
background: var(--color-accent);
|
|
100
|
+
color: var(--color-text);
|
|
101
|
+
border-color: var(--color-accent);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Highlight gutter numbers */
|
|
105
|
+
.highlighted {
|
|
106
|
+
background: var(--color-accent-overlay-10);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/*
|
|
110
|
+
* Consumer-provided line highlighting:
|
|
111
|
+
* Consumers should apply the `.line` class to each code line element
|
|
112
|
+
* and the `.highlighted` class to lines that should be highlighted.
|
|
113
|
+
* This ensures consistent styling with the component's luxury aesthetic.
|
|
114
|
+
*/
|
|
115
|
+
:global(.line.highlighted) {
|
|
116
|
+
background: var(--color-accent-overlay-10);
|
|
117
|
+
display: inline-block;
|
|
118
|
+
width: 100%;
|
|
119
|
+
}</style>
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
* Whether to show the copy button. Default: true.
|
|
27
|
+
*/
|
|
28
|
+
showCopy?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Raw code text for copying. If not provided, copy functionality attempts to extract text from the rendered content.
|
|
31
|
+
*/
|
|
32
|
+
code?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Slot for pre-highlighted code HTML.
|
|
35
|
+
*/
|
|
36
|
+
children?: import('svelte').Snippet;
|
|
37
|
+
}
|
|
38
|
+
declare const CodeBlock: import("svelte").Component<Props, {}, "">;
|
|
39
|
+
type CodeBlock = ReturnType<typeof CodeBlock>;
|
|
40
|
+
export default CodeBlock;
|