@joewinke/jatui 0.1.11 → 0.1.19
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/README.md +123 -0
- package/package.json +2 -1
- package/src/lib/actions/railNav.ts +473 -0
- package/src/lib/components/AnnotationLayer.svelte +108 -0
- package/src/lib/components/AnnotationPanel.svelte +319 -0
- package/src/lib/components/AudioWaveform.svelte +9 -5
- package/src/lib/components/AvailabilityModal.svelte +7 -3
- package/src/lib/components/AvatarUpload.svelte +27 -4
- package/src/lib/components/BookingForm.svelte +11 -9
- package/src/lib/components/BurndownChart.svelte +778 -0
- package/src/lib/components/Button.svelte +10 -1
- package/src/lib/components/CalendarPicker.svelte +3 -3
- package/src/lib/components/Card.svelte +2 -2
- package/src/lib/components/ChipInput.svelte +21 -15
- package/src/lib/components/ColorSelector.svelte +17 -13
- package/src/lib/components/CommentThread.svelte +773 -0
- package/src/lib/components/ConfirmDialog.svelte +348 -0
- package/src/lib/components/ConfirmModal.svelte +78 -11
- package/src/lib/components/ContextMenu.svelte +59 -19
- package/src/lib/components/CountdownTimer.svelte +1 -1
- package/src/lib/components/DateRangePicker.svelte +6 -4
- package/src/lib/components/Drawer.svelte +36 -3
- package/src/lib/components/EntityPreviewCard.svelte +104 -0
- package/src/lib/components/FileDropzone.svelte +493 -0
- package/src/lib/components/FilePicker.svelte +83 -14
- package/src/lib/components/FileThumbnail.svelte +80 -0
- package/src/lib/components/FilterDropdown.svelte +11 -11
- package/src/lib/components/HunkDiffView.svelte +348 -0
- package/src/lib/components/ImageLightbox.svelte +274 -0
- package/src/lib/components/ImageUpload.svelte +58 -9
- package/src/lib/components/InlineEdit.svelte +15 -9
- package/src/lib/components/InputDialog.svelte +327 -0
- package/src/lib/components/LazyImage.svelte +1 -0
- package/src/lib/components/LinkShortener.svelte +1 -1
- package/src/lib/components/LoadingSpinner.svelte +6 -2
- package/src/lib/components/MarkupEditor.svelte +485 -0
- package/src/lib/components/MarkupOverlay.svelte +55 -0
- package/src/lib/components/MediaWorkbench.svelte +871 -0
- package/src/lib/components/MilestoneCard.svelte +1 -1
- package/src/lib/components/MilestoneTimeline.svelte +1 -1
- package/src/lib/components/Modal.svelte +39 -4
- package/src/lib/components/PDFViewer.svelte +105 -0
- package/src/lib/components/PdfThumbnail.svelte +3 -1
- package/src/lib/components/PhoneInput.svelte +1 -1
- package/src/lib/components/ResizablePanel.svelte +4 -4
- package/src/lib/components/SearchDropdown.svelte +26 -13
- package/src/lib/components/SelectInput.svelte +26 -4
- package/src/lib/components/SidebarUserFooter.svelte +1 -1
- package/src/lib/components/SignaturePad.svelte +8 -4
- package/src/lib/components/SmartImageEditor.svelte +720 -0
- package/src/lib/components/SortDropdown.svelte +9 -3
- package/src/lib/components/Sparkline.svelte +9 -0
- package/src/lib/components/StatusBadge.svelte +20 -18
- package/src/lib/components/TextArea.svelte +24 -5
- package/src/lib/components/TextInput.svelte +29 -6
- package/src/lib/components/ThemeSelector.svelte +15 -4
- package/src/lib/components/TimeSlotPicker.svelte +7 -7
- package/src/lib/components/UserAvatar.svelte +14 -1
- package/src/lib/components/VariablePicker.svelte +170 -0
- package/src/lib/components/VoicePlayer.svelte +4 -3
- package/src/lib/components/markup.ts +287 -0
- package/src/lib/components/messaging/ChannelInfoModal.svelte +9 -9
- package/src/lib/components/messaging/ChannelList.svelte +1 -1
- package/src/lib/components/messaging/ChannelMembersModal.svelte +1 -1
- package/src/lib/components/messaging/CreateChannelModal.svelte +1 -1
- package/src/lib/components/messaging/DirectMessageList.svelte +1 -1
- package/src/lib/components/messaging/EmojiSelector.svelte +2 -1
- package/src/lib/components/messaging/MentionAutocomplete.svelte +1 -1
- package/src/lib/components/messaging/MessageAttachment.svelte +3 -3
- package/src/lib/components/messaging/MessageAttachmentUpload.svelte +3 -3
- package/src/lib/components/messaging/MessageInput.svelte +1 -1
- package/src/lib/components/messaging/MessageItem.svelte +6 -3
- package/src/lib/components/messaging/NotificationSettingsModal.svelte +1 -1
- package/src/lib/components/messaging/QuotedMessageDisplay.svelte +6 -1
- package/src/lib/components/messaging/StartDMModal.svelte +1 -1
- package/src/lib/components/pipeline/Pipeline.svelte +4 -4
- package/src/lib/components/pipeline/PipelineCard.svelte +1 -1
- package/src/lib/components/pipeline/PipelineColumn.svelte +8 -3
- package/src/lib/index.ts +91 -0
- package/src/lib/stores/confirmDialog.svelte.ts +48 -0
- package/src/lib/stores/inputDialog.svelte.ts +51 -0
- package/src/lib/styles/rail.css +63 -0
- package/src/lib/types/annotation.ts +38 -0
- package/src/lib/types/comments.ts +97 -0
- package/src/lib/types/entityPreview.ts +45 -0
- package/src/lib/types/filePicker.ts +2 -0
- package/src/lib/types/smartImageEditor.ts +39 -0
- package/src/lib/types/templateVars.ts +36 -0
- package/src/lib/utils/dateFormatters.ts +12 -10
- package/src/lib/utils/taskUtils.ts +21 -7
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
<div class="space-y-1">
|
|
36
36
|
<!-- Progress summary -->
|
|
37
|
-
<div class="flex items-center justify-between text-
|
|
37
|
+
<div class="flex items-center justify-between text-[0.9375rem] mb-4">
|
|
38
38
|
<span class="text-base-content/60">
|
|
39
39
|
{paidCount} of {sorted.length} milestones paid
|
|
40
40
|
</span>
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { onMount } from 'svelte';
|
|
10
|
+
import { fly, fade } from 'svelte/transition';
|
|
11
|
+
import { cubicOut } from 'svelte/easing';
|
|
10
12
|
|
|
11
13
|
interface Props {
|
|
12
14
|
open?: boolean;
|
|
@@ -22,6 +24,18 @@
|
|
|
22
24
|
ariaLabel?: string;
|
|
23
25
|
dismissible?: boolean;
|
|
24
26
|
zIndex?: number;
|
|
27
|
+
/**
|
|
28
|
+
* When true (default), keydown events fired inside the modal are stopped
|
|
29
|
+
* from propagating to window — preventing page-level list/keyboard
|
|
30
|
+
* navigation handlers (listNav-style) from receiving arrow keys, j/k,
|
|
31
|
+
* vim chords, digit counts, etc. while the modal is open.
|
|
32
|
+
*
|
|
33
|
+
* Free-text fields (INPUT, TEXTAREA, contentEditable) are intentionally
|
|
34
|
+
* excluded so normal typing still works. Set to false only when the
|
|
35
|
+
* consumer needs custom window-level key handling even while the modal
|
|
36
|
+
* is open.
|
|
37
|
+
*/
|
|
38
|
+
trapKeys?: boolean;
|
|
25
39
|
onclose?: () => void;
|
|
26
40
|
onopen?: () => void;
|
|
27
41
|
header?: import('svelte').Snippet;
|
|
@@ -43,6 +57,7 @@
|
|
|
43
57
|
ariaLabel = '',
|
|
44
58
|
dismissible = true,
|
|
45
59
|
zIndex = 1000,
|
|
60
|
+
trapKeys = true,
|
|
46
61
|
onclose,
|
|
47
62
|
onopen,
|
|
48
63
|
header,
|
|
@@ -53,6 +68,11 @@
|
|
|
53
68
|
let modalElement: HTMLDialogElement | undefined = $state();
|
|
54
69
|
let previousFocus: HTMLElement | null = null;
|
|
55
70
|
|
|
71
|
+
// Respect prefers-reduced-motion: skip the entrance/exit transitions.
|
|
72
|
+
const reducedMotion =
|
|
73
|
+
typeof window !== 'undefined' &&
|
|
74
|
+
window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
75
|
+
|
|
56
76
|
const sizeClass = $derived(
|
|
57
77
|
{ sm: 'max-w-sm', md: 'max-w-md', lg: 'max-w-lg', xl: 'max-w-xl', full: 'max-w-full h-full' }[size]
|
|
58
78
|
);
|
|
@@ -98,6 +118,21 @@
|
|
|
98
118
|
}
|
|
99
119
|
|
|
100
120
|
function handleKeyDown(event: KeyboardEvent) {
|
|
121
|
+
// Stop keydown events from bubbling to window while the modal is open.
|
|
122
|
+
// This prevents page-level keyboard navigation (listNav-style handlers
|
|
123
|
+
// on window) from reacting to arrow keys, j/k, vim chords, digit counts,
|
|
124
|
+
// etc. typed inside the modal.
|
|
125
|
+
//
|
|
126
|
+
// Free-text fields are excluded: page-level handlers already ignore them,
|
|
127
|
+
// and we must not interfere with normal typing or textarea shortcuts.
|
|
128
|
+
if (trapKeys) {
|
|
129
|
+
const el = document.activeElement as HTMLElement | null;
|
|
130
|
+
const tag = el?.tagName;
|
|
131
|
+
const isTextField =
|
|
132
|
+
!!el && (tag === 'INPUT' || tag === 'TEXTAREA' || el.isContentEditable === true);
|
|
133
|
+
if (!isTextField) event.stopPropagation();
|
|
134
|
+
}
|
|
135
|
+
|
|
101
136
|
if (event.key === 'Tab') {
|
|
102
137
|
trapFocus(event);
|
|
103
138
|
}
|
|
@@ -157,8 +192,8 @@
|
|
|
157
192
|
>
|
|
158
193
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
159
194
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
160
|
-
<div class="modal-backdrop" onclick={handleBackdropClick}>
|
|
161
|
-
<div class={contentClasses}>
|
|
195
|
+
<div class="modal-backdrop" onclick={handleBackdropClick} transition:fade={{ duration: reducedMotion ? 0 : 200 }}>
|
|
196
|
+
<div class={contentClasses} in:fly={{ y: reducedMotion ? 0 : 12, duration: reducedMotion ? 0 : 220, easing: cubicOut }} out:fade={{ duration: reducedMotion ? 0 : 150 }}>
|
|
162
197
|
{#if showHeader && (title || showCloseButton || header)}
|
|
163
198
|
<div class="flex items-center justify-between mb-4">
|
|
164
199
|
{#if header}
|
|
@@ -168,7 +203,7 @@
|
|
|
168
203
|
{/if}
|
|
169
204
|
|
|
170
205
|
{#if showCloseButton && dismissible}
|
|
171
|
-
<button type="button" class="btn btn-sm btn-circle btn-
|
|
206
|
+
<button type="button" class="btn btn-sm btn-circle btn-ghost" onclick={close} aria-label="Close modal">
|
|
172
207
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
173
208
|
<path
|
|
174
209
|
stroke-linecap="round"
|
|
@@ -188,7 +223,7 @@
|
|
|
188
223
|
{/if}
|
|
189
224
|
</div>
|
|
190
225
|
|
|
191
|
-
{#if
|
|
226
|
+
{#if footer}
|
|
192
227
|
<div class="modal-action mt-6">
|
|
193
228
|
{@render footer()}
|
|
194
229
|
</div>
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte"
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
fileId: string
|
|
6
|
+
fileName?: string
|
|
7
|
+
pdfUrl?: string
|
|
8
|
+
class?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
let { fileId, fileName = "document.pdf", class: className = "" }: Props = $props()
|
|
12
|
+
|
|
13
|
+
let pageCount = $state(0)
|
|
14
|
+
let currentPage = $state(1)
|
|
15
|
+
let loading = $state(true)
|
|
16
|
+
let pageLoading = $state(false)
|
|
17
|
+
let error = $state<string | null>(null)
|
|
18
|
+
let imgSrc = $state<string | null>(null)
|
|
19
|
+
|
|
20
|
+
onMount(async () => {
|
|
21
|
+
try {
|
|
22
|
+
const res = await fetch(`/api/files/${fileId}/pdf-info`)
|
|
23
|
+
if (!res.ok) throw new Error(`Failed to load PDF info: ${res.status}`)
|
|
24
|
+
const data = await res.json()
|
|
25
|
+
pageCount = data.pageCount ?? 1
|
|
26
|
+
} catch (err) {
|
|
27
|
+
error = err instanceof Error ? err.message : "Failed to load PDF"
|
|
28
|
+
} finally {
|
|
29
|
+
loading = false
|
|
30
|
+
}
|
|
31
|
+
if (!error) loadPage(1)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
async function loadPage(page: number) {
|
|
35
|
+
pageLoading = true
|
|
36
|
+
imgSrc = null
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch(`/api/files/${fileId}/pdf-page/${page}?scale=2`)
|
|
39
|
+
if (!res.ok) throw new Error(`Failed to render page ${page}`)
|
|
40
|
+
const blob = await res.blob()
|
|
41
|
+
imgSrc = URL.createObjectURL(blob)
|
|
42
|
+
currentPage = page
|
|
43
|
+
} catch (err) {
|
|
44
|
+
error = err instanceof Error ? err.message : "Failed to load page"
|
|
45
|
+
} finally {
|
|
46
|
+
pageLoading = false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function prevPage() {
|
|
51
|
+
if (currentPage > 1) loadPage(currentPage - 1)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function nextPage() {
|
|
55
|
+
if (currentPage < pageCount) loadPage(currentPage + 1)
|
|
56
|
+
}
|
|
57
|
+
</script>
|
|
58
|
+
|
|
59
|
+
<div class="flex flex-col h-full {className}">
|
|
60
|
+
{#if loading}
|
|
61
|
+
<div class="flex-1 flex items-center justify-center text-base-content/40 text-sm">
|
|
62
|
+
Loading PDF…
|
|
63
|
+
</div>
|
|
64
|
+
{:else if error}
|
|
65
|
+
<div class="flex-1 flex items-center justify-center">
|
|
66
|
+
<p class="text-error text-sm text-center px-4">{error}</p>
|
|
67
|
+
</div>
|
|
68
|
+
{:else}
|
|
69
|
+
<!-- Viewer area -->
|
|
70
|
+
<div class="flex-1 min-h-0 flex items-center justify-center bg-base-200/30 p-4 overflow-auto">
|
|
71
|
+
{#if pageLoading}
|
|
72
|
+
<div class="text-base-content/40 text-sm">Rendering page…</div>
|
|
73
|
+
{:else if imgSrc}
|
|
74
|
+
<img
|
|
75
|
+
src={imgSrc}
|
|
76
|
+
alt="Page {currentPage} of {fileName}"
|
|
77
|
+
class="max-w-full max-h-full object-contain shadow-md rounded"
|
|
78
|
+
/>
|
|
79
|
+
{/if}
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
<!-- Pagination controls -->
|
|
83
|
+
{#if pageCount > 1}
|
|
84
|
+
<div class="flex items-center justify-center gap-3 py-2 border-t border-base-300 text-sm">
|
|
85
|
+
<button
|
|
86
|
+
type="button"
|
|
87
|
+
class="btn btn-ghost btn-xs"
|
|
88
|
+
onclick={prevPage}
|
|
89
|
+
disabled={currentPage <= 1}
|
|
90
|
+
aria-label="Previous page"
|
|
91
|
+
>‹</button>
|
|
92
|
+
<span class="text-base-content/60 tabular-nums">
|
|
93
|
+
{currentPage} / {pageCount}
|
|
94
|
+
</span>
|
|
95
|
+
<button
|
|
96
|
+
type="button"
|
|
97
|
+
class="btn btn-ghost btn-xs"
|
|
98
|
+
onclick={nextPage}
|
|
99
|
+
disabled={currentPage >= pageCount}
|
|
100
|
+
aria-label="Next page"
|
|
101
|
+
>›</button>
|
|
102
|
+
</div>
|
|
103
|
+
{/if}
|
|
104
|
+
{/if}
|
|
105
|
+
</div>
|
|
@@ -9,11 +9,12 @@
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
let { src, class: className = "" }: Props = $props()
|
|
12
|
-
let canvas
|
|
12
|
+
let canvas = $state<HTMLCanvasElement | null>(null)
|
|
13
13
|
let loading = $state(true)
|
|
14
14
|
let failed = $state(false)
|
|
15
15
|
|
|
16
16
|
onMount(async () => {
|
|
17
|
+
if (!canvas) return
|
|
17
18
|
try {
|
|
18
19
|
const pdfjsLib = await import("pdfjs-dist")
|
|
19
20
|
pdfjsLib.GlobalWorkerOptions.workerSrc = new URL(
|
|
@@ -36,6 +37,7 @@
|
|
|
36
37
|
|
|
37
38
|
await page.render({
|
|
38
39
|
canvasContext: canvas.getContext("2d")!,
|
|
40
|
+
canvas,
|
|
39
41
|
viewport: scaled,
|
|
40
42
|
}).promise
|
|
41
43
|
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
|
|
139
139
|
{#if dropdownOpen}
|
|
140
140
|
<div
|
|
141
|
-
class="absolute z-50 mt-1 w-72 max-h-80 overflow-y-auto bg-base-
|
|
141
|
+
class="absolute z-50 mt-1 w-72 max-h-80 overflow-y-auto bg-base-200 border border-base-300 rounded-lg"
|
|
142
142
|
role="listbox"
|
|
143
143
|
>
|
|
144
144
|
<div class="sticky top-0 bg-base-100 p-2 border-b border-base-300">
|
|
@@ -271,13 +271,13 @@
|
|
|
271
271
|
width: 30px;
|
|
272
272
|
height: 4px;
|
|
273
273
|
border-radius: 2px;
|
|
274
|
-
background-color: var(--color-base-300,
|
|
274
|
+
background-color: var(--color-base-300, oklch(0.82 0.03 255));
|
|
275
275
|
transition: background-color 0.2s ease;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
.drag-handle:hover .drag-indicator,
|
|
279
279
|
.dragging .drag-indicator {
|
|
280
|
-
background-color: var(--color-primary,
|
|
280
|
+
background-color: var(--color-primary, oklch(0.64 0.18 259));
|
|
281
281
|
}
|
|
282
282
|
|
|
283
283
|
.panel-content {
|
|
@@ -297,9 +297,9 @@
|
|
|
297
297
|
.drag-preview-inner {
|
|
298
298
|
width: 100px;
|
|
299
299
|
height: 60px;
|
|
300
|
-
background-color: var(--color-primary,
|
|
300
|
+
background-color: var(--color-primary, oklch(0.64 0.18 259));
|
|
301
301
|
opacity: 0.3;
|
|
302
302
|
border-radius: 4px;
|
|
303
|
-
border: 2px solid var(--color-primary,
|
|
303
|
+
border: 2px solid var(--color-primary, oklch(0.64 0.18 259));
|
|
304
304
|
}
|
|
305
305
|
</style>
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
|
|
41
41
|
let open = $state(false);
|
|
42
42
|
let searchQuery = $state('');
|
|
43
|
-
let searchInput
|
|
43
|
+
let searchInput = $state<HTMLInputElement | undefined>(undefined);
|
|
44
44
|
let containerRef: HTMLDivElement | undefined;
|
|
45
45
|
|
|
46
46
|
// Find the currently selected option across all groups
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
type="button"
|
|
105
105
|
class="sd-trigger"
|
|
106
106
|
class:sd-disabled={disabled}
|
|
107
|
-
style={activeColor ? `
|
|
107
|
+
style={activeColor ? `color: ${activeColor};` : ''}
|
|
108
108
|
onclick={() => { if (!disabled) open = !open; }}
|
|
109
109
|
{disabled}
|
|
110
110
|
>
|
|
@@ -169,7 +169,7 @@
|
|
|
169
169
|
onclick={() => select(option.value)}
|
|
170
170
|
class="sd-option"
|
|
171
171
|
class:sd-option-selected={value === option.value}
|
|
172
|
-
style={colorFn && colorFn(option.value) ? `
|
|
172
|
+
style={colorFn && colorFn(option.value) ? `color: ${colorFn(option.value)};` : ''}
|
|
173
173
|
>
|
|
174
174
|
{#if option.image}
|
|
175
175
|
<img src={option.image} alt="" class="sd-avatar" />
|
|
@@ -189,7 +189,12 @@
|
|
|
189
189
|
{/each}
|
|
190
190
|
{/each}
|
|
191
191
|
{:else}
|
|
192
|
-
<li class="sd-empty">
|
|
192
|
+
<li class="sd-empty">
|
|
193
|
+
<svg class="sd-empty-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5" aria-hidden="true">
|
|
194
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 15.803 7.5 7.5 0 0015.803 15.803z" />
|
|
195
|
+
</svg>
|
|
196
|
+
No matches for "{searchQuery}"
|
|
197
|
+
</li>
|
|
193
198
|
{/if}
|
|
194
199
|
</ul>
|
|
195
200
|
{#if footer}
|
|
@@ -210,7 +215,7 @@
|
|
|
210
215
|
--sd-border: var(--sd-color-border, color-mix(in oklch, var(--color-base-content, CanvasText) 20%, transparent));
|
|
211
216
|
--sd-text: var(--sd-color-text, var(--color-base-content, CanvasText));
|
|
212
217
|
--sd-text-muted: var(--sd-color-text-muted, color-mix(in oklch, var(--color-base-content, CanvasText) 50%, transparent));
|
|
213
|
-
--sd-text-label: var(--sd-color-text-label, color-mix(in oklch, var(--color-
|
|
218
|
+
--sd-text-label: var(--sd-color-text-label, color-mix(in oklch, var(--color-base-content, CanvasText) 45%, transparent));
|
|
214
219
|
--sd-accent: var(--sd-color-accent, var(--color-primary, LinkText));
|
|
215
220
|
--sd-success: var(--sd-color-success, var(--color-success, green));
|
|
216
221
|
}
|
|
@@ -265,7 +270,7 @@
|
|
|
265
270
|
justify-content: center;
|
|
266
271
|
font-size: 0.6rem;
|
|
267
272
|
font-weight: 700;
|
|
268
|
-
color:
|
|
273
|
+
color: oklch(1 0 0);
|
|
269
274
|
line-height: 1;
|
|
270
275
|
}
|
|
271
276
|
|
|
@@ -347,11 +352,10 @@
|
|
|
347
352
|
padding: 0.375rem 0.75rem 0.125rem;
|
|
348
353
|
}
|
|
349
354
|
.sd-group-label span {
|
|
350
|
-
font-size: 0.
|
|
355
|
+
font-size: 0.8125rem;
|
|
351
356
|
font-family: inherit;
|
|
352
|
-
font-weight:
|
|
353
|
-
|
|
354
|
-
letter-spacing: 0.05em;
|
|
357
|
+
font-weight: 400;
|
|
358
|
+
letter-spacing: 0.005em;
|
|
355
359
|
color: var(--sd-text-label);
|
|
356
360
|
}
|
|
357
361
|
|
|
@@ -368,7 +372,6 @@
|
|
|
368
372
|
cursor: pointer;
|
|
369
373
|
background: transparent;
|
|
370
374
|
border: none;
|
|
371
|
-
border-left: 2px solid transparent;
|
|
372
375
|
color: var(--sd-text);
|
|
373
376
|
}
|
|
374
377
|
.sd-option:hover {
|
|
@@ -376,7 +379,7 @@
|
|
|
376
379
|
}
|
|
377
380
|
.sd-option-selected {
|
|
378
381
|
background: var(--sd-bg-selected);
|
|
379
|
-
|
|
382
|
+
font-weight: 600;
|
|
380
383
|
}
|
|
381
384
|
|
|
382
385
|
.sd-option-icon {
|
|
@@ -399,10 +402,20 @@
|
|
|
399
402
|
}
|
|
400
403
|
|
|
401
404
|
.sd-empty {
|
|
402
|
-
padding: 0.75rem;
|
|
405
|
+
padding: 1rem 0.75rem;
|
|
403
406
|
text-align: center;
|
|
404
407
|
font-size: inherit;
|
|
405
408
|
font-family: inherit;
|
|
406
409
|
color: var(--sd-text-muted);
|
|
410
|
+
display: flex;
|
|
411
|
+
flex-direction: column;
|
|
412
|
+
align-items: center;
|
|
413
|
+
gap: 0.375rem;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.sd-empty-icon {
|
|
417
|
+
width: 1.25rem;
|
|
418
|
+
height: 1.25rem;
|
|
419
|
+
opacity: 0.35;
|
|
407
420
|
}
|
|
408
421
|
</style>
|
|
@@ -115,11 +115,33 @@
|
|
|
115
115
|
document.addEventListener('click', handleClickOutside);
|
|
116
116
|
return () => document.removeEventListener('click', handleClickOutside);
|
|
117
117
|
});
|
|
118
|
+
|
|
119
|
+
// Error-state shake (transitions.dev). Shakes the whole control wrapper when
|
|
120
|
+
// `error` becomes set. See TextInput for the rationale.
|
|
121
|
+
let hadError = false;
|
|
122
|
+
$effect(() => {
|
|
123
|
+
const hasError = !!error;
|
|
124
|
+
if (hasError && !hadError && selectElement) {
|
|
125
|
+
const reduce =
|
|
126
|
+
typeof window !== 'undefined' &&
|
|
127
|
+
window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
128
|
+
if (!reduce) {
|
|
129
|
+
selectElement.classList.remove('t-input-shake');
|
|
130
|
+
void selectElement.offsetWidth;
|
|
131
|
+
selectElement.classList.add('t-input-shake');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
hadError = hasError;
|
|
135
|
+
});
|
|
118
136
|
</script>
|
|
119
137
|
|
|
120
|
-
<div
|
|
138
|
+
<div
|
|
139
|
+
class="form-control w-full"
|
|
140
|
+
bind:this={selectElement}
|
|
141
|
+
onanimationend={() => selectElement?.classList.remove('t-input-shake')}
|
|
142
|
+
>
|
|
121
143
|
<label for={id} class="label">
|
|
122
|
-
<span class="
|
|
144
|
+
<span class="text-[0.8125rem] font-medium text-base-content/85">
|
|
123
145
|
{label}
|
|
124
146
|
{#if required}
|
|
125
147
|
<span class="text-error ml-1">*</span>
|
|
@@ -205,11 +227,11 @@
|
|
|
205
227
|
|
|
206
228
|
{#if error}
|
|
207
229
|
<div class="label">
|
|
208
|
-
<span id="{id}-error" class="
|
|
230
|
+
<span id="{id}-error" class="text-[0.8125rem] text-error" role="alert">{error}</span>
|
|
209
231
|
</div>
|
|
210
232
|
{:else if helpText}
|
|
211
233
|
<div class="label">
|
|
212
|
-
<span id="{id}-help" class="
|
|
234
|
+
<span id="{id}-help" class="text-[0.8125rem] text-base-content/45">{helpText}</span>
|
|
213
235
|
</div>
|
|
214
236
|
{/if}
|
|
215
237
|
</div>
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<div class="flex items-center gap-2 px-2 py-1">
|
|
29
29
|
<a href={accountHref} class="flex items-center gap-2 flex-1 min-w-0" title="Account settings">
|
|
30
30
|
<UserAvatar name={profile?.full_name} email={user?.email} avatarUrl={profile?.avatar_url} size="sm" />
|
|
31
|
-
<span class="truncate text-
|
|
31
|
+
<span class="truncate text-[0.9375rem]">{displayName}</span>
|
|
32
32
|
</a>
|
|
33
33
|
<a href={signOutHref} class="btn btn-ghost btn-xs shrink-0">Sign out</a>
|
|
34
34
|
</div>
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
onSignatureChange,
|
|
13
13
|
width = 400,
|
|
14
14
|
height = 150,
|
|
15
|
-
strokeColor = '
|
|
15
|
+
strokeColor = '',
|
|
16
16
|
strokeWidth = 2,
|
|
17
17
|
label = 'Signature',
|
|
18
18
|
required = false,
|
|
@@ -41,7 +41,10 @@
|
|
|
41
41
|
const ctx = canvas.getContext('2d');
|
|
42
42
|
if (!ctx) return;
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
const resolvedStroke = strokeColor ||
|
|
45
|
+
getComputedStyle(canvas).getPropertyValue('--color-base-content').trim() ||
|
|
46
|
+
'oklch(0.21 0 0)';
|
|
47
|
+
ctx.strokeStyle = resolvedStroke;
|
|
45
48
|
ctx.lineWidth = strokeWidth;
|
|
46
49
|
ctx.lineCap = 'round';
|
|
47
50
|
ctx.lineJoin = 'round';
|
|
@@ -73,7 +76,7 @@
|
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
function draw(e: MouseEvent | TouchEvent) {
|
|
76
|
-
if (!isDrawing) return;
|
|
79
|
+
if (!isDrawing || !ctx) return;
|
|
77
80
|
e.preventDefault();
|
|
78
81
|
|
|
79
82
|
const coords = getCoordinates(e);
|
|
@@ -148,7 +151,8 @@
|
|
|
148
151
|
</div>
|
|
149
152
|
{/if}
|
|
150
153
|
|
|
151
|
-
<div class="
|
|
154
|
+
<div class="rounded-lg bg-base-100 overflow-hidden relative transition-all duration-300
|
|
155
|
+
{hasDrawn ? 'border-2 border-success/60' : 'border-2 border-base-300'}">
|
|
152
156
|
<canvas
|
|
153
157
|
bind:this={canvas}
|
|
154
158
|
{width}
|