@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.
Files changed (242) hide show
  1. package/dist/actions/collapse.d.ts +80 -0
  2. package/dist/actions/collapse.js +134 -0
  3. package/dist/actions/railNav.d.ts +167 -0
  4. package/dist/actions/railNav.js +351 -0
  5. package/dist/components/AnnotationLayer.svelte +108 -0
  6. package/dist/components/AnnotationLayer.svelte.d.ts +12 -0
  7. package/dist/components/AnnotationPanel.svelte +319 -0
  8. package/dist/components/AnnotationPanel.svelte.d.ts +13 -0
  9. package/dist/components/AudioWaveform.svelte +701 -0
  10. package/dist/components/AudioWaveform.svelte.d.ts +19 -0
  11. package/dist/components/AvailabilityModal.svelte +177 -0
  12. package/dist/components/AvailabilityModal.svelte.d.ts +14 -0
  13. package/dist/components/AvatarUpload.svelte +234 -0
  14. package/dist/components/AvatarUpload.svelte.d.ts +23 -0
  15. package/dist/components/Badge.svelte +38 -0
  16. package/dist/components/Badge.svelte.d.ts +14 -0
  17. package/dist/components/BookingForm.svelte +278 -0
  18. package/dist/components/BookingForm.svelte.d.ts +37 -0
  19. package/dist/components/BurndownChart.svelte +778 -0
  20. package/dist/components/BurndownChart.svelte.d.ts +17 -0
  21. package/dist/components/Button.svelte +81 -0
  22. package/dist/components/Button.svelte.d.ts +21 -0
  23. package/dist/components/CalendarPicker.svelte +284 -0
  24. package/dist/components/CalendarPicker.svelte.d.ts +22 -0
  25. package/dist/components/Card.svelte +67 -0
  26. package/dist/components/Card.svelte.d.ts +17 -0
  27. package/dist/components/CharacterCounter.svelte +82 -0
  28. package/dist/components/CharacterCounter.svelte.d.ts +10 -0
  29. package/dist/components/ChipInput.svelte +609 -0
  30. package/dist/components/ChipInput.svelte.d.ts +55 -0
  31. package/dist/components/Collapse.svelte +91 -0
  32. package/dist/components/Collapse.svelte.d.ts +18 -0
  33. package/dist/components/ColorSelector.svelte +167 -0
  34. package/dist/components/ColorSelector.svelte.d.ts +9 -0
  35. package/dist/components/CommentThread.svelte +773 -0
  36. package/dist/components/CommentThread.svelte.d.ts +32 -0
  37. package/dist/components/ConfirmDialog.svelte +348 -0
  38. package/dist/components/ConfirmDialog.svelte.d.ts +3 -0
  39. package/dist/components/ConfirmModal.svelte +142 -0
  40. package/dist/components/ConfirmModal.svelte.d.ts +15 -0
  41. package/dist/components/ContextMenu.svelte +188 -0
  42. package/dist/components/ContextMenu.svelte.d.ts +31 -0
  43. package/dist/components/CountdownTimer.svelte +94 -0
  44. package/dist/components/CountdownTimer.svelte.d.ts +10 -0
  45. package/dist/components/DateRangePicker.svelte +194 -0
  46. package/dist/components/DateRangePicker.svelte.d.ts +10 -0
  47. package/dist/components/Drawer.svelte +143 -0
  48. package/dist/components/Drawer.svelte.d.ts +33 -0
  49. package/dist/components/EmojiPicker.svelte +159 -0
  50. package/dist/components/EmojiPicker.svelte.d.ts +8 -0
  51. package/dist/components/EntityPreviewCard.svelte +104 -0
  52. package/dist/components/EntityPreviewCard.svelte.d.ts +9 -0
  53. package/dist/components/FileDropzone.svelte +493 -0
  54. package/dist/components/FileDropzone.svelte.d.ts +40 -0
  55. package/dist/components/FilePicker.svelte +488 -0
  56. package/dist/components/FilePicker.svelte.d.ts +20 -0
  57. package/dist/components/FileThumbnail.svelte +80 -0
  58. package/dist/components/FileThumbnail.svelte.d.ts +12 -0
  59. package/dist/components/FilterDropdown.svelte +203 -0
  60. package/dist/components/FilterDropdown.svelte.d.ts +48 -0
  61. package/dist/components/FloatingActionBar.svelte +354 -0
  62. package/dist/components/FloatingActionBar.svelte.d.ts +51 -0
  63. package/dist/components/GPSTracker.svelte +202 -0
  64. package/dist/components/GPSTracker.svelte.d.ts +34 -0
  65. package/dist/components/HunkDiffView.svelte +348 -0
  66. package/dist/components/HunkDiffView.svelte.d.ts +9 -0
  67. package/dist/components/ImageLightbox.svelte +274 -0
  68. package/dist/components/ImageLightbox.svelte.d.ts +25 -0
  69. package/dist/components/ImageUpload.svelte +147 -0
  70. package/dist/components/ImageUpload.svelte.d.ts +10 -0
  71. package/dist/components/InlineEdit.svelte +291 -0
  72. package/dist/components/InlineEdit.svelte.d.ts +45 -0
  73. package/dist/components/InputDialog.svelte +327 -0
  74. package/dist/components/InputDialog.svelte.d.ts +3 -0
  75. package/dist/components/KeyboardShortcutsOverlay.svelte +296 -0
  76. package/dist/components/KeyboardShortcutsOverlay.svelte.d.ts +42 -0
  77. package/dist/components/LazyImage.svelte +123 -0
  78. package/dist/components/LazyImage.svelte.d.ts +14 -0
  79. package/dist/components/LinkShortener.svelte +274 -0
  80. package/dist/components/LinkShortener.svelte.d.ts +27 -0
  81. package/dist/components/LoadingSpinner.svelte +106 -0
  82. package/dist/components/LoadingSpinner.svelte.d.ts +21 -0
  83. package/dist/components/LocationMap.svelte +186 -0
  84. package/dist/components/LocationMap.svelte.d.ts +11 -0
  85. package/dist/components/MapView.svelte +341 -0
  86. package/dist/components/MapView.svelte.d.ts +20 -0
  87. package/dist/components/MarkupEditor.svelte +485 -0
  88. package/dist/components/MarkupEditor.svelte.d.ts +10 -0
  89. package/dist/components/MarkupOverlay.svelte +55 -0
  90. package/dist/components/MarkupOverlay.svelte.d.ts +8 -0
  91. package/dist/components/MediaWorkbench.svelte +871 -0
  92. package/dist/components/MediaWorkbench.svelte.d.ts +9 -0
  93. package/dist/components/MilestoneCard.svelte +98 -0
  94. package/dist/components/MilestoneCard.svelte.d.ts +16 -0
  95. package/dist/components/MilestoneTimeline.svelte +91 -0
  96. package/dist/components/MilestoneTimeline.svelte.d.ts +15 -0
  97. package/dist/components/Modal.svelte +244 -0
  98. package/dist/components/Modal.svelte.d.ts +35 -0
  99. package/dist/components/PDFViewer.svelte +105 -0
  100. package/dist/components/PDFViewer.svelte.d.ts +9 -0
  101. package/dist/components/PdfThumbnail.svelte +63 -0
  102. package/dist/components/PdfThumbnail.svelte.d.ts +9 -0
  103. package/dist/components/PhoneInput.svelte +212 -0
  104. package/dist/components/PhoneInput.svelte.d.ts +25 -0
  105. package/dist/components/ResizableDivider.svelte +307 -0
  106. package/dist/components/ResizableDivider.svelte.d.ts +26 -0
  107. package/dist/components/ResizablePanel.svelte +305 -0
  108. package/dist/components/ResizablePanel.svelte.d.ts +18 -0
  109. package/dist/components/SearchDropdown.svelte +421 -0
  110. package/dist/components/SearchDropdown.svelte.d.ts +24 -0
  111. package/dist/components/SelectInput.svelte +237 -0
  112. package/dist/components/SelectInput.svelte.d.ts +26 -0
  113. package/dist/components/SidebarUserFooter.svelte +34 -0
  114. package/dist/components/SidebarUserFooter.svelte.d.ts +16 -0
  115. package/dist/components/SignaturePad.svelte +175 -0
  116. package/dist/components/SignaturePad.svelte.d.ts +17 -0
  117. package/dist/components/SmartImageEditor.svelte +720 -0
  118. package/dist/components/SmartImageEditor.svelte.d.ts +24 -0
  119. package/dist/components/SortDropdown.svelte +156 -0
  120. package/dist/components/SortDropdown.svelte.d.ts +40 -0
  121. package/dist/components/Sparkline.svelte +116 -0
  122. package/dist/components/Sparkline.svelte.d.ts +12 -0
  123. package/dist/components/SpeechForm.svelte +114 -0
  124. package/dist/components/SpeechForm.svelte.d.ts +17 -0
  125. package/dist/components/StatusBadge.svelte +157 -0
  126. package/dist/components/StatusBadge.svelte.d.ts +24 -0
  127. package/dist/components/TaskTypeIcon.svelte +54 -0
  128. package/dist/components/TaskTypeIcon.svelte.d.ts +17 -0
  129. package/dist/components/TextArea.svelte +162 -0
  130. package/dist/components/TextArea.svelte.d.ts +30 -0
  131. package/dist/components/TextInput.svelte +131 -0
  132. package/dist/components/TextInput.svelte.d.ts +29 -0
  133. package/dist/components/ThemeSelector.svelte +206 -0
  134. package/dist/components/ThemeSelector.svelte.d.ts +14 -0
  135. package/dist/components/TimeSlotPicker.svelte +162 -0
  136. package/dist/components/TimeSlotPicker.svelte.d.ts +18 -0
  137. package/dist/components/UserAvatar.svelte +72 -0
  138. package/dist/components/UserAvatar.svelte.d.ts +11 -0
  139. package/dist/components/VariablePicker.svelte +170 -0
  140. package/dist/components/VariablePicker.svelte.d.ts +40 -0
  141. package/dist/components/VoicePlayer.svelte +422 -0
  142. package/dist/components/VoicePlayer.svelte.d.ts +18 -0
  143. package/dist/components/linked-columns/LinkedColumns.svelte +526 -0
  144. package/dist/components/linked-columns/LinkedColumns.svelte.d.ts +130 -0
  145. package/dist/components/markup.d.ts +47 -0
  146. package/dist/components/markup.js +201 -0
  147. package/dist/components/messaging/Avatar.svelte +81 -0
  148. package/dist/components/messaging/Avatar.svelte.d.ts +11 -0
  149. package/dist/components/messaging/ChannelInfoModal.svelte +163 -0
  150. package/dist/components/messaging/ChannelInfoModal.svelte.d.ts +18 -0
  151. package/dist/components/messaging/ChannelList.svelte +107 -0
  152. package/dist/components/messaging/ChannelList.svelte.d.ts +11 -0
  153. package/dist/components/messaging/ChannelMemberAvatarStack.svelte +69 -0
  154. package/dist/components/messaging/ChannelMemberAvatarStack.svelte.d.ts +11 -0
  155. package/dist/components/messaging/ChannelMembersModal.svelte +182 -0
  156. package/dist/components/messaging/ChannelMembersModal.svelte.d.ts +16 -0
  157. package/dist/components/messaging/CreateChannelModal.svelte +190 -0
  158. package/dist/components/messaging/CreateChannelModal.svelte.d.ts +14 -0
  159. package/dist/components/messaging/DirectMessageList.svelte +145 -0
  160. package/dist/components/messaging/DirectMessageList.svelte.d.ts +12 -0
  161. package/dist/components/messaging/EmojiSelector.svelte +261 -0
  162. package/dist/components/messaging/EmojiSelector.svelte.d.ts +19 -0
  163. package/dist/components/messaging/MentionAutocomplete.svelte +193 -0
  164. package/dist/components/messaging/MentionAutocomplete.svelte.d.ts +17 -0
  165. package/dist/components/messaging/MessageAttachment.svelte +273 -0
  166. package/dist/components/messaging/MessageAttachment.svelte.d.ts +13 -0
  167. package/dist/components/messaging/MessageAttachmentUpload.svelte +243 -0
  168. package/dist/components/messaging/MessageAttachmentUpload.svelte.d.ts +19 -0
  169. package/dist/components/messaging/MessageInput.svelte +452 -0
  170. package/dist/components/messaging/MessageInput.svelte.d.ts +28 -0
  171. package/dist/components/messaging/MessageItem.svelte +341 -0
  172. package/dist/components/messaging/MessageItem.svelte.d.ts +41 -0
  173. package/dist/components/messaging/MessageThread.svelte +306 -0
  174. package/dist/components/messaging/MessageThread.svelte.d.ts +30 -0
  175. package/dist/components/messaging/NotificationSettingsModal.svelte +234 -0
  176. package/dist/components/messaging/NotificationSettingsModal.svelte.d.ts +13 -0
  177. package/dist/components/messaging/QuotedMessageDisplay.svelte +123 -0
  178. package/dist/components/messaging/QuotedMessageDisplay.svelte.d.ts +10 -0
  179. package/dist/components/messaging/StartDMModal.svelte +100 -0
  180. package/dist/components/messaging/StartDMModal.svelte.d.ts +14 -0
  181. package/dist/components/messaging/ThreadPanel.svelte +153 -0
  182. package/dist/components/messaging/ThreadPanel.svelte.d.ts +16 -0
  183. package/dist/components/pipeline/DESIGN.md +361 -0
  184. package/dist/components/pipeline/Pipeline.svelte +391 -0
  185. package/dist/components/pipeline/Pipeline.svelte.d.ts +28 -0
  186. package/dist/components/pipeline/PipelineCard.svelte +94 -0
  187. package/dist/components/pipeline/PipelineCard.svelte.d.ts +13 -0
  188. package/dist/components/pipeline/PipelineColumn.svelte +163 -0
  189. package/dist/components/pipeline/PipelineColumn.svelte.d.ts +30 -0
  190. package/dist/components/replay/ChapterTimeline.svelte +326 -0
  191. package/dist/components/replay/ChapterTimeline.svelte.d.ts +39 -0
  192. package/dist/components/session-nav/transcriptModel.d.ts +132 -0
  193. package/dist/components/session-nav/transcriptModel.js +285 -0
  194. package/dist/data/emojis.d.ts +18 -0
  195. package/dist/data/emojis.js +1196 -0
  196. package/dist/index.d.ts +142 -0
  197. package/dist/index.js +142 -0
  198. package/dist/stores/confirmDialog.svelte.d.ts +21 -0
  199. package/dist/stores/confirmDialog.svelte.js +31 -0
  200. package/dist/stores/inputDialog.svelte.d.ts +24 -0
  201. package/dist/stores/inputDialog.svelte.js +32 -0
  202. package/dist/styles/rail.css +63 -0
  203. package/dist/types/annotation.d.ts +32 -0
  204. package/dist/types/annotation.js +3 -0
  205. package/dist/types/booking.d.ts +115 -0
  206. package/dist/types/booking.js +24 -0
  207. package/dist/types/comments.d.ts +65 -0
  208. package/dist/types/comments.js +22 -0
  209. package/dist/types/entityPreview.d.ts +42 -0
  210. package/dist/types/entityPreview.js +10 -0
  211. package/dist/types/filePicker.d.ts +15 -0
  212. package/dist/types/filePicker.js +1 -0
  213. package/dist/types/googleMaps.d.ts +51 -0
  214. package/dist/types/maps.d.ts +43 -0
  215. package/dist/types/maps.js +4 -0
  216. package/dist/types/messaging.d.ts +282 -0
  217. package/dist/types/messaging.js +100 -0
  218. package/dist/types/milestone.d.ts +20 -0
  219. package/dist/types/milestone.js +7 -0
  220. package/dist/types/pipeline.d.ts +115 -0
  221. package/dist/types/pipeline.js +15 -0
  222. package/dist/types/smartImageEditor.d.ts +42 -0
  223. package/dist/types/smartImageEditor.js +1 -0
  224. package/dist/types/templateVars.d.ts +34 -0
  225. package/dist/types/templateVars.js +12 -0
  226. package/dist/utils/currency.d.ts +8 -0
  227. package/dist/utils/currency.js +17 -0
  228. package/dist/utils/daisyuiColors.d.ts +37 -0
  229. package/dist/utils/daisyuiColors.js +152 -0
  230. package/dist/utils/dateFormatters.d.ts +69 -0
  231. package/dist/utils/dateFormatters.js +199 -0
  232. package/dist/utils/googleMapsLoader.d.ts +19 -0
  233. package/dist/utils/googleMapsLoader.js +121 -0
  234. package/dist/utils/mentionParser.d.ts +67 -0
  235. package/dist/utils/mentionParser.js +132 -0
  236. package/dist/utils/phone.d.ts +13 -0
  237. package/dist/utils/phone.js +67 -0
  238. package/dist/utils/phoneFormat.d.ts +26 -0
  239. package/dist/utils/phoneFormat.js +69 -0
  240. package/dist/utils/taskUtils.d.ts +37 -0
  241. package/dist/utils/taskUtils.js +71 -0
  242. package/package.json +3 -2
