@joewinke/jatui 0.1.25 → 0.1.26
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/actions/collapse.d.ts +80 -0
- package/dist/actions/collapse.js +134 -0
- package/dist/actions/railNav.d.ts +167 -0
- package/dist/actions/railNav.js +351 -0
- package/dist/components/AnnotationLayer.svelte +108 -0
- package/dist/components/AnnotationLayer.svelte.d.ts +12 -0
- package/dist/components/AnnotationPanel.svelte +319 -0
- package/dist/components/AnnotationPanel.svelte.d.ts +13 -0
- package/dist/components/AudioWaveform.svelte +701 -0
- package/dist/components/AudioWaveform.svelte.d.ts +19 -0
- package/dist/components/AvailabilityModal.svelte +177 -0
- package/dist/components/AvailabilityModal.svelte.d.ts +14 -0
- package/dist/components/AvatarUpload.svelte +234 -0
- package/dist/components/AvatarUpload.svelte.d.ts +23 -0
- package/dist/components/Badge.svelte +38 -0
- package/dist/components/Badge.svelte.d.ts +14 -0
- package/dist/components/BookingForm.svelte +278 -0
- package/dist/components/BookingForm.svelte.d.ts +37 -0
- package/dist/components/BurndownChart.svelte +778 -0
- package/dist/components/BurndownChart.svelte.d.ts +17 -0
- package/dist/components/Button.svelte +81 -0
- package/dist/components/Button.svelte.d.ts +21 -0
- package/dist/components/CalendarPicker.svelte +284 -0
- package/dist/components/CalendarPicker.svelte.d.ts +22 -0
- package/dist/components/Card.svelte +67 -0
- package/dist/components/Card.svelte.d.ts +17 -0
- package/dist/components/CharacterCounter.svelte +82 -0
- package/dist/components/CharacterCounter.svelte.d.ts +10 -0
- package/dist/components/ChipInput.svelte +609 -0
- package/dist/components/ChipInput.svelte.d.ts +55 -0
- package/dist/components/Collapse.svelte +91 -0
- package/dist/components/Collapse.svelte.d.ts +18 -0
- package/dist/components/ColorSelector.svelte +167 -0
- package/dist/components/ColorSelector.svelte.d.ts +9 -0
- package/dist/components/CommentThread.svelte +773 -0
- package/dist/components/CommentThread.svelte.d.ts +32 -0
- package/dist/components/ConfirmDialog.svelte +348 -0
- package/dist/components/ConfirmDialog.svelte.d.ts +3 -0
- package/dist/components/ConfirmModal.svelte +142 -0
- package/dist/components/ConfirmModal.svelte.d.ts +15 -0
- package/dist/components/ContextMenu.svelte +188 -0
- package/dist/components/ContextMenu.svelte.d.ts +31 -0
- package/dist/components/CountdownTimer.svelte +94 -0
- package/dist/components/CountdownTimer.svelte.d.ts +10 -0
- package/dist/components/DateRangePicker.svelte +194 -0
- package/dist/components/DateRangePicker.svelte.d.ts +10 -0
- package/dist/components/Drawer.svelte +143 -0
- package/dist/components/Drawer.svelte.d.ts +33 -0
- package/dist/components/EmojiPicker.svelte +159 -0
- package/dist/components/EmojiPicker.svelte.d.ts +8 -0
- package/dist/components/EntityPreviewCard.svelte +104 -0
- package/dist/components/EntityPreviewCard.svelte.d.ts +9 -0
- package/dist/components/FileDropzone.svelte +493 -0
- package/dist/components/FileDropzone.svelte.d.ts +40 -0
- package/dist/components/FilePicker.svelte +488 -0
- package/dist/components/FilePicker.svelte.d.ts +20 -0
- package/dist/components/FileThumbnail.svelte +80 -0
- package/dist/components/FileThumbnail.svelte.d.ts +12 -0
- package/dist/components/FilterDropdown.svelte +203 -0
- package/dist/components/FilterDropdown.svelte.d.ts +48 -0
- package/dist/components/FloatingActionBar.svelte +354 -0
- package/dist/components/FloatingActionBar.svelte.d.ts +51 -0
- package/dist/components/GPSTracker.svelte +202 -0
- package/dist/components/GPSTracker.svelte.d.ts +34 -0
- package/dist/components/HunkDiffView.svelte +348 -0
- package/dist/components/HunkDiffView.svelte.d.ts +9 -0
- package/dist/components/ImageLightbox.svelte +274 -0
- package/dist/components/ImageLightbox.svelte.d.ts +25 -0
- package/dist/components/ImageUpload.svelte +147 -0
- package/dist/components/ImageUpload.svelte.d.ts +10 -0
- package/dist/components/InlineEdit.svelte +291 -0
- package/dist/components/InlineEdit.svelte.d.ts +45 -0
- package/dist/components/InputDialog.svelte +327 -0
- package/dist/components/InputDialog.svelte.d.ts +3 -0
- package/dist/components/KeyboardShortcutsOverlay.svelte +296 -0
- package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +42 -0
- package/dist/components/LazyImage.svelte +123 -0
- package/dist/components/LazyImage.svelte.d.ts +14 -0
- package/dist/components/LinkShortener.svelte +274 -0
- package/dist/components/LinkShortener.svelte.d.ts +27 -0
- package/dist/components/LoadingSpinner.svelte +106 -0
- package/dist/components/LoadingSpinner.svelte.d.ts +21 -0
- package/dist/components/LocationMap.svelte +186 -0
- package/dist/components/LocationMap.svelte.d.ts +11 -0
- package/dist/components/MapView.svelte +341 -0
- package/dist/components/MapView.svelte.d.ts +20 -0
- package/dist/components/MarkupEditor.svelte +485 -0
- package/dist/components/MarkupEditor.svelte.d.ts +10 -0
- package/dist/components/MarkupOverlay.svelte +55 -0
- package/dist/components/MarkupOverlay.svelte.d.ts +8 -0
- package/dist/components/MediaWorkbench.svelte +871 -0
- package/dist/components/MediaWorkbench.svelte.d.ts +9 -0
- package/dist/components/MilestoneCard.svelte +98 -0
- package/dist/components/MilestoneCard.svelte.d.ts +16 -0
- package/dist/components/MilestoneTimeline.svelte +91 -0
- package/dist/components/MilestoneTimeline.svelte.d.ts +15 -0
- package/dist/components/Modal.svelte +244 -0
- package/dist/components/Modal.svelte.d.ts +35 -0
- package/dist/components/PDFViewer.svelte +105 -0
- package/dist/components/PDFViewer.svelte.d.ts +9 -0
- package/dist/components/PdfThumbnail.svelte +63 -0
- package/dist/components/PdfThumbnail.svelte.d.ts +9 -0
- package/dist/components/PhoneInput.svelte +212 -0
- package/dist/components/PhoneInput.svelte.d.ts +25 -0
- package/dist/components/ResizableDivider.svelte +307 -0
- package/dist/components/ResizableDivider.svelte.d.ts +26 -0
- package/dist/components/ResizablePanel.svelte +305 -0
- package/dist/components/ResizablePanel.svelte.d.ts +18 -0
- package/dist/components/SearchDropdown.svelte +421 -0
- package/dist/components/SearchDropdown.svelte.d.ts +24 -0
- package/dist/components/SelectInput.svelte +237 -0
- package/dist/components/SelectInput.svelte.d.ts +26 -0
- package/dist/components/SidebarUserFooter.svelte +34 -0
- package/dist/components/SidebarUserFooter.svelte.d.ts +16 -0
- package/dist/components/SignaturePad.svelte +175 -0
- package/dist/components/SignaturePad.svelte.d.ts +17 -0
- package/dist/components/SmartImageEditor.svelte +720 -0
- package/dist/components/SmartImageEditor.svelte.d.ts +24 -0
- package/dist/components/SortDropdown.svelte +156 -0
- package/dist/components/SortDropdown.svelte.d.ts +40 -0
- package/dist/components/Sparkline.svelte +116 -0
- package/dist/components/Sparkline.svelte.d.ts +12 -0
- package/dist/components/SpeechForm.svelte +114 -0
- package/dist/components/SpeechForm.svelte.d.ts +17 -0
- package/dist/components/StatusBadge.svelte +157 -0
- package/dist/components/StatusBadge.svelte.d.ts +24 -0
- package/dist/components/TaskTypeIcon.svelte +54 -0
- package/dist/components/TaskTypeIcon.svelte.d.ts +17 -0
- package/dist/components/TextArea.svelte +162 -0
- package/dist/components/TextArea.svelte.d.ts +30 -0
- package/dist/components/TextInput.svelte +131 -0
- package/dist/components/TextInput.svelte.d.ts +29 -0
- package/dist/components/ThemeSelector.svelte +206 -0
- package/dist/components/ThemeSelector.svelte.d.ts +14 -0
- package/dist/components/TimeSlotPicker.svelte +162 -0
- package/dist/components/TimeSlotPicker.svelte.d.ts +18 -0
- package/dist/components/UserAvatar.svelte +72 -0
- package/dist/components/UserAvatar.svelte.d.ts +11 -0
- package/dist/components/VariablePicker.svelte +170 -0
- package/dist/components/VariablePicker.svelte.d.ts +40 -0
- package/dist/components/VoicePlayer.svelte +422 -0
- package/dist/components/VoicePlayer.svelte.d.ts +18 -0
- package/dist/components/linked-columns/LinkedColumns.svelte +526 -0
- package/dist/components/linked-columns/LinkedColumns.svelte.d.ts +130 -0
- package/dist/components/markup.d.ts +47 -0
- package/dist/components/markup.js +201 -0
- package/dist/components/messaging/Avatar.svelte +81 -0
- package/dist/components/messaging/Avatar.svelte.d.ts +11 -0
- package/dist/components/messaging/ChannelInfoModal.svelte +163 -0
- package/dist/components/messaging/ChannelInfoModal.svelte.d.ts +18 -0
- package/dist/components/messaging/ChannelList.svelte +107 -0
- package/dist/components/messaging/ChannelList.svelte.d.ts +11 -0
- package/dist/components/messaging/ChannelMemberAvatarStack.svelte +69 -0
- package/dist/components/messaging/ChannelMemberAvatarStack.svelte.d.ts +11 -0
- package/dist/components/messaging/ChannelMembersModal.svelte +182 -0
- package/dist/components/messaging/ChannelMembersModal.svelte.d.ts +16 -0
- package/dist/components/messaging/CreateChannelModal.svelte +190 -0
- package/dist/components/messaging/CreateChannelModal.svelte.d.ts +14 -0
- package/dist/components/messaging/DirectMessageList.svelte +145 -0
- package/dist/components/messaging/DirectMessageList.svelte.d.ts +12 -0
- package/dist/components/messaging/EmojiSelector.svelte +261 -0
- package/dist/components/messaging/EmojiSelector.svelte.d.ts +19 -0
- package/dist/components/messaging/MentionAutocomplete.svelte +193 -0
- package/dist/components/messaging/MentionAutocomplete.svelte.d.ts +17 -0
- package/dist/components/messaging/MessageAttachment.svelte +273 -0
- package/dist/components/messaging/MessageAttachment.svelte.d.ts +13 -0
- package/dist/components/messaging/MessageAttachmentUpload.svelte +243 -0
- package/dist/components/messaging/MessageAttachmentUpload.svelte.d.ts +19 -0
- package/dist/components/messaging/MessageInput.svelte +452 -0
- package/dist/components/messaging/MessageInput.svelte.d.ts +28 -0
- package/dist/components/messaging/MessageItem.svelte +341 -0
- package/dist/components/messaging/MessageItem.svelte.d.ts +41 -0
- package/dist/components/messaging/MessageThread.svelte +306 -0
- package/dist/components/messaging/MessageThread.svelte.d.ts +30 -0
- package/dist/components/messaging/NotificationSettingsModal.svelte +234 -0
- package/dist/components/messaging/NotificationSettingsModal.svelte.d.ts +13 -0
- package/dist/components/messaging/QuotedMessageDisplay.svelte +123 -0
- package/dist/components/messaging/QuotedMessageDisplay.svelte.d.ts +10 -0
- package/dist/components/messaging/StartDMModal.svelte +100 -0
- package/dist/components/messaging/StartDMModal.svelte.d.ts +14 -0
- package/dist/components/messaging/ThreadPanel.svelte +153 -0
- package/dist/components/messaging/ThreadPanel.svelte.d.ts +16 -0
- package/dist/components/pipeline/DESIGN.md +361 -0
- package/dist/components/pipeline/Pipeline.svelte +391 -0
- package/dist/components/pipeline/Pipeline.svelte.d.ts +28 -0
- package/dist/components/pipeline/PipelineCard.svelte +94 -0
- package/dist/components/pipeline/PipelineCard.svelte.d.ts +13 -0
- package/dist/components/pipeline/PipelineColumn.svelte +163 -0
- package/dist/components/pipeline/PipelineColumn.svelte.d.ts +30 -0
- package/dist/components/replay/ChapterTimeline.svelte +326 -0
- package/dist/components/replay/ChapterTimeline.svelte.d.ts +39 -0
- package/dist/components/session-nav/transcriptModel.d.ts +132 -0
- package/dist/components/session-nav/transcriptModel.js +285 -0
- package/dist/data/emojis.d.ts +18 -0
- package/dist/data/emojis.js +1196 -0
- package/dist/index.d.ts +142 -0
- package/dist/index.js +142 -0
- package/dist/stores/confirmDialog.svelte.d.ts +21 -0
- package/dist/stores/confirmDialog.svelte.js +31 -0
- package/dist/stores/inputDialog.svelte.d.ts +24 -0
- package/dist/stores/inputDialog.svelte.js +32 -0
- package/dist/styles/rail.css +63 -0
- package/dist/types/annotation.d.ts +32 -0
- package/dist/types/annotation.js +3 -0
- package/dist/types/booking.d.ts +115 -0
- package/dist/types/booking.js +24 -0
- package/dist/types/comments.d.ts +65 -0
- package/dist/types/comments.js +22 -0
- package/dist/types/entityPreview.d.ts +42 -0
- package/dist/types/entityPreview.js +10 -0
- package/dist/types/filePicker.d.ts +15 -0
- package/dist/types/filePicker.js +1 -0
- package/dist/types/googleMaps.d.ts +51 -0
- package/dist/types/maps.d.ts +43 -0
- package/dist/types/maps.js +4 -0
- package/dist/types/messaging.d.ts +282 -0
- package/dist/types/messaging.js +100 -0
- package/dist/types/milestone.d.ts +20 -0
- package/dist/types/milestone.js +7 -0
- package/dist/types/pipeline.d.ts +115 -0
- package/dist/types/pipeline.js +15 -0
- package/dist/types/smartImageEditor.d.ts +42 -0
- package/dist/types/smartImageEditor.js +1 -0
- package/dist/types/templateVars.d.ts +34 -0
- package/dist/types/templateVars.js +12 -0
- package/dist/utils/currency.d.ts +8 -0
- package/dist/utils/currency.js +17 -0
- package/dist/utils/daisyuiColors.d.ts +37 -0
- package/dist/utils/daisyuiColors.js +152 -0
- package/dist/utils/dateFormatters.d.ts +69 -0
- package/dist/utils/dateFormatters.js +199 -0
- package/dist/utils/googleMapsLoader.d.ts +19 -0
- package/dist/utils/googleMapsLoader.js +121 -0
- package/dist/utils/mentionParser.d.ts +67 -0
- package/dist/utils/mentionParser.js +132 -0
- package/dist/utils/phone.d.ts +13 -0
- package/dist/utils/phone.js +67 -0
- package/dist/utils/phoneFormat.d.ts +26 -0
- package/dist/utils/phoneFormat.js +69 -0
- package/dist/utils/taskUtils.d.ts +37 -0
- package/dist/utils/taskUtils.js +71 -0
- package/package.json +3 -2
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* ContextMenu Component
|
|
4
|
+
*
|
|
5
|
+
* Floating right-click (or programmatic) menu positioned at a cursor point.
|
|
6
|
+
* Headless primitive that handles positioning, viewport clamping, and
|
|
7
|
+
* dismiss behavior (click-outside, Escape, right-click elsewhere).
|
|
8
|
+
*
|
|
9
|
+
* Viewport clamping is measured from the rendered DOM (offsetWidth /
|
|
10
|
+
* offsetHeight) — no estimatedWidth/estimatedHeight props needed. The menu
|
|
11
|
+
* stays inside the viewport regardless of how many items are passed in.
|
|
12
|
+
* `transform-origin: top left` is set on the menu element so a caller's
|
|
13
|
+
* scale-based entrance animation (via `class`) cannot shift the visual
|
|
14
|
+
* rect away from the layout-box position used for clamping.
|
|
15
|
+
*
|
|
16
|
+
* Usage:
|
|
17
|
+
* let cm = $state({ open: false, x: 0, y: 0, target: null })
|
|
18
|
+
*
|
|
19
|
+
* <button oncontextmenu={(e) => {
|
|
20
|
+
* e.preventDefault()
|
|
21
|
+
* cm = { open: true, x: e.clientX, y: e.clientY, target: row }
|
|
22
|
+
* }}>Row</button>
|
|
23
|
+
*
|
|
24
|
+
* <ContextMenu
|
|
25
|
+
* bind:open={cm.open}
|
|
26
|
+
* x={cm.x}
|
|
27
|
+
* y={cm.y}
|
|
28
|
+
* header={cm.target?.name}
|
|
29
|
+
* items={[
|
|
30
|
+
* { label: "Preview", onSelect: () => preview(cm.target) },
|
|
31
|
+
* { label: "Copy URL", onSelect: () => copy(cm.target) },
|
|
32
|
+
* { divider: true },
|
|
33
|
+
* { label: "Delete", variant: "danger", onSelect: () => del(cm.target) },
|
|
34
|
+
* ]}
|
|
35
|
+
* />
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { scale, fade } from 'svelte/transition';
|
|
39
|
+
import { cubicOut } from 'svelte/easing';
|
|
40
|
+
|
|
41
|
+
type ActionItem = {
|
|
42
|
+
label: string;
|
|
43
|
+
onSelect: () => void;
|
|
44
|
+
variant?: 'default' | 'danger';
|
|
45
|
+
disabled?: boolean;
|
|
46
|
+
};
|
|
47
|
+
type DividerItem = { divider: true };
|
|
48
|
+
export type ContextMenuItem = ActionItem | DividerItem;
|
|
49
|
+
|
|
50
|
+
interface Props {
|
|
51
|
+
/** Whether the menu is currently shown. Bindable. */
|
|
52
|
+
open: boolean;
|
|
53
|
+
/** Viewport x coordinate to anchor the top-left corner to (e.g. event.clientX). */
|
|
54
|
+
x: number;
|
|
55
|
+
/** Viewport y coordinate to anchor the top-left corner to (e.g. event.clientY). */
|
|
56
|
+
y: number;
|
|
57
|
+
/** Ordered list of items and dividers to render. */
|
|
58
|
+
items: ContextMenuItem[];
|
|
59
|
+
/** Optional small italic label rendered above the items (e.g. a filename). */
|
|
60
|
+
header?: string;
|
|
61
|
+
/** Stacking context z-index. Default 70 (above DaisyUI modals at 50). */
|
|
62
|
+
zIndex?: number;
|
|
63
|
+
/** Additional CSS class for the menu element. */
|
|
64
|
+
class?: string;
|
|
65
|
+
/** Called after the menu closes, regardless of cause. */
|
|
66
|
+
onClose?: () => void;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let {
|
|
70
|
+
open = $bindable(false),
|
|
71
|
+
x,
|
|
72
|
+
y,
|
|
73
|
+
items,
|
|
74
|
+
header,
|
|
75
|
+
zIndex = 70,
|
|
76
|
+
class: className = '',
|
|
77
|
+
onClose
|
|
78
|
+
}: Props = $props();
|
|
79
|
+
|
|
80
|
+
function close() {
|
|
81
|
+
if (!open) return;
|
|
82
|
+
open = false;
|
|
83
|
+
onClose?.();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function handleSelect(item: ActionItem) {
|
|
87
|
+
if (item.disabled) return;
|
|
88
|
+
item.onSelect();
|
|
89
|
+
close();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Position is measured from the rendered menu so it stays correct as items
|
|
93
|
+
// are added/removed. Initial values match the cursor; the post-render
|
|
94
|
+
// effect below corrects them once the layout box is known.
|
|
95
|
+
let menuEl = $state<HTMLDivElement | null>(null);
|
|
96
|
+
// svelte-ignore state_referenced_locally
|
|
97
|
+
let clampedX = $state(x);
|
|
98
|
+
// svelte-ignore state_referenced_locally
|
|
99
|
+
let clampedY = $state(y);
|
|
100
|
+
|
|
101
|
+
function measureAndClamp() {
|
|
102
|
+
if (typeof window === 'undefined' || !menuEl) return;
|
|
103
|
+
// offset* gives the layout box, ignoring any transform: scale() the
|
|
104
|
+
// caller might apply via a className. getBoundingClientRect would
|
|
105
|
+
// return the post-transform rect and underestimate during entrance
|
|
106
|
+
// animations.
|
|
107
|
+
const w = menuEl.offsetWidth;
|
|
108
|
+
const h = menuEl.offsetHeight;
|
|
109
|
+
const margin = 8;
|
|
110
|
+
clampedX =
|
|
111
|
+
x + w > window.innerWidth - margin
|
|
112
|
+
? Math.max(margin, window.innerWidth - w - margin)
|
|
113
|
+
: Math.max(margin, x);
|
|
114
|
+
clampedY =
|
|
115
|
+
y + h > window.innerHeight - margin
|
|
116
|
+
? Math.max(margin, window.innerHeight - h - margin)
|
|
117
|
+
: Math.max(margin, y);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Re-measure whenever the menu opens, the anchor moves, or the item list
|
|
121
|
+
// changes (which can change the menu height).
|
|
122
|
+
$effect(() => {
|
|
123
|
+
if (!open) return;
|
|
124
|
+
// Touch reactive deps so the effect re-runs.
|
|
125
|
+
void x; void y; void items.length;
|
|
126
|
+
// Wait for the menu element to mount before measuring.
|
|
127
|
+
if (menuEl) {
|
|
128
|
+
measureAndClamp();
|
|
129
|
+
} else {
|
|
130
|
+
// Element not mounted yet — fall back to cursor position; the next
|
|
131
|
+
// effect tick will fire once `bind:this` resolves.
|
|
132
|
+
clampedX = x;
|
|
133
|
+
clampedY = y;
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
</script>
|
|
137
|
+
|
|
138
|
+
{#if open}
|
|
139
|
+
<!-- Invisible full-viewport overlay — captures click/right-click to dismiss -->
|
|
140
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
141
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
142
|
+
<div
|
|
143
|
+
class="fixed inset-0"
|
|
144
|
+
style="z-index: {zIndex - 1};"
|
|
145
|
+
onclick={close}
|
|
146
|
+
oncontextmenu={(e) => {
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
close();
|
|
149
|
+
}}
|
|
150
|
+
></div>
|
|
151
|
+
|
|
152
|
+
<div
|
|
153
|
+
bind:this={menuEl}
|
|
154
|
+
role="menu"
|
|
155
|
+
tabindex="-1"
|
|
156
|
+
class="fixed min-w-[200px] bg-base-200 border border-base-300 rounded-md py-1 {className}"
|
|
157
|
+
style="left: {clampedX}px; top: {clampedY}px; z-index: {zIndex}; transform-origin: top left;"
|
|
158
|
+
in:scale={{ start: 0.92, duration: 140, easing: cubicOut }}
|
|
159
|
+
out:fade={{ duration: 100 }}
|
|
160
|
+
onkeydown={(e) => e.key === 'Escape' && close()}
|
|
161
|
+
>
|
|
162
|
+
{#if header}
|
|
163
|
+
<p
|
|
164
|
+
class="px-3 py-1.5 text-[11px] italic text-base-content/60 truncate border-b border-base-300"
|
|
165
|
+
>
|
|
166
|
+
{header}
|
|
167
|
+
</p>
|
|
168
|
+
{/if}
|
|
169
|
+
|
|
170
|
+
{#each items as item}
|
|
171
|
+
{#if 'divider' in item}
|
|
172
|
+
<div class="border-t border-base-300 my-1"></div>
|
|
173
|
+
{:else}
|
|
174
|
+
<button
|
|
175
|
+
type="button"
|
|
176
|
+
role="menuitem"
|
|
177
|
+
class="w-full text-left px-3 py-1.5 text-sm transition-colors
|
|
178
|
+
{item.variant === 'danger' ? 'text-error hover:bg-error/10' : 'hover:bg-base-200'}
|
|
179
|
+
{item.disabled ? 'opacity-50 cursor-not-allowed' : ''}"
|
|
180
|
+
disabled={item.disabled}
|
|
181
|
+
onclick={() => handleSelect(item)}
|
|
182
|
+
>
|
|
183
|
+
{item.label}
|
|
184
|
+
</button>
|
|
185
|
+
{/if}
|
|
186
|
+
{/each}
|
|
187
|
+
</div>
|
|
188
|
+
{/if}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
type ActionItem = {
|
|
2
|
+
label: string;
|
|
3
|
+
onSelect: () => void;
|
|
4
|
+
variant?: 'default' | 'danger';
|
|
5
|
+
disabled?: boolean;
|
|
6
|
+
};
|
|
7
|
+
type DividerItem = {
|
|
8
|
+
divider: true;
|
|
9
|
+
};
|
|
10
|
+
export type ContextMenuItem = ActionItem | DividerItem;
|
|
11
|
+
interface Props {
|
|
12
|
+
/** Whether the menu is currently shown. Bindable. */
|
|
13
|
+
open: boolean;
|
|
14
|
+
/** Viewport x coordinate to anchor the top-left corner to (e.g. event.clientX). */
|
|
15
|
+
x: number;
|
|
16
|
+
/** Viewport y coordinate to anchor the top-left corner to (e.g. event.clientY). */
|
|
17
|
+
y: number;
|
|
18
|
+
/** Ordered list of items and dividers to render. */
|
|
19
|
+
items: ContextMenuItem[];
|
|
20
|
+
/** Optional small italic label rendered above the items (e.g. a filename). */
|
|
21
|
+
header?: string;
|
|
22
|
+
/** Stacking context z-index. Default 70 (above DaisyUI modals at 50). */
|
|
23
|
+
zIndex?: number;
|
|
24
|
+
/** Additional CSS class for the menu element. */
|
|
25
|
+
class?: string;
|
|
26
|
+
/** Called after the menu closes, regardless of cause. */
|
|
27
|
+
onClose?: () => void;
|
|
28
|
+
}
|
|
29
|
+
declare const ContextMenu: import("svelte").Component<Props, {}, "open">;
|
|
30
|
+
type ContextMenu = ReturnType<typeof ContextMenu>;
|
|
31
|
+
export default ContextMenu;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fade } from "svelte/transition"
|
|
3
|
+
|
|
4
|
+
type TimerProps = {
|
|
5
|
+
targetDate?: string | null
|
|
6
|
+
minutesToAdd?: number
|
|
7
|
+
onComplete?: () => void
|
|
8
|
+
size?: "sm" | "md" | "lg"
|
|
9
|
+
completionMessage?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
targetDate = null,
|
|
14
|
+
minutesToAdd = 1,
|
|
15
|
+
onComplete = () => {},
|
|
16
|
+
size = "sm",
|
|
17
|
+
completionMessage = "Complete",
|
|
18
|
+
}: TimerProps = $props()
|
|
19
|
+
|
|
20
|
+
let minutes = $state(0)
|
|
21
|
+
let seconds = $state(0)
|
|
22
|
+
let countdownInterval: number | null = null
|
|
23
|
+
let startTime: Date | null = null
|
|
24
|
+
let isComplete = $state(false)
|
|
25
|
+
|
|
26
|
+
const sizeClasses: Record<"sm" | "md" | "lg", string> = {
|
|
27
|
+
sm: "text-lg",
|
|
28
|
+
md: "text-2xl",
|
|
29
|
+
lg: "text-4xl",
|
|
30
|
+
}
|
|
31
|
+
let sizeClass = $derived(sizeClasses[size])
|
|
32
|
+
|
|
33
|
+
function updateCountdown() {
|
|
34
|
+
const now = new Date()
|
|
35
|
+
let targetTime: Date
|
|
36
|
+
|
|
37
|
+
if (targetDate) {
|
|
38
|
+
const finishTime = new Date(targetDate)
|
|
39
|
+
targetTime = new Date(finishTime.getTime() + minutesToAdd * 60 * 1000)
|
|
40
|
+
} else {
|
|
41
|
+
if (!startTime) {
|
|
42
|
+
startTime = now
|
|
43
|
+
}
|
|
44
|
+
targetTime = new Date(startTime.getTime() + minutesToAdd * 60 * 1000)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const diff = targetTime.getTime() - now.getTime()
|
|
48
|
+
|
|
49
|
+
if (diff <= 0) {
|
|
50
|
+
if (countdownInterval) {
|
|
51
|
+
clearInterval(countdownInterval)
|
|
52
|
+
countdownInterval = null
|
|
53
|
+
}
|
|
54
|
+
minutes = 0
|
|
55
|
+
seconds = 0
|
|
56
|
+
isComplete = true
|
|
57
|
+
onComplete()
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
minutes = Math.floor(diff / (1000 * 60))
|
|
62
|
+
seconds = Math.floor((diff % (1000 * 60)) / 1000)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
$effect(() => {
|
|
66
|
+
if (targetDate || minutesToAdd > 0) {
|
|
67
|
+
updateCountdown()
|
|
68
|
+
countdownInterval = setInterval(updateCountdown, 1000) as unknown as number
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return () => {
|
|
72
|
+
if (countdownInterval) {
|
|
73
|
+
clearInterval(countdownInterval)
|
|
74
|
+
countdownInterval = null
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<div class="text-info">
|
|
81
|
+
{#if !isComplete}
|
|
82
|
+
EST.
|
|
83
|
+
<span class="countdown font-mono {sizeClass}" transition:fade>
|
|
84
|
+
<span style="--value:{minutes};"></span>:
|
|
85
|
+
<span style="--value:{seconds};"></span>
|
|
86
|
+
REMAINING
|
|
87
|
+
</span>
|
|
88
|
+
{:else}
|
|
89
|
+
<span class="countdown {sizeClass}" transition:fade>
|
|
90
|
+
<span class="loading loading-dots loading-md mr-3"></span>
|
|
91
|
+
{completionMessage}
|
|
92
|
+
</span>
|
|
93
|
+
{/if}
|
|
94
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type TimerProps = {
|
|
2
|
+
targetDate?: string | null;
|
|
3
|
+
minutesToAdd?: number;
|
|
4
|
+
onComplete?: () => void;
|
|
5
|
+
size?: "sm" | "md" | "lg";
|
|
6
|
+
completionMessage?: string;
|
|
7
|
+
};
|
|
8
|
+
declare const CountdownTimer: import("svelte").Component<TimerProps, {}, "">;
|
|
9
|
+
type CountdownTimer = ReturnType<typeof CountdownTimer>;
|
|
10
|
+
export default CountdownTimer;
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* DateRangePicker Component
|
|
4
|
+
* DaisyUI dropdown for filtering by date range with quick presets
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { formatShortDate } from '../utils/dateFormatters';
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
selectedRange: string; // 'today' | 'yesterday' | 'week' | 'month' | 'all' | 'custom'
|
|
11
|
+
customFrom?: string | null; // ISO date for custom range
|
|
12
|
+
customTo?: string | null; // ISO date for custom range
|
|
13
|
+
onRangeChange: (range: string, from?: string, to?: string) => void;
|
|
14
|
+
compact?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
let {
|
|
18
|
+
selectedRange = 'all',
|
|
19
|
+
customFrom = null,
|
|
20
|
+
customTo = null,
|
|
21
|
+
onRangeChange,
|
|
22
|
+
compact = false
|
|
23
|
+
}: Props = $props();
|
|
24
|
+
|
|
25
|
+
// Local state for custom date inputs
|
|
26
|
+
// svelte-ignore state_referenced_locally
|
|
27
|
+
let localFrom = $state(customFrom || '');
|
|
28
|
+
// svelte-ignore state_referenced_locally
|
|
29
|
+
let localTo = $state(customTo || '');
|
|
30
|
+
let showCustom = $state(false);
|
|
31
|
+
|
|
32
|
+
// Preset options
|
|
33
|
+
const presets = [
|
|
34
|
+
{ value: 'today', label: 'Today', icon: '1' },
|
|
35
|
+
{ value: 'yesterday', label: 'Yesterday', icon: '-1' },
|
|
36
|
+
{ value: 'week', label: 'Last 7 days', icon: '7' },
|
|
37
|
+
{ value: 'month', label: 'Last 30 days', icon: '30' },
|
|
38
|
+
{ value: 'all', label: 'All time', icon: '*' }
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
// Get display label for current selection
|
|
42
|
+
const displayLabel = $derived.by(() => {
|
|
43
|
+
if (selectedRange === 'custom' && customFrom && customTo) {
|
|
44
|
+
return `${formatShortDate(customFrom)} - ${formatShortDate(customTo)}`;
|
|
45
|
+
}
|
|
46
|
+
if (selectedRange === 'custom' && customFrom) {
|
|
47
|
+
return `From ${formatShortDate(customFrom)}`;
|
|
48
|
+
}
|
|
49
|
+
if (selectedRange === 'custom' && customTo) {
|
|
50
|
+
return `Until ${formatShortDate(customTo)}`;
|
|
51
|
+
}
|
|
52
|
+
return presets.find(p => p.value === selectedRange)?.label || 'All time';
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function handlePresetSelect(preset: string) {
|
|
56
|
+
showCustom = false;
|
|
57
|
+
localFrom = '';
|
|
58
|
+
localTo = '';
|
|
59
|
+
onRangeChange(preset);
|
|
60
|
+
|
|
61
|
+
// Close dropdown
|
|
62
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
63
|
+
document.activeElement.blur();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function handleCustomApply() {
|
|
68
|
+
if (localFrom || localTo) {
|
|
69
|
+
onRangeChange('custom', localFrom || undefined, localTo || undefined);
|
|
70
|
+
|
|
71
|
+
// Close dropdown
|
|
72
|
+
if (document.activeElement instanceof HTMLElement) {
|
|
73
|
+
document.activeElement.blur();
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function toggleCustom() {
|
|
79
|
+
showCustom = !showCustom;
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<!-- Date Range Picker -->
|
|
84
|
+
<div class="dropdown dropdown-end">
|
|
85
|
+
<!-- Trigger Button -->
|
|
86
|
+
<div
|
|
87
|
+
tabindex="0"
|
|
88
|
+
role="button"
|
|
89
|
+
class="{compact ? 'px-2 py-1 text-[10px]' : 'px-2.5 py-1.5 text-xs'} rounded cursor-pointer transition-all flex items-center gap-1.5 font-mono tracking-wider whitespace-nowrap bg-base-200 border border-base-300 text-base-content/60"
|
|
90
|
+
>
|
|
91
|
+
<!-- Calendar icon -->
|
|
92
|
+
<svg
|
|
93
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
94
|
+
fill="none"
|
|
95
|
+
viewBox="0 0 24 24"
|
|
96
|
+
stroke-width="1.5"
|
|
97
|
+
stroke="currentColor"
|
|
98
|
+
class="w-4 h-4 text-primary"
|
|
99
|
+
>
|
|
100
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" />
|
|
101
|
+
</svg>
|
|
102
|
+
<span class="hidden sm:inline">{displayLabel}</span>
|
|
103
|
+
<span class="sm:hidden">
|
|
104
|
+
{#if selectedRange === 'today'}
|
|
105
|
+
1d
|
|
106
|
+
{:else if selectedRange === 'yesterday'}
|
|
107
|
+
-1d
|
|
108
|
+
{:else if selectedRange === 'week'}
|
|
109
|
+
7d
|
|
110
|
+
{:else if selectedRange === 'month'}
|
|
111
|
+
30d
|
|
112
|
+
{:else if selectedRange === 'all'}
|
|
113
|
+
All
|
|
114
|
+
{:else}
|
|
115
|
+
Custom
|
|
116
|
+
{/if}
|
|
117
|
+
</span>
|
|
118
|
+
<svg
|
|
119
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
120
|
+
fill="none"
|
|
121
|
+
viewBox="0 0 24 24"
|
|
122
|
+
stroke-width="1.5"
|
|
123
|
+
stroke="currentColor"
|
|
124
|
+
class="w-3 h-3 text-base-content/50"
|
|
125
|
+
>
|
|
126
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 8.25l-7.5 7.5-7.5-7.5" />
|
|
127
|
+
</svg>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<!-- Dropdown Content -->
|
|
131
|
+
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
132
|
+
<div
|
|
133
|
+
tabindex="0"
|
|
134
|
+
class="dropdown-content rounded-box z-40 w-72 p-3 mt-1 bg-base-200 border border-base-300"
|
|
135
|
+
>
|
|
136
|
+
<!-- Quick Presets -->
|
|
137
|
+
<div class="flex flex-wrap gap-1.5 mb-3">
|
|
138
|
+
{#each presets as preset}
|
|
139
|
+
<button
|
|
140
|
+
type="button"
|
|
141
|
+
class="px-2 py-1 rounded cursor-pointer transition-all duration-200 font-mono text-xs border {selectedRange === preset.value ? 'bg-primary/20 border-primary text-primary' : 'bg-base-300 border-base-content/20 text-base-content opacity-60 hover:opacity-100'}"
|
|
142
|
+
onclick={() => handlePresetSelect(preset.value)}
|
|
143
|
+
>
|
|
144
|
+
{preset.label}
|
|
145
|
+
</button>
|
|
146
|
+
{/each}
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
<!-- Divider -->
|
|
150
|
+
<div class="my-2 flex items-center gap-2">
|
|
151
|
+
<div class="flex-1 h-px bg-base-300"></div>
|
|
152
|
+
<span class="text-[0.75rem] text-base-content/45">or custom</span>
|
|
153
|
+
<div class="flex-1 h-px bg-base-300"></div>
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<!-- Custom Range Section -->
|
|
157
|
+
<div class="space-y-2">
|
|
158
|
+
<div class="flex gap-2">
|
|
159
|
+
<div class="form-control flex-1">
|
|
160
|
+
<label class="label py-0.5" for="date-range-from">
|
|
161
|
+
<span class="text-[0.8125rem] text-base-content/45">From</span>
|
|
162
|
+
</label>
|
|
163
|
+
<input
|
|
164
|
+
id="date-range-from"
|
|
165
|
+
type="date"
|
|
166
|
+
class="input input-sm w-full font-mono bg-base-300 border border-base-300 text-base-content"
|
|
167
|
+
bind:value={localFrom}
|
|
168
|
+
max={localTo || undefined}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
<div class="form-control flex-1">
|
|
172
|
+
<label class="label py-0.5" for="date-range-to">
|
|
173
|
+
<span class="text-[0.8125rem] text-base-content/45">To</span>
|
|
174
|
+
</label>
|
|
175
|
+
<input
|
|
176
|
+
id="date-range-to"
|
|
177
|
+
type="date"
|
|
178
|
+
class="input input-sm w-full font-mono bg-base-300 border border-base-300 text-base-content"
|
|
179
|
+
bind:value={localTo}
|
|
180
|
+
min={localFrom || undefined}
|
|
181
|
+
/>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
class="btn btn-sm btn-primary w-full font-mono"
|
|
187
|
+
onclick={handleCustomApply}
|
|
188
|
+
disabled={!localFrom && !localTo}
|
|
189
|
+
>
|
|
190
|
+
Apply Custom Range
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
interface Props {
|
|
2
|
+
selectedRange: string;
|
|
3
|
+
customFrom?: string | null;
|
|
4
|
+
customTo?: string | null;
|
|
5
|
+
onRangeChange: (range: string, from?: string, to?: string) => void;
|
|
6
|
+
compact?: boolean;
|
|
7
|
+
}
|
|
8
|
+
declare const DateRangePicker: import("svelte").Component<Props, {}, "">;
|
|
9
|
+
type DateRangePicker = ReturnType<typeof DateRangePicker>;
|
|
10
|
+
export default DateRangePicker;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Drawer Component
|
|
4
|
+
*
|
|
5
|
+
* Sliding panel overlay with backdrop, escape key, and header actions.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Snippet } from 'svelte';
|
|
9
|
+
|
|
10
|
+
interface Props {
|
|
11
|
+
open?: boolean;
|
|
12
|
+
title?: string;
|
|
13
|
+
position?: 'left' | 'right';
|
|
14
|
+
size?: 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | 'full';
|
|
15
|
+
showCloseButton?: boolean;
|
|
16
|
+
contentClass?: string;
|
|
17
|
+
/**
|
|
18
|
+
* When true (default), keydown events fired inside the drawer panel are
|
|
19
|
+
* stopped from propagating to window — preventing page-level list/keyboard
|
|
20
|
+
* navigation handlers (listNav-style) from receiving arrow keys, j/k, vim
|
|
21
|
+
* chords, digit counts, etc. while the drawer is open.
|
|
22
|
+
*
|
|
23
|
+
* Free-text fields (INPUT, TEXTAREA, contentEditable) are intentionally
|
|
24
|
+
* excluded so normal typing still works. Set to false only when the
|
|
25
|
+
* consumer needs custom window-level key handling even while the drawer
|
|
26
|
+
* is open.
|
|
27
|
+
*/
|
|
28
|
+
trapKeys?: boolean;
|
|
29
|
+
onclose?: () => void;
|
|
30
|
+
class?: string;
|
|
31
|
+
headerActions?: Snippet;
|
|
32
|
+
children?: Snippet;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let {
|
|
36
|
+
open = $bindable(false),
|
|
37
|
+
title = '',
|
|
38
|
+
position = 'right',
|
|
39
|
+
size = 'md',
|
|
40
|
+
showCloseButton = true,
|
|
41
|
+
contentClass = '',
|
|
42
|
+
trapKeys = true,
|
|
43
|
+
onclose,
|
|
44
|
+
class: className = '',
|
|
45
|
+
headerActions,
|
|
46
|
+
children
|
|
47
|
+
}: Props = $props();
|
|
48
|
+
|
|
49
|
+
const sizeClasses: Record<string, string> = {
|
|
50
|
+
sm: 'w-80',
|
|
51
|
+
md: 'w-96',
|
|
52
|
+
lg: 'w-[640px]',
|
|
53
|
+
xl: 'w-[800px]',
|
|
54
|
+
'2xl': 'w-[960px]',
|
|
55
|
+
'3xl': 'w-[1024px] max-w-[calc(100vw-2rem)]',
|
|
56
|
+
full: 'w-full'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function handleClose() {
|
|
60
|
+
open = false;
|
|
61
|
+
onclose?.();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
65
|
+
if (e.key === 'Escape' && open) {
|
|
66
|
+
e.stopImmediatePropagation();
|
|
67
|
+
handleClose();
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Stop keydown events from bubbling to window while the drawer is open.
|
|
73
|
+
* This prevents page-level keyboard navigation (listNav-style handlers on
|
|
74
|
+
* window) from reacting to arrow keys, j/k, vim chords, digit counts, etc.
|
|
75
|
+
* typed inside the drawer.
|
|
76
|
+
*
|
|
77
|
+
* Free-text fields are excluded: page-level handlers already ignore them,
|
|
78
|
+
* and we must not interfere with normal typing or textarea shortcuts.
|
|
79
|
+
*/
|
|
80
|
+
function handlePanelKeydown(e: KeyboardEvent) {
|
|
81
|
+
if (!trapKeys) return;
|
|
82
|
+
const el = document.activeElement as HTMLElement | null;
|
|
83
|
+
const tag = el?.tagName;
|
|
84
|
+
const isTextField =
|
|
85
|
+
!!el && (tag === 'INPUT' || tag === 'TEXTAREA' || el.isContentEditable === true);
|
|
86
|
+
if (!isTextField) e.stopPropagation();
|
|
87
|
+
}
|
|
88
|
+
</script>
|
|
89
|
+
|
|
90
|
+
<svelte:window onkeydown={handleKeydown} />
|
|
91
|
+
|
|
92
|
+
<div
|
|
93
|
+
class="fixed inset-0 transition-opacity duration-300 {open
|
|
94
|
+
? 'z-[100] opacity-100'
|
|
95
|
+
: 'opacity-0 pointer-events-none -z-10'}"
|
|
96
|
+
style="background: color-mix(in oklch, var(--color-primary) 55%, transparent)"
|
|
97
|
+
onclick={open ? handleClose : undefined}
|
|
98
|
+
role={open ? 'presentation' : undefined}
|
|
99
|
+
></div>
|
|
100
|
+
|
|
101
|
+
<div
|
|
102
|
+
class="fixed top-0 {position === 'right'
|
|
103
|
+
? 'right-0'
|
|
104
|
+
: 'left-0'} h-full bg-base-200 {position === 'right' ? 'border-l' : 'border-r'} border-base-300 {sizeClasses[size]} flex flex-col transition-all duration-300 {open
|
|
105
|
+
? 'z-[101] translate-x-0'
|
|
106
|
+
: '-z-10 ' + (position === 'right' ? 'translate-x-full' : '-translate-x-full')} {className}"
|
|
107
|
+
role="dialog"
|
|
108
|
+
aria-modal={open}
|
|
109
|
+
aria-label={title}
|
|
110
|
+
aria-hidden={!open}
|
|
111
|
+
onkeydown={handlePanelKeydown}
|
|
112
|
+
>
|
|
113
|
+
{#if title || showCloseButton || headerActions}
|
|
114
|
+
<div class="flex items-center justify-between p-6 border-b border-base-content/10 flex-shrink-0">
|
|
115
|
+
{#if title}
|
|
116
|
+
<h2 class="text-xl font-semibold">{title}</h2>
|
|
117
|
+
{/if}
|
|
118
|
+
<div class="flex items-center gap-2 ml-auto">
|
|
119
|
+
{#if headerActions}
|
|
120
|
+
{@render headerActions()}
|
|
121
|
+
{/if}
|
|
122
|
+
{#if showCloseButton}
|
|
123
|
+
<button
|
|
124
|
+
type="button"
|
|
125
|
+
class="btn btn-sm btn-circle btn-ghost"
|
|
126
|
+
onclick={handleClose}
|
|
127
|
+
aria-label="Close drawer"
|
|
128
|
+
>
|
|
129
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
130
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
131
|
+
</svg>
|
|
132
|
+
</button>
|
|
133
|
+
{/if}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
{/if}
|
|
137
|
+
|
|
138
|
+
<div class="flex-1 overflow-y-auto {contentClass}">
|
|
139
|
+
{#if open && children}
|
|
140
|
+
{@render children()}
|
|
141
|
+
{/if}
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|