@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
|
@@ -14,6 +14,38 @@
|
|
|
14
14
|
}>()
|
|
15
15
|
|
|
16
16
|
let error = $state("")
|
|
17
|
+
let isDragging = $state(false)
|
|
18
|
+
|
|
19
|
+
function handleDragOver(event: DragEvent) {
|
|
20
|
+
event.preventDefault()
|
|
21
|
+
isDragging = true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function handleDragLeave(event: DragEvent) {
|
|
25
|
+
// Only clear if leaving the label entirely (not entering a child)
|
|
26
|
+
if (!(event.currentTarget as HTMLElement).contains(event.relatedTarget as Node)) {
|
|
27
|
+
isDragging = false
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function handleDrop(event: DragEvent) {
|
|
32
|
+
event.preventDefault()
|
|
33
|
+
isDragging = false
|
|
34
|
+
error = ""
|
|
35
|
+
const file = event.dataTransfer?.files[0]
|
|
36
|
+
if (!file) return
|
|
37
|
+
if (!file.type.startsWith("image/")) {
|
|
38
|
+
error = "Please drop an image file."
|
|
39
|
+
return
|
|
40
|
+
}
|
|
41
|
+
if (file.size > maxSize) {
|
|
42
|
+
error = `File too large! Maximum size is ${formatFileSize(maxSize)}.`
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
const url = URL.createObjectURL(file)
|
|
46
|
+
imageUrl = url
|
|
47
|
+
onUpload?.(url)
|
|
48
|
+
}
|
|
17
49
|
|
|
18
50
|
async function handleFileInput(event: Event) {
|
|
19
51
|
error = ""
|
|
@@ -71,17 +103,34 @@
|
|
|
71
103
|
</button>
|
|
72
104
|
</div>
|
|
73
105
|
{:else}
|
|
106
|
+
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
|
74
107
|
<label
|
|
75
|
-
class="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed rounded-md cursor-pointer
|
|
108
|
+
class="flex flex-col items-center justify-center w-full h-32 border-2 border-dashed rounded-md cursor-pointer transition-all duration-200
|
|
109
|
+
{isDragging
|
|
110
|
+
? 'border-primary bg-primary/8 scale-[1.01]'
|
|
111
|
+
: 'border-base-content/20 bg-base-200 hover:bg-base-300 hover:border-base-content/30'}"
|
|
112
|
+
ondragover={handleDragOver}
|
|
113
|
+
ondragleave={handleDragLeave}
|
|
114
|
+
ondrop={handleDrop}
|
|
76
115
|
>
|
|
77
|
-
<div class="flex flex-col items-center justify-center pt-5 pb-6">
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
116
|
+
<div class="flex flex-col items-center justify-center pt-5 pb-6 pointer-events-none">
|
|
117
|
+
{#if isDragging}
|
|
118
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mb-2 text-primary">
|
|
119
|
+
<path d="M12 15V3"/><path d="M7 10l5 5 5-5"/>
|
|
120
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
|
121
|
+
</svg>
|
|
122
|
+
<p class="mb-2 text-sm font-semibold text-primary">Drop to upload</p>
|
|
123
|
+
{:else}
|
|
124
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mb-2 text-base-content/40">
|
|
125
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/>
|
|
126
|
+
</svg>
|
|
127
|
+
<p class="mb-2 text-sm">
|
|
128
|
+
<span class="font-semibold">Click to upload</span> or drag and drop
|
|
129
|
+
</p>
|
|
130
|
+
<p class="text-xs text-base-content/45">
|
|
131
|
+
PNG, JPG or JPEG (max {formatFileSize(maxSize)})
|
|
132
|
+
</p>
|
|
133
|
+
{/if}
|
|
85
134
|
</div>
|
|
86
135
|
<input
|
|
87
136
|
type="file"
|
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
<script module lang="ts">
|
|
2
|
+
/** A display segment for formula-aware rendering */
|
|
3
|
+
export interface DisplaySegment {
|
|
4
|
+
type: 'text' | 'formula';
|
|
5
|
+
display: string;
|
|
6
|
+
tooltip?: string;
|
|
7
|
+
}
|
|
8
|
+
</script>
|
|
9
|
+
|
|
1
10
|
<script lang="ts">
|
|
2
11
|
/**
|
|
3
12
|
* InlineEdit Component
|
|
@@ -14,13 +23,6 @@
|
|
|
14
23
|
* - Optional formula-aware display: segments with type/display/tooltip
|
|
15
24
|
*/
|
|
16
25
|
|
|
17
|
-
/** A display segment for formula-aware rendering */
|
|
18
|
-
export interface DisplaySegment {
|
|
19
|
-
type: 'text' | 'formula';
|
|
20
|
-
display: string;
|
|
21
|
-
tooltip?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
26
|
interface Props {
|
|
25
27
|
/** Current value */
|
|
26
28
|
value: string;
|
|
@@ -62,6 +64,7 @@
|
|
|
62
64
|
|
|
63
65
|
// Internal state
|
|
64
66
|
let isEditing = $state(false);
|
|
67
|
+
// svelte-ignore state_referenced_locally
|
|
65
68
|
let editValue = $state(value);
|
|
66
69
|
let isSaving = $state(false);
|
|
67
70
|
let inputElement = $state<HTMLInputElement | HTMLTextAreaElement | null>(null);
|
|
@@ -136,6 +139,8 @@
|
|
|
136
139
|
});
|
|
137
140
|
}
|
|
138
141
|
|
|
142
|
+
import { fade } from 'svelte/transition';
|
|
143
|
+
|
|
139
144
|
// Auto-resize textarea to fit content
|
|
140
145
|
function autoresize(node: HTMLTextAreaElement) {
|
|
141
146
|
function resize() {
|
|
@@ -157,14 +162,14 @@
|
|
|
157
162
|
|
|
158
163
|
{#if isEditing}
|
|
159
164
|
<!-- Edit mode -->
|
|
160
|
-
<div class="inline-edit-container w-full {className}">
|
|
165
|
+
<div class="inline-edit-container w-full {className}" in:fade={{ duration: 120 }}>
|
|
161
166
|
{#if type === 'textarea'}
|
|
162
167
|
<textarea
|
|
163
168
|
bind:this={inputElement}
|
|
164
169
|
bind:value={editValue}
|
|
165
170
|
{placeholder}
|
|
166
171
|
{rows}
|
|
167
|
-
class="textarea textarea-bordered w-full text-
|
|
172
|
+
class="textarea textarea-bordered w-full text-[0.9375rem] resize-none overflow-hidden"
|
|
168
173
|
style="min-height: {rows * 1.5}rem;"
|
|
169
174
|
disabled={isSaving}
|
|
170
175
|
onkeydown={handleKeyDown}
|
|
@@ -217,6 +222,7 @@
|
|
|
217
222
|
<!-- Display mode -->
|
|
218
223
|
<button
|
|
219
224
|
class="inline-edit-display text-left w-full rounded px-2 py-1 transition-colors {disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer hover:bg-base-200'} {truncate ? 'inline-edit-truncate' : ''} {className}"
|
|
225
|
+
in:fade={{ duration: 120 }}
|
|
220
226
|
onclick={startEditing}
|
|
221
227
|
disabled={disabled}
|
|
222
228
|
type="button"
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { fly, fade } from 'svelte/transition';
|
|
3
|
+
import { cubicOut } from 'svelte/easing';
|
|
4
|
+
import { tick } from 'svelte';
|
|
5
|
+
import { getPendingInput, submitInput, cancelInput } from '../stores/inputDialog.svelte';
|
|
6
|
+
|
|
7
|
+
const pending = $derived(getPendingInput());
|
|
8
|
+
const opts = $derived(pending?.options ?? null);
|
|
9
|
+
const visible = $derived(pending !== null);
|
|
10
|
+
|
|
11
|
+
let value = $state('');
|
|
12
|
+
let error = $state<string | null>(null);
|
|
13
|
+
let inputRef = $state<HTMLInputElement | null>(null);
|
|
14
|
+
|
|
15
|
+
$effect(() => {
|
|
16
|
+
if (visible && opts) {
|
|
17
|
+
value = '';
|
|
18
|
+
error = null;
|
|
19
|
+
tick().then(() => inputRef?.focus());
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function handleSubmit() {
|
|
24
|
+
if (opts?.validate) {
|
|
25
|
+
const msg = opts.validate(value);
|
|
26
|
+
if (msg !== null) { error = msg; return; }
|
|
27
|
+
}
|
|
28
|
+
error = null;
|
|
29
|
+
submitInput(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function handleKeydown(e: KeyboardEvent) {
|
|
33
|
+
if (!visible) return;
|
|
34
|
+
if (e.key === 'Escape') { e.preventDefault(); e.stopPropagation(); cancelInput(); }
|
|
35
|
+
if (e.key === 'Enter') { e.preventDefault(); e.stopPropagation(); handleSubmit(); }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
$effect(() => {
|
|
39
|
+
window.addEventListener('keydown', handleKeydown, true);
|
|
40
|
+
return () => window.removeEventListener('keydown', handleKeydown, true);
|
|
41
|
+
});
|
|
42
|
+
</script>
|
|
43
|
+
|
|
44
|
+
{#if visible && opts}
|
|
45
|
+
<!-- Scrim (above ConfirmDialog z-9990/9991) -->
|
|
46
|
+
<div
|
|
47
|
+
class="id-scrim"
|
|
48
|
+
transition:fade={{ duration: 120 }}
|
|
49
|
+
role="presentation"
|
|
50
|
+
onclick={cancelInput}
|
|
51
|
+
></div>
|
|
52
|
+
|
|
53
|
+
<!-- Dialog -->
|
|
54
|
+
<div
|
|
55
|
+
class="id-wrap"
|
|
56
|
+
role="dialog"
|
|
57
|
+
aria-modal="true"
|
|
58
|
+
aria-label="{opts.title}. Press Enter to confirm or Escape to cancel."
|
|
59
|
+
in:fly={{ y: -20, duration: 200, easing: cubicOut }}
|
|
60
|
+
out:fade={{ duration: 120 }}
|
|
61
|
+
>
|
|
62
|
+
<div class="id-box">
|
|
63
|
+
<!-- Icon + title -->
|
|
64
|
+
<div class="id-header">
|
|
65
|
+
<div class="id-icon">
|
|
66
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
67
|
+
<path d="M12 20h9"/>
|
|
68
|
+
<path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
|
|
69
|
+
</svg>
|
|
70
|
+
</div>
|
|
71
|
+
<div class="id-title-group">
|
|
72
|
+
<h3 class="id-title">{opts.title}</h3>
|
|
73
|
+
{#if opts.body}
|
|
74
|
+
<p class="id-body">{opts.body}</p>
|
|
75
|
+
{/if}
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- Input field -->
|
|
80
|
+
<input
|
|
81
|
+
bind:this={inputRef}
|
|
82
|
+
bind:value
|
|
83
|
+
oninput={() => { if (error) error = null; }}
|
|
84
|
+
type="text"
|
|
85
|
+
class="id-input"
|
|
86
|
+
class:id-input-error={error !== null}
|
|
87
|
+
placeholder={opts.placeholder ?? ''}
|
|
88
|
+
maxlength={opts.maxLength}
|
|
89
|
+
autocomplete="off"
|
|
90
|
+
spellcheck="false"
|
|
91
|
+
/>
|
|
92
|
+
{#if error !== null || opts.maxLength}
|
|
93
|
+
<div class="id-field-footer">
|
|
94
|
+
{#if error !== null}
|
|
95
|
+
<p class="id-error">{error}</p>
|
|
96
|
+
{:else}
|
|
97
|
+
<span></span>
|
|
98
|
+
{/if}
|
|
99
|
+
{#if opts.maxLength}
|
|
100
|
+
<span class="id-char-count">{value.length}/{opts.maxLength}</span>
|
|
101
|
+
{/if}
|
|
102
|
+
</div>
|
|
103
|
+
{/if}
|
|
104
|
+
|
|
105
|
+
<!-- Actions -->
|
|
106
|
+
<div class="id-actions">
|
|
107
|
+
<button
|
|
108
|
+
type="button"
|
|
109
|
+
class="id-btn id-btn-cancel"
|
|
110
|
+
onclick={cancelInput}
|
|
111
|
+
>
|
|
112
|
+
<kbd class="id-kbd">Esc</kbd>
|
|
113
|
+
<span>{opts.cancelLabel ?? 'Cancel'}</span>
|
|
114
|
+
</button>
|
|
115
|
+
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
class="id-btn id-btn-confirm"
|
|
119
|
+
onclick={handleSubmit}
|
|
120
|
+
>
|
|
121
|
+
<kbd class="id-kbd">⏎</kbd>
|
|
122
|
+
<span>{opts.confirmLabel ?? 'OK'}</span>
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</div>
|
|
127
|
+
{/if}
|
|
128
|
+
|
|
129
|
+
<style>
|
|
130
|
+
.id-scrim {
|
|
131
|
+
position: fixed;
|
|
132
|
+
inset: 0;
|
|
133
|
+
background: oklch(0 0 0 / 0.45);
|
|
134
|
+
z-index: 9991;
|
|
135
|
+
backdrop-filter: blur(2px);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.id-wrap {
|
|
139
|
+
position: fixed;
|
|
140
|
+
top: 5rem;
|
|
141
|
+
left: 50%;
|
|
142
|
+
transform: translateX(-50%);
|
|
143
|
+
z-index: 9992;
|
|
144
|
+
pointer-events: auto;
|
|
145
|
+
width: max-content;
|
|
146
|
+
max-width: min(92vw, 460px);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.id-box {
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
gap: 0.625rem;
|
|
153
|
+
padding: 1rem 1.125rem 0.625rem;
|
|
154
|
+
background: oklch(0.17 0.02 250 / 0.98);
|
|
155
|
+
border: 1px solid oklch(0.32 0.03 250);
|
|
156
|
+
border-radius: 0.875rem;
|
|
157
|
+
box-shadow:
|
|
158
|
+
0 20px 60px oklch(0 0 0 / 0.55),
|
|
159
|
+
0 0 0 1px oklch(0.28 0.03 250 / 0.5);
|
|
160
|
+
backdrop-filter: blur(16px);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.id-header {
|
|
164
|
+
display: flex;
|
|
165
|
+
align-items: flex-start;
|
|
166
|
+
gap: 0.875rem;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.id-icon {
|
|
170
|
+
flex-shrink: 0;
|
|
171
|
+
width: 2.25rem;
|
|
172
|
+
height: 2.25rem;
|
|
173
|
+
border-radius: 0.5rem;
|
|
174
|
+
display: flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
justify-content: center;
|
|
177
|
+
background: oklch(0.30 0.06 200 / 0.35);
|
|
178
|
+
border: 1px solid oklch(0.45 0.10 200 / 0.4);
|
|
179
|
+
color: oklch(0.75 0.12 200);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.id-icon svg {
|
|
183
|
+
width: 1.125rem;
|
|
184
|
+
height: 1.125rem;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.id-title-group {
|
|
188
|
+
display: flex;
|
|
189
|
+
flex-direction: column;
|
|
190
|
+
gap: 0.25rem;
|
|
191
|
+
flex: 1;
|
|
192
|
+
min-width: 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.id-title {
|
|
196
|
+
margin: 0;
|
|
197
|
+
font-size: 0.9375rem;
|
|
198
|
+
font-weight: 600;
|
|
199
|
+
color: oklch(0.92 0.04 250);
|
|
200
|
+
line-height: 1.3;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.id-body {
|
|
204
|
+
margin: 0;
|
|
205
|
+
font-size: 0.8rem;
|
|
206
|
+
color: oklch(0.60 0.03 250);
|
|
207
|
+
line-height: 1.5;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.id-input {
|
|
211
|
+
width: 100%;
|
|
212
|
+
padding: 0.4375rem 0.75rem;
|
|
213
|
+
font-size: 0.875rem;
|
|
214
|
+
background: oklch(0.13 0.02 250);
|
|
215
|
+
border: 1px solid oklch(0.32 0.03 250);
|
|
216
|
+
border-radius: 0.5rem;
|
|
217
|
+
color: oklch(0.92 0.04 250);
|
|
218
|
+
outline: none;
|
|
219
|
+
transition: border-color 0.1s ease;
|
|
220
|
+
box-sizing: border-box;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.id-input::placeholder {
|
|
224
|
+
color: oklch(0.45 0.03 250);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.id-input:focus {
|
|
228
|
+
border-color: oklch(0.55 0.12 250);
|
|
229
|
+
box-shadow: 0 0 0 2px oklch(0.55 0.12 250 / 0.2);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.id-input-error,
|
|
233
|
+
.id-input-error:focus {
|
|
234
|
+
border-color: oklch(0.55 0.20 25);
|
|
235
|
+
box-shadow: 0 0 0 2px oklch(0.55 0.20 25 / 0.2);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.id-field-footer {
|
|
239
|
+
display: flex;
|
|
240
|
+
align-items: center;
|
|
241
|
+
justify-content: space-between;
|
|
242
|
+
gap: 0.5rem;
|
|
243
|
+
margin-top: -0.125rem;
|
|
244
|
+
min-height: 1rem;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.id-error {
|
|
248
|
+
margin: 0;
|
|
249
|
+
font-size: 0.75rem;
|
|
250
|
+
color: oklch(0.65 0.18 25);
|
|
251
|
+
line-height: 1.4;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.id-char-count {
|
|
255
|
+
margin-left: auto;
|
|
256
|
+
font-size: 0.75rem;
|
|
257
|
+
color: oklch(0.50 0.03 250);
|
|
258
|
+
font-variant-numeric: tabular-nums;
|
|
259
|
+
white-space: nowrap;
|
|
260
|
+
flex-shrink: 0;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.id-actions {
|
|
264
|
+
display: flex;
|
|
265
|
+
align-items: center;
|
|
266
|
+
gap: 0.375rem;
|
|
267
|
+
padding-top: 0.125rem;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.id-btn {
|
|
271
|
+
display: inline-flex;
|
|
272
|
+
align-items: center;
|
|
273
|
+
gap: 0.375rem;
|
|
274
|
+
padding: 0.3125rem 0.75rem;
|
|
275
|
+
font-size: 0.8rem;
|
|
276
|
+
font-weight: 500;
|
|
277
|
+
border-radius: 0.5rem;
|
|
278
|
+
border: 1px solid transparent;
|
|
279
|
+
background: transparent;
|
|
280
|
+
color: oklch(0.80 0.04 250);
|
|
281
|
+
cursor: pointer;
|
|
282
|
+
transition: background 0.1s ease, border-color 0.1s ease, color 0.1s ease;
|
|
283
|
+
white-space: nowrap;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.id-btn:hover {
|
|
287
|
+
background: oklch(0.24 0.03 250);
|
|
288
|
+
border-color: oklch(0.32 0.04 250);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.id-btn-cancel:focus-visible {
|
|
292
|
+
outline: 2px solid oklch(0.60 0.10 250);
|
|
293
|
+
outline-offset: 2px;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.id-btn-confirm {
|
|
297
|
+
background: oklch(0.28 0.04 250 / 0.6);
|
|
298
|
+
border-color: oklch(0.40 0.05 250);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.id-btn-confirm:hover {
|
|
302
|
+
background: oklch(0.34 0.06 250);
|
|
303
|
+
border-color: oklch(0.50 0.08 250);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.id-btn-confirm:focus-visible {
|
|
307
|
+
outline: 2px solid oklch(0.60 0.10 250);
|
|
308
|
+
outline-offset: 2px;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.id-kbd {
|
|
312
|
+
font-family: ui-monospace, monospace;
|
|
313
|
+
font-size: 0.65rem;
|
|
314
|
+
padding: 0.0625rem 0.3125rem;
|
|
315
|
+
background: oklch(0.22 0.02 250);
|
|
316
|
+
border: 1px solid oklch(0.30 0.03 250);
|
|
317
|
+
border-bottom-width: 2px;
|
|
318
|
+
border-radius: 0.25rem;
|
|
319
|
+
color: oklch(0.75 0.04 250);
|
|
320
|
+
line-height: 1.6;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
@media (prefers-reduced-motion: reduce) {
|
|
324
|
+
.id-input { transition: none; }
|
|
325
|
+
.id-btn { transition: none; }
|
|
326
|
+
}
|
|
327
|
+
</style>
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
<div class="space-y-4 {className}">
|
|
78
78
|
{#if showHeader}
|
|
79
79
|
<div class="flex items-center justify-between">
|
|
80
|
-
<h2 class="text-
|
|
80
|
+
<h2 class="text-lg font-semibold">Short Links</h2>
|
|
81
81
|
{#if !showForm}
|
|
82
82
|
<button class="btn btn-primary btn-sm" onclick={() => showForm = true}>
|
|
83
83
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
|
|
62
62
|
const containerClass = $derived(
|
|
63
63
|
[
|
|
64
|
-
overlay ? 'fixed inset-0
|
|
64
|
+
overlay ? 'fixed inset-0 flex items-center justify-center z-50' : '',
|
|
65
65
|
centered && !overlay ? 'flex items-center justify-center' : '',
|
|
66
66
|
textPosition === 'left' || textPosition === 'right' ? 'flex items-center' : '',
|
|
67
67
|
textPosition === 'top' || textPosition === 'bottom' ? 'flex flex-col items-center' : ''
|
|
@@ -70,6 +70,10 @@
|
|
|
70
70
|
.join(' ')
|
|
71
71
|
);
|
|
72
72
|
|
|
73
|
+
const containerStyle = $derived(
|
|
74
|
+
overlay ? 'background: color-mix(in oklch, #180042 55%, transparent)' : ''
|
|
75
|
+
);
|
|
76
|
+
|
|
73
77
|
const textClass = $derived(
|
|
74
78
|
['text-sm text-base-content/70', { top: 'mb-2', bottom: 'mt-2', left: 'mr-2', right: 'ml-2' }[textPosition]]
|
|
75
79
|
.filter(Boolean)
|
|
@@ -80,7 +84,7 @@
|
|
|
80
84
|
</script>
|
|
81
85
|
|
|
82
86
|
{#if visible}
|
|
83
|
-
<div class={containerClass} aria-label={ariaLabel} role="status" aria-live="polite">
|
|
87
|
+
<div class={containerClass} style={containerStyle} aria-label={ariaLabel} role="status" aria-live="polite">
|
|
84
88
|
{#if text && textPosition === 'top'}
|
|
85
89
|
<div class={textClass}>{text}</div>
|
|
86
90
|
{/if}
|