@@ -0,0 +1,162 @@
1
+ <script lang="ts">
2
+ /**
3
+ * TimeSlotPicker — Display available time slots grouped by morning/afternoon/evening.
4
+ *
5
+ * Accepts an array of TimeSlot objects with `start` (Date), `end` (Date), and `available` (boolean).
6
+ * Grouped automatically by hour: morning (<12), afternoon (<17), evening (17+).
7
+ */
8
+
9
+ import type { TimeSlot } from '../types/booking';
10
+
11
+ export type { TimeSlot };
12
+
13
+ let {
14
+ slots,
15
+ loading = false,
16
+ onSelect,
17
+ accentColor = 'primary',
18
+ }: {
19
+ slots: TimeSlot[];
20
+ loading?: boolean;
21
+ onSelect: (slot: TimeSlot) => void;
22
+ accentColor?: string;
23
+ } = $props();
24
+
25
+ const groupedSlots = $derived(() => {
26
+ const morning: TimeSlot[] = [];
27
+ const afternoon: TimeSlot[] = [];
28
+ const evening: TimeSlot[] = [];
29
+
30
+ for (const slot of slots) {
31
+ const hour = slot.start.getHours();
32
+ if (hour < 12) {
33
+ morning.push(slot);
34
+ } else if (hour < 17) {
35
+ afternoon.push(slot);
36
+ } else {
37
+ evening.push(slot);
38
+ }
39
+ }
40
+
41
+ return { morning, afternoon, evening };
42
+ });
43
+
44
+ function formatTime(date: Date): string {
45
+ return date.toLocaleTimeString('en-US', {
46
+ hour: 'numeric',
47
+ minute: '2-digit',
48
+ hour12: true
49
+ });
50
+ }
51
+
52
+ function handleSlotClick(slot: TimeSlot) {
53
+ if (slot.available) {
54
+ onSelect(slot);
55
+ }
56
+ }
57
+ </script>
58
+
59
+ <div class="time-slot-picker">
60
+ {#if loading}
61
+ <div class="flex justify-center py-12">
62
+ <span class="loading loading-spinner loading-lg text-{accentColor}"></span>
63
+ </div>
64
+ {:else if slots.length === 0}
65
+ <div class="text-center py-12">
66
+ <div class="w-16 h-16 mx-auto mb-4 rounded-full bg-base-200 flex items-center justify-center">
67
+ <!-- Clock icon -->
68
+ <svg class="w-8 h-8 text-base-content/40" fill="none" stroke="currentColor" viewBox="0 0 24 24">
69
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
70
+ </svg>
71
+ </div>
72
+ <h3 class="text-lg font-semibold mb-2">No Available Times</h3>
73
+ <p class="text-base-content/60">Please select a different date.</p>
74
+ </div>
75
+ {:else}
76
+ {@const { morning, afternoon, evening } = groupedSlots()}
77
+ {@const availableCount = slots.filter(s => s.available).length}
78
+
79
+ <p class="text-[0.8125rem] text-base-content/45 mb-4">
80
+ {availableCount} time{availableCount !== 1 ? 's' : ''} available
81
+ </p>
82
+
83
+ <div class="space-y-6">
84
+ {#if morning.length > 0}
85
+ <div>
86
+ <h4 class="text-[0.8125rem] text-base-content/45 mb-3 flex items-center gap-2 tracking-[0.005em]">
87
+ <!-- Sun icon -->
88
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
89
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
90
+ </svg>
91
+ Morning
92
+ </h4>
93
+ <div class="grid grid-cols-3 sm:grid-cols-4 gap-2">
94
+ {#each morning as slot}
95
+ <button
96
+ onclick={() => handleSlotClick(slot)}
97
+ disabled={!slot.available}
98
+ class="py-3 px-4 rounded-lg text-[0.9375rem] font-medium transition-all duration-200
99
+ {slot.available
100
+ ? `bg-base-200/50 hover:bg-${accentColor} hover:text-${accentColor}-content`
101
+ : 'bg-base-200/20 text-base-content/30 cursor-not-allowed line-through'}"
102
+ >
103
+ {formatTime(slot.start)}
104
+ </button>
105
+ {/each}
106
+ </div>
107
+ </div>
108
+ {/if}
109
+
110
+ {#if afternoon.length > 0}
111
+ <div>
112
+ <h4 class="text-[0.8125rem] text-base-content/45 mb-3 flex items-center gap-2 tracking-[0.005em]">
113
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
114
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
115
+ </svg>
116
+ Afternoon
117
+ </h4>
118
+ <div class="grid grid-cols-3 sm:grid-cols-4 gap-2">
119
+ {#each afternoon as slot}
120
+ <button
121
+ onclick={() => handleSlotClick(slot)}
122
+ disabled={!slot.available}
123
+ class="py-3 px-4 rounded-lg text-[0.9375rem] font-medium transition-all duration-200
124
+ {slot.available
125
+ ? `bg-base-200/50 hover:bg-${accentColor} hover:text-${accentColor}-content`
126
+ : 'bg-base-200/20 text-base-content/30 cursor-not-allowed line-through'}"
127
+ >
128
+ {formatTime(slot.start)}
129
+ </button>
130
+ {/each}
131
+ </div>
132
+ </div>
133
+ {/if}
134
+
135
+ {#if evening.length > 0}
136
+ <div>
137
+ <h4 class="text-[0.8125rem] text-base-content/45 mb-3 flex items-center gap-2 tracking-[0.005em]">
138
+ <!-- Moon icon -->
139
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
140
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
141
+ </svg>
142
+ Evening
143
+ </h4>
144
+ <div class="grid grid-cols-3 sm:grid-cols-4 gap-2">
145
+ {#each evening as slot}
146
+ <button
147
+ onclick={() => handleSlotClick(slot)}
148
+ disabled={!slot.available}
149
+ class="py-3 px-4 rounded-lg text-[0.9375rem] font-medium transition-all duration-200
150
+ {slot.available
151
+ ? `bg-base-200/50 hover:bg-${accentColor} hover:text-${accentColor}-content`
152
+ : 'bg-base-200/20 text-base-content/30 cursor-not-allowed line-through'}"
153
+ >
154
+ {formatTime(slot.start)}
155
+ </button>
156
+ {/each}
157
+ </div>
158
+ </div>
159
+ {/if}
160
+ </div>
161
+ {/if}
162
+ </div>
@@ -0,0 +1,18 @@
1
+ /**
2
+ * TimeSlotPicker — Display available time slots grouped by morning/afternoon/evening.
3
+ *
4
+ * Accepts an array of TimeSlot objects with `start` (Date), `end` (Date), and `available` (boolean).
5
+ * Grouped automatically by hour: morning (<12), afternoon (<17), evening (17+).
6
+ */
7
+ import type { TimeSlot } from '../types/booking';
8
+ type $$ComponentProps = {
9
+ slots: TimeSlot[];
10
+ loading?: boolean;
11
+ onSelect: (slot: TimeSlot) => void;
12
+ accentColor?: string;
13
+ };
14
+ declare const TimeSlotPicker: import("svelte").Component<$$ComponentProps, {
15
+ TimeSlot: typeof TimeSlot;
16
+ }, "">;
17
+ type TimeSlotPicker = ReturnType<typeof TimeSlotPicker>;
18
+ export default TimeSlotPicker;
@@ -0,0 +1,72 @@
1
+ <!--
2
+ UserAvatar — shows a user's avatar image or initials fallback.
3
+ Generic component with no DB type coupling.
4
+ For use anywhere a user needs to be visually represented.
5
+ -->
6
+ <script lang="ts">
7
+ interface Props {
8
+ name?: string | null
9
+ email?: string | null
10
+ avatarUrl?: string | null
11
+ size?: 'xs' | 'sm' | 'md' | 'lg'
12
+ tooltip?: boolean
13
+ class?: string
14
+ }
15
+
16
+ let {
17
+ name = null,
18
+ email = null,
19
+ avatarUrl = null,
20
+ size = 'sm',
21
+ tooltip = true,
22
+ class: className = '',
23
+ }: Props = $props()
24
+
25
+ const sizeClasses: Record<string, string> = {
26
+ xs: 'w-5 h-5 text-[9px]',
27
+ sm: 'w-7 h-7 text-xs',
28
+ md: 'w-9 h-9 text-sm',
29
+ lg: 'w-12 h-12 text-base',
30
+ }
31
+
32
+ const initials = $derived.by(() => {
33
+ const source = name || email || ''
34
+ if (!source) return '?'
35
+ // For email, use first char before @
36
+ const parts = source.includes('@')
37
+ ? [source.split('@')[0]]
38
+ : source.split(' ').filter(Boolean)
39
+ if (parts.length >= 2) return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase()
40
+ return parts[0]?.charAt(0).toUpperCase() || '?'
41
+ })
42
+
43
+ const label = $derived(name || email || 'Unknown')
44
+
45
+ // Derive a consistent color per user from their name/email
46
+ const avatarBg = $derived.by(() => {
47
+ const source = name || email || ''
48
+ if (!source) return 'oklch(0.65 0.10 240)'
49
+ let hash = 0
50
+ for (let i = 0; i < source.length; i++) {
51
+ hash = source.charCodeAt(i) + ((hash << 5) - hash)
52
+ }
53
+ const hues = [30, 85, 145, 200, 240, 270, 310, 350]
54
+ const hue = hues[Math.abs(hash) % hues.length]
55
+ return `oklch(0.62 0.14 ${hue})`
56
+ })
57
+ </script>
58
+
59
+ <div
60
+ class="inline-flex shrink-0 {className}"
61
+ title={tooltip ? label : undefined}
62
+ >
63
+ <div class="rounded-full overflow-hidden {sizeClasses[size]}">
64
+ {#if avatarUrl}
65
+ <img src={avatarUrl} alt={label} class="w-full h-full object-cover" />
66
+ {:else}
67
+ <div class="w-full h-full flex items-center justify-center font-semibold text-white" style="background: {avatarBg}">
68
+ {initials}
69
+ </div>
70
+ {/if}
71
+ </div>
72
+ </div>
@@ -0,0 +1,11 @@
1
+ interface Props {
2
+ name?: string | null;
3
+ email?: string | null;
4
+ avatarUrl?: string | null;
5
+ size?: 'xs' | 'sm' | 'md' | 'lg';
6
+ tooltip?: boolean;
7
+ class?: string;
8
+ }
9
+ declare const UserAvatar: import("svelte").Component<Props, {}, "">;
10
+ type UserAvatar = ReturnType<typeof UserAvatar>;
11
+ export default UserAvatar;
@@ -0,0 +1,170 @@
1
+ <script lang="ts">
2
+ /**
3
+ * VariablePicker — browse and insert template merge variables.
4
+ *
5
+ * Renders the available merge variables for a document template, grouped by
6
+ * context (client / org / financial / custom). Clicking a variable inserts
7
+ * its `{{name}}` token at the caret of a bound textarea/input and/or fires
8
+ * an `onInsert` callback.
9
+ *
10
+ * Source of `groups`: the JST server-side registry
11
+ * `getVarsForCategory(category, template.available_vars)` in
12
+ * `src/lib/server/template-vars.ts`. Compute it in a `+page.server.ts` load
13
+ * (the registry is server-only) and pass the result straight in.
14
+ *
15
+ * This is a self-contained panel (not a modal) so it can sit beside a
16
+ * template editor as a sidebar. Wrap it in your own Drawer/Modal if needed.
17
+ */
18
+
19
+ import type { TemplateVar, TemplateVarGroup } from "../types/templateVars"
20
+
21
+ interface Props {
22
+ /** Variable groups to show. From `getVarsForCategory(...)`. */
23
+ groups: TemplateVarGroup[]
24
+ /**
25
+ * Optional textarea/input to insert into. When set, clicking a variable
26
+ * splices `{{name}}` at the current caret, restores focus + caret after
27
+ * the token, and dispatches an `input` event so `bind:value` updates.
28
+ */
29
+ targetEl?: HTMLTextAreaElement | HTMLInputElement | null
30
+ /** Called with the inserted token (`{{name}}`) and the bare var name. */
31
+ onInsert?: (token: string, varName: string) => void
32
+ /** Show the filter box. Default true. */
33
+ searchable?: boolean
34
+ /** Denser layout for narrow sidebars. Default false. */
35
+ compact?: boolean
36
+ /** Panel heading. Default "Variables". */
37
+ title?: string
38
+ /** Shown when there are no groups/vars at all. */
39
+ emptyText?: string
40
+ }
41
+
42
+ let {
43
+ groups,
44
+ targetEl = null,
45
+ onInsert,
46
+ searchable = true,
47
+ compact = false,
48
+ title = "Variables",
49
+ emptyText = "No variables available for this template.",
50
+ }: Props = $props()
51
+
52
+ let query = $state("")
53
+
54
+ const totalVars = $derived(
55
+ groups.reduce((n, g) => n + g.vars.length, 0),
56
+ )
57
+
58
+ // Filter vars by name/label/description, dropping groups left empty.
59
+ const filteredGroups = $derived.by<TemplateVarGroup[]>(() => {
60
+ const q = query.trim().toLowerCase()
61
+ if (!q) return groups
62
+ const out: TemplateVarGroup[] = []
63
+ for (const g of groups) {
64
+ const vars = g.vars.filter(
65
+ (v) =>
66
+ v.name.toLowerCase().includes(q) ||
67
+ v.label.toLowerCase().includes(q) ||
68
+ (v.description?.toLowerCase().includes(q) ?? false),
69
+ )
70
+ if (vars.length > 0) out.push({ ...g, vars })
71
+ }
72
+ return out
73
+ })
74
+
75
+ function tokenFor(v: TemplateVar): string {
76
+ return `{{${v.name}}}`
77
+ }
78
+
79
+ function insert(v: TemplateVar) {
80
+ const token = tokenFor(v)
81
+
82
+ if (targetEl) {
83
+ const el = targetEl
84
+ const start = el.selectionStart ?? el.value.length
85
+ const end = el.selectionEnd ?? el.value.length
86
+ el.value = el.value.slice(0, start) + token + el.value.slice(end)
87
+ const caret = start + token.length
88
+ // Restore focus + caret after the inserted token.
89
+ el.focus()
90
+ el.setSelectionRange(caret, caret)
91
+ // Notify Svelte bindings / listeners.
92
+ el.dispatchEvent(new Event("input", { bubbles: true }))
93
+ }
94
+
95
+ onInsert?.(token, v.name)
96
+ }
97
+ </script>
98
+
99
+ <div
100
+ class="border-base-300 bg-base-100 flex flex-col rounded-box border"
101
+ class:text-sm={compact}
102
+ >
103
+ <div class="border-base-300 flex items-center justify-between border-b px-4 py-3">
104
+ <h3 class="font-semibold">{title}</h3>
105
+ {#if totalVars > 0}
106
+ <span class="badge badge-ghost badge-sm">{totalVars}</span>
107
+ {/if}
108
+ </div>
109
+
110
+ {#if searchable && totalVars > 0}
111
+ <div class="border-base-300 border-b p-3">
112
+ <input
113
+ type="text"
114
+ class="input input-bordered input-sm w-full"
115
+ placeholder="Filter variables…"
116
+ aria-label="Filter variables"
117
+ bind:value={query}
118
+ />
119
+ </div>
120
+ {/if}
121
+
122
+ <div class="flex-1 overflow-y-auto p-2" class:max-h-96={!compact}>
123
+ {#if totalVars === 0}
124
+ <p class="text-base-content/60 px-2 py-6 text-center text-sm">
125
+ {emptyText}
126
+ </p>
127
+ {:else if filteredGroups.length === 0}
128
+ <p class="text-base-content/60 px-2 py-6 text-center text-sm">
129
+ No variables match "{query}".
130
+ </p>
131
+ {:else}
132
+ {#each filteredGroups as group (group.id)}
133
+ <div class="mb-3 last:mb-0">
134
+ <div
135
+ class="text-base-content/45 px-2 pb-1 text-[0.8125rem] tracking-[0.005em]"
136
+ >
137
+ {group.label}
138
+ </div>
139
+ <div class="flex flex-col gap-0.5">
140
+ {#each group.vars as v (v.name)}
141
+ <button
142
+ type="button"
143
+ class="hover:bg-base-200 focus-visible:bg-base-200 group flex w-full flex-col items-start gap-0.5 rounded-md px-2 py-1.5 text-left transition-colors"
144
+ onclick={() => insert(v)}
145
+ title={`Insert ${tokenFor(v)}`}
146
+ >
147
+ <div class="flex w-full items-baseline gap-2">
148
+ <span class="font-medium flex-1 min-w-0 truncate">{v.label}</span>
149
+ <code
150
+ class="text-base-content/60 group-hover:text-primary shrink-0 font-mono text-xs"
151
+ >
152
+ {tokenFor(v)}
153
+ </code>
154
+ </div>
155
+ {#if v.description && !compact}
156
+ <span class="text-base-content/45 text-[0.75rem]">
157
+ {v.description}{#if v.example}
158
+ <span class="text-base-content/45">
159
+ — e.g. {v.example}</span
160
+ >{/if}
161
+ </span>
162
+ {/if}
163
+ </button>
164
+ {/each}
165
+ </div>
166
+ </div>
167
+ {/each}
168
+ {/if}
169
+ </div>
170
+ </div>
@@ -0,0 +1,40 @@
1
+ /**
2
+ * VariablePicker — browse and insert template merge variables.
3
+ *
4
+ * Renders the available merge variables for a document template, grouped by
5
+ * context (client / org / financial / custom). Clicking a variable inserts
6
+ * its `{{name}}` token at the caret of a bound textarea/input and/or fires
7
+ * an `onInsert` callback.
8
+ *
9
+ * Source of `groups`: the JST server-side registry
10
+ * `getVarsForCategory(category, template.available_vars)` in
11
+ * `src/lib/server/template-vars.ts`. Compute it in a `+page.server.ts` load
12
+ * (the registry is server-only) and pass the result straight in.
13
+ *
14
+ * This is a self-contained panel (not a modal) so it can sit beside a
15
+ * template editor as a sidebar. Wrap it in your own Drawer/Modal if needed.
16
+ */
17
+ import type { TemplateVarGroup } from "../types/templateVars";
18
+ interface Props {
19
+ /** Variable groups to show. From `getVarsForCategory(...)`. */
20
+ groups: TemplateVarGroup[];
21
+ /**
22
+ * Optional textarea/input to insert into. When set, clicking a variable
23
+ * splices `{{name}}` at the current caret, restores focus + caret after
24
+ * the token, and dispatches an `input` event so `bind:value` updates.
25
+ */
26
+ targetEl?: HTMLTextAreaElement | HTMLInputElement | null;
27
+ /** Called with the inserted token (`{{name}}`) and the bare var name. */
28
+ onInsert?: (token: string, varName: string) => void;
29
+ /** Show the filter box. Default true. */
30
+ searchable?: boolean;
31
+ /** Denser layout for narrow sidebars. Default false. */
32
+ compact?: boolean;
33
+ /** Panel heading. Default "Variables". */
34
+ title?: string;
35
+ /** Shown when there are no groups/vars at all. */
36
+ emptyText?: string;
37
+ }
38
+ declare const VariablePicker: import("svelte").Component<Props, {}, "">;
39
+ type VariablePicker = ReturnType<typeof VariablePicker>;
40
+ export default VariablePicker;