@joewinke/jatui 0.1.23 → 0.1.24
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/CLAUDE.md +244 -0
- package/package.json +23 -10
- package/src/lib/actions/collapse.ts +0 -136
- package/src/lib/actions/railNav.ts +0 -473
- package/src/lib/components/AnnotationLayer.svelte +0 -108
- package/src/lib/components/AnnotationPanel.svelte +0 -319
- package/src/lib/components/AudioWaveform.svelte +0 -698
- package/src/lib/components/AvailabilityModal.svelte +0 -177
- package/src/lib/components/AvatarUpload.svelte +0 -234
- package/src/lib/components/Badge.svelte +0 -38
- package/src/lib/components/BookingForm.svelte +0 -278
- package/src/lib/components/BurndownChart.svelte +0 -778
- package/src/lib/components/Button.svelte +0 -81
- package/src/lib/components/CalendarPicker.svelte +0 -284
- package/src/lib/components/Card.svelte +0 -67
- package/src/lib/components/CharacterCounter.svelte +0 -82
- package/src/lib/components/ChipInput.svelte +0 -609
- package/src/lib/components/Collapse.svelte +0 -82
- package/src/lib/components/ColorSelector.svelte +0 -167
- package/src/lib/components/CommentThread.svelte +0 -773
- package/src/lib/components/ConfirmDialog.svelte +0 -348
- package/src/lib/components/ConfirmModal.svelte +0 -142
- package/src/lib/components/ContextMenu.svelte +0 -188
- package/src/lib/components/CountdownTimer.svelte +0 -94
- package/src/lib/components/DateRangePicker.svelte +0 -194
- package/src/lib/components/Drawer.svelte +0 -143
- package/src/lib/components/EmojiPicker.svelte +0 -159
- package/src/lib/components/EntityPreviewCard.svelte +0 -104
- package/src/lib/components/FileDropzone.svelte +0 -493
- package/src/lib/components/FilePicker.svelte +0 -488
- package/src/lib/components/FileThumbnail.svelte +0 -80
- package/src/lib/components/FilterDropdown.svelte +0 -203
- package/src/lib/components/FloatingActionBar.svelte +0 -354
- package/src/lib/components/GPSTracker.svelte +0 -202
- package/src/lib/components/HunkDiffView.svelte +0 -348
- package/src/lib/components/ImageLightbox.svelte +0 -274
- package/src/lib/components/ImageUpload.svelte +0 -147
- package/src/lib/components/InlineEdit.svelte +0 -291
- package/src/lib/components/InputDialog.svelte +0 -327
- package/src/lib/components/KeyboardShortcutsOverlay.svelte +0 -296
- package/src/lib/components/LazyImage.svelte +0 -123
- package/src/lib/components/LinkShortener.svelte +0 -274
- package/src/lib/components/LoadingSpinner.svelte +0 -106
- package/src/lib/components/LocationMap.svelte +0 -186
- package/src/lib/components/MapView.svelte +0 -341
- package/src/lib/components/MarkupEditor.svelte +0 -485
- package/src/lib/components/MarkupOverlay.svelte +0 -55
- package/src/lib/components/MediaWorkbench.svelte +0 -871
- package/src/lib/components/MilestoneCard.svelte +0 -98
- package/src/lib/components/MilestoneTimeline.svelte +0 -91
- package/src/lib/components/Modal.svelte +0 -244
- package/src/lib/components/PDFViewer.svelte +0 -105
- package/src/lib/components/PdfThumbnail.svelte +0 -63
- package/src/lib/components/PhoneInput.svelte +0 -212
- package/src/lib/components/ResizableDivider.svelte +0 -307
- package/src/lib/components/ResizablePanel.svelte +0 -305
- package/src/lib/components/SearchDropdown.svelte +0 -421
- package/src/lib/components/SelectInput.svelte +0 -237
- package/src/lib/components/SidebarUserFooter.svelte +0 -34
- package/src/lib/components/SignaturePad.svelte +0 -175
- package/src/lib/components/SmartImageEditor.svelte +0 -720
- package/src/lib/components/SortDropdown.svelte +0 -156
- package/src/lib/components/Sparkline.svelte +0 -116
- package/src/lib/components/SpeechForm.svelte +0 -114
- package/src/lib/components/StatusBadge.svelte +0 -157
- package/src/lib/components/TaskTypeIcon.svelte +0 -54
- package/src/lib/components/TextArea.svelte +0 -162
- package/src/lib/components/TextInput.svelte +0 -131
- package/src/lib/components/ThemeSelector.svelte +0 -206
- package/src/lib/components/TimeSlotPicker.svelte +0 -162
- package/src/lib/components/UserAvatar.svelte +0 -72
- package/src/lib/components/VariablePicker.svelte +0 -170
- package/src/lib/components/VoicePlayer.svelte +0 -422
- package/src/lib/components/linked-columns/LinkedColumns.svelte +0 -526
- package/src/lib/components/markup.ts +0 -287
- package/src/lib/components/messaging/Avatar.svelte +0 -81
- package/src/lib/components/messaging/ChannelInfoModal.svelte +0 -163
- package/src/lib/components/messaging/ChannelList.svelte +0 -107
- package/src/lib/components/messaging/ChannelMemberAvatarStack.svelte +0 -69
- package/src/lib/components/messaging/ChannelMembersModal.svelte +0 -182
- package/src/lib/components/messaging/CreateChannelModal.svelte +0 -190
- package/src/lib/components/messaging/DirectMessageList.svelte +0 -145
- package/src/lib/components/messaging/EmojiSelector.svelte +0 -261
- package/src/lib/components/messaging/MentionAutocomplete.svelte +0 -193
- package/src/lib/components/messaging/MessageAttachment.svelte +0 -273
- package/src/lib/components/messaging/MessageAttachmentUpload.svelte +0 -243
- package/src/lib/components/messaging/MessageInput.svelte +0 -452
- package/src/lib/components/messaging/MessageItem.svelte +0 -341
- package/src/lib/components/messaging/MessageThread.svelte +0 -306
- package/src/lib/components/messaging/NotificationSettingsModal.svelte +0 -234
- package/src/lib/components/messaging/QuotedMessageDisplay.svelte +0 -123
- package/src/lib/components/messaging/StartDMModal.svelte +0 -100
- package/src/lib/components/messaging/ThreadPanel.svelte +0 -153
- package/src/lib/components/pipeline/DESIGN.md +0 -361
- package/src/lib/components/pipeline/Pipeline.svelte +0 -391
- package/src/lib/components/pipeline/PipelineCard.svelte +0 -94
- package/src/lib/components/pipeline/PipelineColumn.svelte +0 -163
- package/src/lib/components/replay/ChapterTimeline.svelte +0 -326
- package/src/lib/components/session-nav/transcriptModel.ts +0 -352
- package/src/lib/data/emojis.ts +0 -1208
- package/src/lib/index.ts +0 -405
- package/src/lib/stores/confirmDialog.svelte.ts +0 -48
- package/src/lib/stores/inputDialog.svelte.ts +0 -51
- package/src/lib/styles/rail.css +0 -63
- package/src/lib/types/annotation.ts +0 -38
- package/src/lib/types/booking.ts +0 -143
- package/src/lib/types/comments.ts +0 -97
- package/src/lib/types/entityPreview.ts +0 -45
- package/src/lib/types/filePicker.ts +0 -16
- package/src/lib/types/googleMaps.d.ts +0 -51
- package/src/lib/types/maps.ts +0 -43
- package/src/lib/types/messaging.ts +0 -459
- package/src/lib/types/milestone.ts +0 -22
- package/src/lib/types/pipeline.ts +0 -150
- package/src/lib/types/smartImageEditor.ts +0 -39
- package/src/lib/types/templateVars.ts +0 -36
- package/src/lib/utils/currency.ts +0 -20
- package/src/lib/utils/daisyuiColors.ts +0 -243
- package/src/lib/utils/dateFormatters.ts +0 -196
- package/src/lib/utils/googleMapsLoader.ts +0 -84
- package/src/lib/utils/mentionParser.ts +0 -188
- package/src/lib/utils/phone.ts +0 -80
- package/src/lib/utils/phoneFormat.ts +0 -74
- package/src/lib/utils/taskUtils.ts +0 -75
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# jatui — Shared Svelte 5 + DaisyUI Component Library
|
|
2
|
+
|
|
3
|
+
Reusable UI components extracted from JAT projects. Svelte 5 runes syntax, DaisyUI + Tailwind CSS v4.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install ../jatui # or "file:../jatui" in package.json
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Then import:
|
|
12
|
+
```svelte
|
|
13
|
+
<script>
|
|
14
|
+
import { SearchDropdown, Button, Modal } from "jatui"
|
|
15
|
+
</script>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Component Catalog
|
|
19
|
+
|
|
20
|
+
### Form Inputs
|
|
21
|
+
|
|
22
|
+
| Component | Purpose | Key Props |
|
|
23
|
+
|-----------|---------|-----------|
|
|
24
|
+
| `TextInput` | Text input with label, error, icon slots | `label`, `value` (bindable), `placeholder`, `error`, `disabled` |
|
|
25
|
+
| `TextArea` | Multiline text input | `label`, `value` (bindable), `placeholder`, `rows`, `error`, `maxlength` |
|
|
26
|
+
| `SelectInput` | Dropdown select | `label`, `value` (bindable), `options: SelectOption[]`, `placeholder` |
|
|
27
|
+
| `PhoneInput` | Intl phone (country dropdown + tel input, outputs E.164) | `value` (bindable), `name`, `defaultCountry`, `required` |
|
|
28
|
+
| `ChipInput` | Tag/chip entry with suggestions | `chips` (bindable), `suggestions: SuggestionGroup[]`, `placeholder` |
|
|
29
|
+
| `SearchDropdown` | Searchable dropdown with grouped results | `groups: SearchDropdownGroup[]`, `placeholder`, `onSelect` |
|
|
30
|
+
| `FilterDropdown` | Multi-select filter dropdown | `label`, `options: string[]`, `selected: Set<string>` (bindable) |
|
|
31
|
+
| `SortDropdown` | Sort by column with direction | `options: SortOption[]`, `sortBy` (bindable), `sortDir` (bindable) |
|
|
32
|
+
| `InlineEdit` | Click-to-edit text | `value` (bindable), `segments: DisplaySegment[]`, `onSave` |
|
|
33
|
+
| `VariablePicker` | Browse/insert template merge vars at caret | `groups: TemplateVarGroup[]`, `targetEl`, `onInsert` |
|
|
34
|
+
| `CharacterCounter` | Shows chars remaining | `current: number`, `max: number` |
|
|
35
|
+
| `ColorSelector` | Color picker from palette | `value` (bindable), `colors: string[]` |
|
|
36
|
+
| `ImageUpload` | Drag-drop image upload | `value` (bindable), `accept`, `maxSize` |
|
|
37
|
+
| `SmartImageEditor` | AI-powered image upload with background removal and multi-variant processing pipeline. **Preferred over `ImageUpload` in editor contexts.** Two modes: simple (drop-in replacement, outputs blob URL) and editor (POST multipart to backend → variant grid). | `value` (bindable), `spec?: ImageProcessingSpec`, `uploadEndpoint?`, `onComplete?`, `accept?`, `maxSizeMb?` |
|
|
38
|
+
| `FileDropzone` | Multi-file/folder/ZIP drag-drop upload zone. Handles FileSystem API folder recursion, MIME/size validation, and shows a pending-file grid. Calls `onFilesSelected(files, errors)` with validated `File[]`. Accepts already-uploaded files via `uploadedFiles: FileDropzoneFile[]` for post-upload display. | `accept?: string[]`, `maxSize?`, `maxFiles?`, `multiple?`, `uploadedFiles?`, `isUploading?`, `isExtracting?`, `dropLabel?`, `dropSubtext?`, `onFilesSelected?` |
|
|
39
|
+
| `FileThumbnail` | File thumbnail with MIME-aware icon fallback. Shows an `<img>` for image files (with loading/error states) and typed icons for PDF, ZIP, CAD, and generic files. Size variants: `sm`, `md`, `lg`, `xl`. | `name`, `mimeType`, `url?`, `size?` |
|
|
40
|
+
| `SignaturePad` | Draw signature on canvas | `onSave: (dataUrl: string) => void` |
|
|
41
|
+
|
|
42
|
+
### Scheduling & Time
|
|
43
|
+
|
|
44
|
+
| Component | Purpose | Key Props |
|
|
45
|
+
|-----------|---------|-----------|
|
|
46
|
+
| `CalendarPicker` | Month calendar with day selection | `selectedDate` (bindable), `availabilityCounts`, `onDateSelect` |
|
|
47
|
+
| `DateRangePicker` | Start/end date range | `selectedRange` (bindable), `onRangeChange` |
|
|
48
|
+
| `TimeSlotPicker` | Time slot grid selector | `slots: TimeSlot[]`, `selectedSlot` (bindable), `onSlotSelect` |
|
|
49
|
+
| `AvailabilityModal` | Date override editor (modal) | `date`, `overrides`, `onSave`, `onClose` |
|
|
50
|
+
| `BookingForm` | Guest info form for booking | `onSubmit`, `submitting`, `requirePhone`, `accentColor`, `showAttendeeOption` |
|
|
51
|
+
| `CountdownTimer` | Countdown to target date | `targetDate: Date`, `label` |
|
|
52
|
+
|
|
53
|
+
### Display & Feedback
|
|
54
|
+
|
|
55
|
+
| Component | Purpose | Key Props |
|
|
56
|
+
|-----------|---------|-----------|
|
|
57
|
+
| `Button` | Styled button with variants | `variant`, `size`, `loading`, `disabled`, `onclick` |
|
|
58
|
+
| `Badge` | Status/label badge | `variant`, `size`, `children` (snippet) |
|
|
59
|
+
| `StatusBadge` | Status indicator with dot | `status: StatusValue`, `label` |
|
|
60
|
+
| `Card` | Content card container | `title`, `children` (snippet) |
|
|
61
|
+
| `Modal` | Dialog overlay | `open` (bindable), `title`, `children` (snippet) |
|
|
62
|
+
| `ConfirmModal` | Yes/no confirmation dialog | `open` (bindable), `title`, `message`, `onConfirm`, `onCancel` |
|
|
63
|
+
| `Drawer` | Slide-out side panel | `open` (bindable), `side`, `children` (snippet) |
|
|
64
|
+
| `LoadingSpinner` | Spinner animation | `size`, `color` |
|
|
65
|
+
| `Sparkline` | Inline mini chart | `data: number[]`, `width`, `height`, `color` |
|
|
66
|
+
| `LazyImage` | Image with lazy loading + placeholder | `src`, `alt`, `placeholder` |
|
|
67
|
+
| `HunkDiffView` | Side-by-side diff with line numbers, red/green backgrounds, strikethrough on removed lines, inline word-level highlights via the `diff` lib. Used for reviewing YAML/text changes per hunk. | `oldText: string \| null`, `newText: string \| null`, `unified?: boolean` |
|
|
68
|
+
|
|
69
|
+
### Media
|
|
70
|
+
|
|
71
|
+
| Component | Purpose | Key Props |
|
|
72
|
+
|-----------|---------|-----------|
|
|
73
|
+
| `MediaWorkbench` | Full-featured image editor modal — upload, AI generate, AI edit, background removal, annotation canvas. **Preferred over `SmartImageEditor` for new image fields.** Two-column modal: preview left, 4 action tabs right (Generate, Edit, Remove BG, Annotate). Save uploads blob to `uploadEndpoint` or emits `onComplete`. Requires proxy endpoints from jst-23w1l.2. | `value` (bindable `string \| null`), `uploadEndpoint?` (default `/api/media`), `onComplete?`, `accept?` |
|
|
74
|
+
|
|
75
|
+
### Layout
|
|
76
|
+
|
|
77
|
+
| Component | Purpose | Key Props |
|
|
78
|
+
|-----------|---------|-----------|
|
|
79
|
+
| `ResizablePanel` | Resizable container | `minWidth`, `maxWidth`, `defaultWidth` |
|
|
80
|
+
| `ResizableDivider` | Drag handle between panels | `onResize` |
|
|
81
|
+
|
|
82
|
+
### Audio & Voice
|
|
83
|
+
|
|
84
|
+
| Component | Purpose | Key Props |
|
|
85
|
+
|-----------|---------|-----------|
|
|
86
|
+
| `AudioWaveform` | Audio waveform visualizer | `audioUrl`, `isPlaying` |
|
|
87
|
+
| `VoicePlayer` | Audio playback with controls | `src`, `autoplay` |
|
|
88
|
+
| `SpeechForm` | Voice-to-text form input | `onTranscript` |
|
|
89
|
+
|
|
90
|
+
### PDF
|
|
91
|
+
|
|
92
|
+
| Component | Purpose | Key Props |
|
|
93
|
+
|-----------|---------|-----------|
|
|
94
|
+
| `PDFViewer` | Server-rendered PDF viewer — pages rendered as `<img>` via PDFium WASM (no client pdf.js). Cache-first: rendered PNGs stored in MinIO. Requires host app to expose pdf-info + pdf-page API routes (JST provides `/api/files/[id]/pdf-info` and `/api/files/[id]/pdf-page/[page]`). | `fileId`, `fileName`, `pdfUrl` (raw PDF URL for Open/Download), `infoUrl?` (custom info endpoint), `pageUrlTemplate?` (custom page template with `{page}` and `{scale}` placeholders) |
|
|
95
|
+
|
|
96
|
+
**Usage (JST default routes):**
|
|
97
|
+
```svelte
|
|
98
|
+
<PDFViewer fileId={file.id} fileName={file.fileName} pdfUrl="/api/files/{file.id}?download=1" />
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
**Server-side setup (JST):** `src/lib/server/pdfToImage.ts` + routes at `/api/files/[id]/pdf-info` and `/api/files/[id]/pdf-page/[page]`. Requires `@hyzyla/pdfium`, `unpdf`, `sharp` in host app.
|
|
102
|
+
|
|
103
|
+
### Document Annotation Suite
|
|
104
|
+
|
|
105
|
+
| Component | Purpose | Key Props |
|
|
106
|
+
|-----------|---------|-----------|
|
|
107
|
+
| `MarkupEditor` | Fullscreen canvas markup tool — draw arrows, boxes, ellipses, revision clouds, freehand, text over an image | `imageUrl`, `initialShapes?: MarkupShape[]`, `onsave: (shapes) => void`, `oncancel: () => void` |
|
|
108
|
+
| `MarkupOverlay` | Read-only canvas layer that renders saved `MarkupShape[]` over an image | `shapes: MarkupShape[]`, `imageUrl` |
|
|
109
|
+
| `ImageLightbox` | Inline image viewer with fullscreen modal, zoom controls, optional markup + annotation overlays | `imageUrl`, `filename?`, `markupShapes?`, `annotations?`, `activeAnnotationId?`, `annotationMode?`, `onAnnotationClick?`, `onImageClick?`, `onClose?` |
|
|
110
|
+
| `AnnotationLayer` | Absolute-positioned pin overlay — renders threaded annotation pins at `x_percent`/`y_percent` coordinates | `annotations: Annotation[]`, `activeAnnotationId?`, `annotationMode?`, `showResolved?`, `onAnnotationClick?`, `onImageClick?` |
|
|
111
|
+
| `AnnotationPanel` | Sidebar thread list for annotations — reply, resolve, edit, delete. Uses callback props for async ops. | `annotations`, `profiles`, `activeAnnotationId?`, `currentUserId`, `callbacks: AnnotationCallbacks`, `onAnnotationSelect?`, `onAnnotationsChanged?` |
|
|
112
|
+
|
|
113
|
+
**Types & utilities:**
|
|
114
|
+
```typescript
|
|
115
|
+
import type { MarkupShape, MarkupTool, Point, Annotation, AnnotationProfile, AnnotationCallbacks } from "jatui"
|
|
116
|
+
import { MARKUP_COLORS, DEFAULT_STROKE_WIDTH, renderShape, renderAllShapes, nextShapeId } from "jatui"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Usage pattern** — `AnnotationPanel` is decoupled from HTTP: pass an `AnnotationCallbacks` object:
|
|
120
|
+
```typescript
|
|
121
|
+
const callbacks: AnnotationCallbacks = {
|
|
122
|
+
onAddReply: async (parentId, content) => { /* POST /api/.../annotations */ },
|
|
123
|
+
onResolve: async (id, resolved) => { /* PATCH */ },
|
|
124
|
+
onDelete: async (id) => { /* DELETE */ },
|
|
125
|
+
onEdit: async (id, content) => { /* PATCH */ },
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**`MarkupShape` is JSON-serializable** — store in a JSONB column and pass back as `initialShapes`.
|
|
130
|
+
|
|
131
|
+
### Theme
|
|
132
|
+
|
|
133
|
+
| Component | Purpose | Key Props |
|
|
134
|
+
|-----------|---------|-----------|
|
|
135
|
+
| `ThemeSelector` | DaisyUI theme switcher with preview dots | `themes`, `defaultTheme`, `dropUp`, `storageKey`, `onThemeChange` |
|
|
136
|
+
|
|
137
|
+
### Keyboard Navigation
|
|
138
|
+
|
|
139
|
+
| Component | Purpose | Key Props |
|
|
140
|
+
|-----------|---------|-----------|
|
|
141
|
+
| `KeyboardShortcutsOverlay` | `?`-triggered modal listing keyboard shortcuts. Ignores typing targets and Ctrl/Meta/Alt combos. Supports a flat `shortcuts` list or grouped `sections` with optional per-key color tinting. | `shortcuts?: KeyboardShortcut[]`, `sections?: KeyboardShortcutSection[]`, `title?`, `open` (bindable) |
|
|
142
|
+
|
|
143
|
+
### Maps (Google Maps)
|
|
144
|
+
|
|
145
|
+
Require a `PUBLIC_GOOGLE_MAPS_API_KEY` env var passed explicitly (no SvelteKit `$env` dependency in jatui).
|
|
146
|
+
|
|
147
|
+
| Component | Purpose | Key Props |
|
|
148
|
+
|-----------|---------|-----------|
|
|
149
|
+
| `MapView` | Multi-marker dispatch map — job pins (colored by status) + agent avatars with GPS or active-job fallback position. Auto-fits bounds. | `apiKey`, `jobs?: MapJob[]`, `agents?: MapAgent[]`, `defaultCenter?`, `showLegend?`, `onJobClick?`, `onAgentClick?` |
|
|
150
|
+
| `LocationMap` | Single-location embed with pulsing dot + accuracy circle. Shows "Waiting for GPS signal…" while no coords. | `apiKey`, `latitude?`, `longitude?`, `accuracy?`, `showAccuracyCircle?`, `height?` |
|
|
151
|
+
| `GPSTracker` | Headless GPS tracker — watches device position, fires `onLocationUpdate(pos)` on each fix. Host app sends coords to server. Mobile: `watchPosition`; desktop: interval polling. Renders a small fixed status indicator. | `enabled?`, `updateInterval?`, `onLocationUpdate?`, `onError?` |
|
|
152
|
+
|
|
153
|
+
**MapView types:**
|
|
154
|
+
```typescript
|
|
155
|
+
import type { MapJob, MapAgent } from 'jatui'
|
|
156
|
+
// MapJob — id, status, type?, customer?, location? (address/lat/lng)
|
|
157
|
+
// MapAgent — id, name, email?, avatarUrl?, status?, location? (lat/lng/age_seconds), dispatches?
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Google Maps loader (util):**
|
|
161
|
+
```typescript
|
|
162
|
+
import { loadGoogleMapsAPI, isGoogleMapsLoaded } from 'jatui'
|
|
163
|
+
await loadGoogleMapsAPI({ apiKey, libraries: ['marker', 'geometry'] })
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Utilities
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Date formatting
|
|
170
|
+
import { formatRelativeTime, formatDate, formatShortDate, formatDateTime } from "jatui"
|
|
171
|
+
|
|
172
|
+
// Task age — color-coded staleness indicator for task rows
|
|
173
|
+
// Returns { label: "3d", color: "oklch(...)" } — green→blue→amber→orange→red
|
|
174
|
+
import { taskAge } from "jatui"
|
|
175
|
+
|
|
176
|
+
// Task display helpers (project_tasks domain)
|
|
177
|
+
import { statusColor, priorityColor, typeIcon, statusLabel } from "jatui"
|
|
178
|
+
// statusColor(status) → DaisyUI badge class ("badge-info", "badge-warning", ...)
|
|
179
|
+
// priorityColor(priority) → Tailwind text class ("text-error font-bold", ...)
|
|
180
|
+
// typeIcon(type) → emoji ("🐛", "✨", "📋", "🏔️", "📄")
|
|
181
|
+
// statusLabel(status) → "in_progress" → "In Progress"
|
|
182
|
+
|
|
183
|
+
// Phone formatting
|
|
184
|
+
import { formatPhoneNumber, isValidPhoneNumber } from "jatui"
|
|
185
|
+
|
|
186
|
+
// Currency
|
|
187
|
+
import { formatCurrency } from "jatui"
|
|
188
|
+
|
|
189
|
+
// DaisyUI color access (for Canvas/JS)
|
|
190
|
+
import { getDaisyUIColor, isDarkTheme, getCurrentTheme } from "jatui"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Types
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
// Component types
|
|
197
|
+
import type {
|
|
198
|
+
SearchDropdownOption, SearchDropdownGroup,
|
|
199
|
+
ChipSuggestion, SuggestionGroup, ChipInfo,
|
|
200
|
+
SortOption, SelectOption, StatusValue,
|
|
201
|
+
DisplaySegment
|
|
202
|
+
} from "jatui"
|
|
203
|
+
|
|
204
|
+
// Booking types (from src/lib/types/booking.ts)
|
|
205
|
+
import type {
|
|
206
|
+
LocationType, BookingStatus, OverrideType,
|
|
207
|
+
Host, EventType, WeeklyAvailability, DateOverride,
|
|
208
|
+
Booking, BookingAttendee, TimeSlot, BookingFormData,
|
|
209
|
+
AvailabilityCounts
|
|
210
|
+
} from "jatui"
|
|
211
|
+
|
|
212
|
+
// Booking constants
|
|
213
|
+
import { DAYS_OF_WEEK, DAYS_SHORT, LOCATION_LABELS, LOCATION_ICONS } from "jatui"
|
|
214
|
+
|
|
215
|
+
// FileDropzone types
|
|
216
|
+
import type { FileDropzoneFile } from "jatui"
|
|
217
|
+
// FileDropzoneFile — { file?: File, name, mimeType, size?, url?, meta? }
|
|
218
|
+
|
|
219
|
+
// SmartImageEditor types
|
|
220
|
+
import type {
|
|
221
|
+
ImageProcessingSpec, ImageVariantSpec,
|
|
222
|
+
VariantResult, VariantState
|
|
223
|
+
} from "jatui"
|
|
224
|
+
// ImageProcessingSpec — { removeBackground?, aspectRatio?, variants: ImageVariantSpec[] }
|
|
225
|
+
// ImageVariantSpec — { name, width, height, fit?, background? }
|
|
226
|
+
// VariantResult — { name, url, width, height, backgroundRemovalFailed? }
|
|
227
|
+
// VariantState — union: idle | loading | done (VariantResult) | error (message)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Live Demos
|
|
231
|
+
|
|
232
|
+
If the project uses JST (Joe's Software Template), browse component demos at `/components/{component-name}` (kebab-case).
|
|
233
|
+
|
|
234
|
+
## Notes for Agents
|
|
235
|
+
|
|
236
|
+
- All components use **Svelte 5 runes** (`$state`, `$derived`, `$props`, `$bindable`)
|
|
237
|
+
- All components use **DaisyUI classes** (not custom CSS) — they inherit the project's theme
|
|
238
|
+
- Bindable props use `$bindable()` — bind with `bind:value={myVar}`
|
|
239
|
+
- Check the component source in `src/lib/components/` for full prop types
|
|
240
|
+
- Prefer jatui components over building custom ones when there's a match
|
|
241
|
+
|
|
242
|
+
# Accessibility (a11y)
|
|
243
|
+
|
|
244
|
+
@~/code/jat/shared/a11y-patterns.md
|
package/package.json
CHANGED
|
@@ -1,38 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joewinke/jatui",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.24",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Shared Svelte 5 component library for JAT projects",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"svelte": "./
|
|
8
|
-
"types": "./
|
|
7
|
+
"svelte": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
-
"types": "./
|
|
12
|
-
"svelte": "./
|
|
13
|
-
"default": "./
|
|
14
|
-
}
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"svelte": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./styles/rail.css": "./dist/styles/rail.css"
|
|
15
16
|
},
|
|
16
17
|
"files": [
|
|
17
|
-
"
|
|
18
|
+
"dist",
|
|
19
|
+
"CLAUDE.md",
|
|
20
|
+
"README.md"
|
|
18
21
|
],
|
|
19
22
|
"scripts": {
|
|
20
23
|
"dev": "vite dev --port 5180",
|
|
21
24
|
"build": "vite build",
|
|
22
|
-
"preview": "vite preview"
|
|
25
|
+
"preview": "vite preview",
|
|
26
|
+
"package": "svelte-package --input src/lib --output dist"
|
|
23
27
|
},
|
|
24
28
|
"peerDependencies": {
|
|
25
29
|
"d3": "^7.0.0",
|
|
26
|
-
"
|
|
30
|
+
"daisyui": "^5.0.0",
|
|
31
|
+
"svelte": "^5.0.0",
|
|
32
|
+
"tailwindcss": "^4.0.0"
|
|
27
33
|
},
|
|
28
34
|
"peerDependenciesMeta": {
|
|
29
35
|
"d3": {
|
|
30
36
|
"optional": true
|
|
37
|
+
},
|
|
38
|
+
"daisyui": {
|
|
39
|
+
"optional": false
|
|
40
|
+
},
|
|
41
|
+
"tailwindcss": {
|
|
42
|
+
"optional": false
|
|
31
43
|
}
|
|
32
44
|
},
|
|
33
45
|
"devDependencies": {
|
|
34
46
|
"@sveltejs/adapter-auto": "^6.0.0",
|
|
35
47
|
"@sveltejs/kit": "^2.21.0",
|
|
48
|
+
"@sveltejs/package": "^2.5.8",
|
|
36
49
|
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
|
37
50
|
"@tailwindcss/vite": "^4.0.0",
|
|
38
51
|
"@types/d3": "^7.4.3",
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tall-content collapse — Svelte action.
|
|
3
|
-
*
|
|
4
|
-
* Caps an arbitrarily-tall block at a max height with a fade-out gradient so it
|
|
5
|
-
* shows a preview instead of overflowing its container. The consumer renders the
|
|
6
|
-
* "Show more / Show less" toggle and owns the `expanded` boolean; the action does
|
|
7
|
-
* the measuring and applies the collapse styling.
|
|
8
|
-
*
|
|
9
|
-
* Why an action (not a wrapper-only component): most adoption sites are existing
|
|
10
|
-
* elements (`<p>`, a `prose` div, a `<ul>`, a `<pre>`), and an action attaches
|
|
11
|
-
* with zero DOM restructuring. A batteries-included `<Collapse>` wrapper that
|
|
12
|
-
* uses this action is also provided for greenfield use.
|
|
13
|
-
*
|
|
14
|
-
* ─── Why inline styles, not a CSS class ───────────────────────────────────────
|
|
15
|
-
*
|
|
16
|
-
* The action writes `max-height` / `overflow` / `mask-image` directly on the node
|
|
17
|
-
* when collapsed. That means it needs NO accompanying stylesheet — it works
|
|
18
|
-
* identically in Tailwind-utility components and scoped-CSS components, and
|
|
19
|
-
* consumers don't have to copy a `.collapse-*` rule into their app.css (the same
|
|
20
|
-
* trap rail.css has). Expanding clears the inline styles.
|
|
21
|
-
*
|
|
22
|
-
* ─── Why ResizeObserver, not a one-shot rAF ───────────────────────────────────
|
|
23
|
-
*
|
|
24
|
-
* Streaming chat answers and `{@html}` markdown bodies grow AFTER mount. A
|
|
25
|
-
* one-shot measurement on mount would miss them (and would wrongly hide the
|
|
26
|
-
* toggle on content that hasn't streamed in yet). A ResizeObserver re-measures
|
|
27
|
-
* whenever the content's natural height changes, so `collapsible` stays correct.
|
|
28
|
-
*
|
|
29
|
-
* `scrollHeight` reports the FULL content height even while `max-height` +
|
|
30
|
-
* `overflow:hidden` are applied, so the action can keep measuring without ever
|
|
31
|
-
* having to un-collapse first.
|
|
32
|
-
*
|
|
33
|
-
* ─── Usage — action form (most sites) ─────────────────────────────────────────
|
|
34
|
-
*
|
|
35
|
-
* <script>
|
|
36
|
-
* import { collapse } from 'jatui'
|
|
37
|
-
* let collapsible = $state(false)
|
|
38
|
-
* let expanded = $state(false)
|
|
39
|
-
* </script>
|
|
40
|
-
*
|
|
41
|
-
* <div use:collapse={{ expanded, onCollapsible: (c) => (collapsible = c) }}>
|
|
42
|
-
* {content}
|
|
43
|
-
* </div>
|
|
44
|
-
* {#if collapsible}
|
|
45
|
-
* <button onclick={() => (expanded = !expanded)}>
|
|
46
|
-
* {expanded ? 'Show less ↑' : 'Show more ↓'}
|
|
47
|
-
* </button>
|
|
48
|
-
* {/if}
|
|
49
|
-
*
|
|
50
|
-
* For mapped lists, give each row its own `expanded` / `collapsible` state
|
|
51
|
-
* (e.g. a `SvelteSet` of ids) — one action instance attaches per row element.
|
|
52
|
-
*/
|
|
53
|
-
|
|
54
|
-
export interface CollapseOptions {
|
|
55
|
-
/** Max collapsed height in px. Content taller than this collapses. Default 120. */
|
|
56
|
-
threshold?: number;
|
|
57
|
-
/** Controlled expand state. When true, the node is never collapsed. Default false. */
|
|
58
|
-
expanded?: boolean;
|
|
59
|
-
/** Where the fade-to-transparent begins, as a %. Default 55. */
|
|
60
|
-
fadeStart?: number;
|
|
61
|
-
/** Slack (px) added to the threshold so a near-fit doesn't toggle. Default 4. */
|
|
62
|
-
epsilon?: number;
|
|
63
|
-
/**
|
|
64
|
-
* Called whenever collapsibility changes (content crosses the threshold).
|
|
65
|
-
* Drive the toggle button's visibility from this — it self-hides when the
|
|
66
|
-
* content is shorter than the threshold.
|
|
67
|
-
*/
|
|
68
|
-
onCollapsible?: (collapsible: boolean) => void;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const DEFAULTS = { threshold: 120, expanded: false, fadeStart: 55, epsilon: 4 } as const;
|
|
72
|
-
|
|
73
|
-
export function collapse(node: HTMLElement, options: CollapseOptions = {}) {
|
|
74
|
-
let opts = { ...DEFAULTS, ...options };
|
|
75
|
-
let collapsible = false;
|
|
76
|
-
let rafId = 0;
|
|
77
|
-
let applied = false;
|
|
78
|
-
|
|
79
|
-
function gradient() {
|
|
80
|
-
return `linear-gradient(to bottom, black ${opts.fadeStart}%, transparent 100%)`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function apply() {
|
|
84
|
-
const shouldCollapse = collapsible && !opts.expanded;
|
|
85
|
-
if (shouldCollapse === applied) return; // idempotent — avoids RO feedback loops
|
|
86
|
-
applied = shouldCollapse;
|
|
87
|
-
if (shouldCollapse) {
|
|
88
|
-
node.style.maxHeight = `${opts.threshold}px`;
|
|
89
|
-
node.style.overflow = 'hidden';
|
|
90
|
-
node.style.maskImage = gradient();
|
|
91
|
-
node.style.webkitMaskImage = gradient();
|
|
92
|
-
} else {
|
|
93
|
-
node.style.maxHeight = '';
|
|
94
|
-
node.style.overflow = '';
|
|
95
|
-
node.style.maskImage = '';
|
|
96
|
-
node.style.webkitMaskImage = '';
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function measure() {
|
|
101
|
-
// scrollHeight is the full content height even while collapsed.
|
|
102
|
-
const next = node.scrollHeight > opts.threshold + opts.epsilon;
|
|
103
|
-
if (next !== collapsible) {
|
|
104
|
-
collapsible = next;
|
|
105
|
-
opts.onCollapsible?.(collapsible);
|
|
106
|
-
}
|
|
107
|
-
apply();
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function scheduleMeasure() {
|
|
111
|
-
cancelAnimationFrame(rafId);
|
|
112
|
-
rafId = requestAnimationFrame(measure);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Re-measure when the content's natural size changes (streaming, {@html}, lazy images).
|
|
116
|
-
const ro = new ResizeObserver(scheduleMeasure);
|
|
117
|
-
ro.observe(node);
|
|
118
|
-
|
|
119
|
-
scheduleMeasure();
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
update(next: CollapseOptions = {}) {
|
|
123
|
-
opts = { ...DEFAULTS, ...next };
|
|
124
|
-
// expanded/threshold/fade may have changed — re-measure and let
|
|
125
|
-
// apply() compare the freshly computed shouldCollapse against the
|
|
126
|
-
// real last-applied state. (Force-flipping `applied` here used to make
|
|
127
|
-
// that comparison spuriously match on a plain expand/collapse toggle,
|
|
128
|
-
// silently skipping the style update.)
|
|
129
|
-
scheduleMeasure();
|
|
130
|
-
},
|
|
131
|
-
destroy() {
|
|
132
|
-
cancelAnimationFrame(rafId);
|
|
133
|
-
ro.disconnect();
|
|
134
|
-
},
|
|
135
|
-
};
|
|
136
|
-
}
|