@joewinke/jatui 0.1.0
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/package.json +46 -0
- package/src/lib/components/AudioWaveform.svelte +694 -0
- package/src/lib/components/AvailabilityModal.svelte +173 -0
- package/src/lib/components/Badge.svelte +38 -0
- package/src/lib/components/BookingForm.svelte +276 -0
- package/src/lib/components/Button.svelte +72 -0
- package/src/lib/components/CalendarPicker.svelte +284 -0
- package/src/lib/components/Card.svelte +67 -0
- package/src/lib/components/CharacterCounter.svelte +82 -0
- package/src/lib/components/ChipInput.svelte +596 -0
- package/src/lib/components/ColorSelector.svelte +163 -0
- package/src/lib/components/ConfirmModal.svelte +75 -0
- package/src/lib/components/CountdownTimer.svelte +94 -0
- package/src/lib/components/DateRangePicker.svelte +192 -0
- package/src/lib/components/Drawer.svelte +110 -0
- package/src/lib/components/FilterDropdown.svelte +202 -0
- package/src/lib/components/ImageUpload.svelte +97 -0
- package/src/lib/components/InlineEdit.svelte +283 -0
- package/src/lib/components/LazyImage.svelte +122 -0
- package/src/lib/components/LoadingSpinner.svelte +102 -0
- package/src/lib/components/Modal.svelte +208 -0
- package/src/lib/components/PhoneInput.svelte +92 -0
- package/src/lib/components/ResizableDivider.svelte +305 -0
- package/src/lib/components/ResizablePanel.svelte +302 -0
- package/src/lib/components/SearchDropdown.svelte +341 -0
- package/src/lib/components/SelectInput.svelte +215 -0
- package/src/lib/components/SignaturePad.svelte +171 -0
- package/src/lib/components/SortDropdown.svelte +148 -0
- package/src/lib/components/Sparkline.svelte +107 -0
- package/src/lib/components/SpeechForm.svelte +114 -0
- package/src/lib/components/StatusBadge.svelte +155 -0
- package/src/lib/components/TextArea.svelte +143 -0
- package/src/lib/components/TextInput.svelte +108 -0
- package/src/lib/components/ThemeSelector.svelte +195 -0
- package/src/lib/components/TimeSlotPicker.svelte +162 -0
- package/src/lib/components/VoicePlayer.svelte +420 -0
- package/src/lib/components/messaging/Avatar.svelte +81 -0
- package/src/lib/components/messaging/ChannelInfoModal.svelte +163 -0
- package/src/lib/components/messaging/ChannelList.svelte +107 -0
- package/src/lib/components/messaging/ChannelMemberAvatarStack.svelte +69 -0
- package/src/lib/components/messaging/ChannelMembersModal.svelte +182 -0
- package/src/lib/components/messaging/CreateChannelModal.svelte +190 -0
- package/src/lib/components/messaging/DirectMessageList.svelte +145 -0
- package/src/lib/components/messaging/EmojiSelector.svelte +260 -0
- package/src/lib/components/messaging/MentionAutocomplete.svelte +193 -0
- package/src/lib/components/messaging/MessageAttachment.svelte +270 -0
- package/src/lib/components/messaging/MessageAttachmentUpload.svelte +243 -0
- package/src/lib/components/messaging/MessageInput.svelte +451 -0
- package/src/lib/components/messaging/MessageItem.svelte +338 -0
- package/src/lib/components/messaging/MessageThread.svelte +306 -0
- package/src/lib/components/messaging/NotificationSettingsModal.svelte +234 -0
- package/src/lib/components/messaging/QuotedMessageDisplay.svelte +118 -0
- package/src/lib/components/messaging/StartDMModal.svelte +100 -0
- package/src/lib/components/messaging/ThreadPanel.svelte +153 -0
- package/src/lib/index.ts +185 -0
- package/src/lib/types/booking.ts +143 -0
- package/src/lib/types/messaging.ts +459 -0
- package/src/lib/utils/currency.ts +20 -0
- package/src/lib/utils/daisyuiColors.ts +243 -0
- package/src/lib/utils/dateFormatters.ts +153 -0
- package/src/lib/utils/mentionParser.ts +188 -0
- package/src/lib/utils/phoneFormat.ts +74 -0
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
// Messaging System Types
|
|
2
|
+
// Generic types for messaging UI components — no framework or backend dependencies
|
|
3
|
+
|
|
4
|
+
// ─── Core Message Types ───────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
export interface Message {
|
|
7
|
+
id: string
|
|
8
|
+
content: string
|
|
9
|
+
senderId: string
|
|
10
|
+
senderName: string
|
|
11
|
+
senderAvatar?: string
|
|
12
|
+
createdAt: string
|
|
13
|
+
editedAt?: string
|
|
14
|
+
isRead?: boolean
|
|
15
|
+
readByCount?: number
|
|
16
|
+
isPinned?: boolean
|
|
17
|
+
pinnedAt?: string
|
|
18
|
+
pinnedBy?: string
|
|
19
|
+
pinnedByName?: string
|
|
20
|
+
parentMessageId?: string
|
|
21
|
+
recipientId?: string
|
|
22
|
+
thread_reply_count?: number
|
|
23
|
+
thread_last_reply_at?: string
|
|
24
|
+
thread_last_sender?: {
|
|
25
|
+
id: string
|
|
26
|
+
full_name: string
|
|
27
|
+
avatar_url?: string
|
|
28
|
+
}
|
|
29
|
+
quotedMessageId?: string
|
|
30
|
+
quotedMessage?: QuotedMessage
|
|
31
|
+
attachments?: MessageAttachment[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface QuotedMessage {
|
|
35
|
+
id: string
|
|
36
|
+
content: string
|
|
37
|
+
senderId: string
|
|
38
|
+
senderName: string
|
|
39
|
+
senderAvatar?: string
|
|
40
|
+
createdAt: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface MessageAttachment {
|
|
44
|
+
id: string
|
|
45
|
+
file_name: string
|
|
46
|
+
file_type: string
|
|
47
|
+
file_size: number
|
|
48
|
+
storage_path: string
|
|
49
|
+
width?: number
|
|
50
|
+
height?: number
|
|
51
|
+
duration?: number
|
|
52
|
+
thumbnail_path?: string
|
|
53
|
+
uploaded_by: string
|
|
54
|
+
created_at: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface PendingAttachment {
|
|
58
|
+
id: string
|
|
59
|
+
file: File
|
|
60
|
+
preview?: string
|
|
61
|
+
uploading: boolean
|
|
62
|
+
progress: number
|
|
63
|
+
error?: string
|
|
64
|
+
storagePath?: string
|
|
65
|
+
width?: number
|
|
66
|
+
height?: number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── Channel Types ────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
export interface Channel {
|
|
72
|
+
id: string
|
|
73
|
+
name: string
|
|
74
|
+
description?: string
|
|
75
|
+
type: 'public' | 'private' | 'direct'
|
|
76
|
+
isActive: boolean
|
|
77
|
+
memberRole: 'admin' | 'moderator' | 'member'
|
|
78
|
+
joinedAt: string
|
|
79
|
+
createdAt: string
|
|
80
|
+
unreadCount?: number
|
|
81
|
+
emoji?: string
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ChannelMember {
|
|
85
|
+
id: string
|
|
86
|
+
userId: string
|
|
87
|
+
fullName: string
|
|
88
|
+
avatarUrl?: string | null
|
|
89
|
+
role: 'admin' | 'moderator' | 'member'
|
|
90
|
+
userRole?: string
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ─── Direct Message Types ─────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
export interface DirectConversation {
|
|
96
|
+
id: string
|
|
97
|
+
name: string
|
|
98
|
+
avatar?: string
|
|
99
|
+
lastMessageAt: string
|
|
100
|
+
unreadCount?: number
|
|
101
|
+
isOnline?: boolean
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export interface OrgMember {
|
|
105
|
+
id: string
|
|
106
|
+
full_name: string
|
|
107
|
+
avatar_url?: string
|
|
108
|
+
role: string
|
|
109
|
+
active?: boolean
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ─── Emoji Types ──────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
export type EmojiCategory =
|
|
115
|
+
| 'search'
|
|
116
|
+
| 'frequently_used'
|
|
117
|
+
| 'smileys_people'
|
|
118
|
+
| 'animals_nature'
|
|
119
|
+
| 'food_drink'
|
|
120
|
+
| 'travel_places'
|
|
121
|
+
| 'activities'
|
|
122
|
+
| 'objects'
|
|
123
|
+
| 'symbols'
|
|
124
|
+
| 'flags'
|
|
125
|
+
| 'custom'
|
|
126
|
+
|
|
127
|
+
export type SkinTone =
|
|
128
|
+
| 'default'
|
|
129
|
+
| 'light'
|
|
130
|
+
| 'medium-light'
|
|
131
|
+
| 'medium'
|
|
132
|
+
| 'medium-dark'
|
|
133
|
+
| 'dark'
|
|
134
|
+
|
|
135
|
+
export interface StandardEmoji {
|
|
136
|
+
unicode: string
|
|
137
|
+
name: string
|
|
138
|
+
shortcode: string
|
|
139
|
+
category: EmojiCategory
|
|
140
|
+
keywords: string[]
|
|
141
|
+
skinTones?: string[]
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface CustomEmoji {
|
|
145
|
+
id: string
|
|
146
|
+
organizationId: string
|
|
147
|
+
name: string
|
|
148
|
+
displayName: string
|
|
149
|
+
description?: string
|
|
150
|
+
fileName: string
|
|
151
|
+
fileSize: number
|
|
152
|
+
contentType: 'image/png' | 'image/gif'
|
|
153
|
+
storagePath: string
|
|
154
|
+
isAnimated: boolean
|
|
155
|
+
isActive: boolean
|
|
156
|
+
usageCount: number
|
|
157
|
+
uploadedBy: string
|
|
158
|
+
createdAt: string
|
|
159
|
+
updatedAt: string
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export interface EmojiReaction {
|
|
163
|
+
id: string
|
|
164
|
+
messageId: string
|
|
165
|
+
userId: string
|
|
166
|
+
emojiUnicode?: string
|
|
167
|
+
customEmojiId?: string
|
|
168
|
+
skinTone?: SkinTone
|
|
169
|
+
createdAt: string
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface ReactionSummary {
|
|
173
|
+
emojiUnicode?: string
|
|
174
|
+
customEmojiId?: string
|
|
175
|
+
customEmojiName?: string
|
|
176
|
+
customEmojiStoragePath?: string
|
|
177
|
+
customEmojiIsAnimated?: boolean
|
|
178
|
+
reactionCount: number
|
|
179
|
+
userReacted: boolean
|
|
180
|
+
recentUsers: Array<{ id: string; name: string }>
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export interface EmojiSelection {
|
|
184
|
+
type: 'standard' | 'custom'
|
|
185
|
+
unicode?: string
|
|
186
|
+
customEmojiId?: string
|
|
187
|
+
name: string
|
|
188
|
+
shortcode: string
|
|
189
|
+
skinTone?: SkinTone
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface EmojiPreferences {
|
|
193
|
+
skinTone: SkinTone
|
|
194
|
+
frequentlyUsed: string[]
|
|
195
|
+
lastUsedAt?: string
|
|
196
|
+
customEmojiFavorites?: string[]
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export interface EmojiCategoryInfo {
|
|
200
|
+
id: EmojiCategory
|
|
201
|
+
name: string
|
|
202
|
+
icon: string
|
|
203
|
+
description: string
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface SkinToneInfo {
|
|
207
|
+
id: SkinTone
|
|
208
|
+
name: string
|
|
209
|
+
unicode: string
|
|
210
|
+
preview: string
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Mention Types ────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
export interface MentionItem {
|
|
216
|
+
type: string
|
|
217
|
+
id: string
|
|
218
|
+
name: string
|
|
219
|
+
secondary: string
|
|
220
|
+
url: string
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ─── Notification Types ───────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
export interface NotificationChannels {
|
|
226
|
+
email: boolean
|
|
227
|
+
sms: boolean
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export interface NotificationEvents {
|
|
231
|
+
[key: string]: boolean
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface NotificationTiming {
|
|
235
|
+
[key: string]: string | number
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface NotificationPreferences {
|
|
239
|
+
channels: NotificationChannels
|
|
240
|
+
events: NotificationEvents
|
|
241
|
+
timing: NotificationTiming
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// ─── Presence Types ───────────────────────────────────────────────────
|
|
245
|
+
|
|
246
|
+
export interface PresenceState {
|
|
247
|
+
[userId: string]: {
|
|
248
|
+
status: string
|
|
249
|
+
technicianStatus?: string
|
|
250
|
+
lastSeen?: string
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ─── Callback Interfaces ──────────────────────────────────────────────
|
|
255
|
+
// These define the data-layer contracts that consuming apps must implement.
|
|
256
|
+
|
|
257
|
+
export interface MessageCallbacks {
|
|
258
|
+
loadMessages: (params: {
|
|
259
|
+
channelId?: string
|
|
260
|
+
dmUserId?: string
|
|
261
|
+
before?: string
|
|
262
|
+
limit?: number
|
|
263
|
+
}) => Promise<Message[]>
|
|
264
|
+
sendMessage: (params: {
|
|
265
|
+
content: string
|
|
266
|
+
channelId?: string
|
|
267
|
+
dmUserId?: string
|
|
268
|
+
parentMessageId?: string
|
|
269
|
+
quotedMessageId?: string
|
|
270
|
+
attachments?: PendingAttachment[]
|
|
271
|
+
priority?: string
|
|
272
|
+
}) => Promise<Message>
|
|
273
|
+
editMessage: (messageId: string, content: string) => Promise<Message>
|
|
274
|
+
deleteMessage: (messageId: string) => Promise<void>
|
|
275
|
+
pinMessage: (messageId: string, channelId: string) => Promise<void>
|
|
276
|
+
unpinMessage: (messageId: string, channelId: string) => Promise<void>
|
|
277
|
+
markAsRead: (messageIds: string[]) => Promise<void>
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export interface ReactionCallbacks {
|
|
281
|
+
loadReactions: (messageId: string) => Promise<ReactionSummary[]>
|
|
282
|
+
addReaction: (
|
|
283
|
+
messageId: string,
|
|
284
|
+
emoji: { unicode?: string; customEmojiId?: string; skinTone?: SkinTone }
|
|
285
|
+
) => Promise<void>
|
|
286
|
+
removeReaction: (
|
|
287
|
+
messageId: string,
|
|
288
|
+
emoji: { unicode?: string; customEmojiId?: string }
|
|
289
|
+
) => Promise<void>
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export interface AttachmentCallbacks {
|
|
293
|
+
getSignedUrl: (storagePath: string) => Promise<string>
|
|
294
|
+
getThumbnailUrl: (thumbnailPath: string) => Promise<string | null>
|
|
295
|
+
uploadFile: (
|
|
296
|
+
file: File,
|
|
297
|
+
organizationId: string
|
|
298
|
+
) => Promise<{ path: string; error?: string }>
|
|
299
|
+
deleteAttachment: (attachmentId: string) => Promise<void>
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export interface MentionCallbacks {
|
|
303
|
+
searchMentions: (query: string) => Promise<MentionItem[]>
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export interface EmojiCallbacks {
|
|
307
|
+
loadCustomEmojis: () => Promise<CustomEmoji[]>
|
|
308
|
+
loadPreferences: () => Promise<EmojiPreferences>
|
|
309
|
+
savePreferences: (prefs: Partial<EmojiPreferences>) => Promise<void>
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export interface ChannelCallbacks {
|
|
313
|
+
createChannel: (params: {
|
|
314
|
+
name: string
|
|
315
|
+
description?: string
|
|
316
|
+
type: 'public' | 'private'
|
|
317
|
+
emoji?: string
|
|
318
|
+
memberIds?: string[]
|
|
319
|
+
}) => Promise<Channel>
|
|
320
|
+
updateChannel: (
|
|
321
|
+
channelId: string,
|
|
322
|
+
params: Partial<Pick<Channel, 'name' | 'description' | 'emoji'>>
|
|
323
|
+
) => Promise<Channel>
|
|
324
|
+
deleteChannel: (channelId: string) => Promise<void>
|
|
325
|
+
leaveChannel: (channelId: string) => Promise<void>
|
|
326
|
+
getMembers: (channelId: string) => Promise<ChannelMember[]>
|
|
327
|
+
addMember: (channelId: string, userId: string) => Promise<void>
|
|
328
|
+
removeMember: (channelId: string, userId: string) => Promise<void>
|
|
329
|
+
updateMemberRole: (
|
|
330
|
+
channelId: string,
|
|
331
|
+
userId: string,
|
|
332
|
+
role: 'admin' | 'moderator' | 'member'
|
|
333
|
+
) => Promise<void>
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
export interface ThreadCallbacks {
|
|
337
|
+
loadReplies: (parentMessageId: string) => Promise<Message[]>
|
|
338
|
+
sendReply: (
|
|
339
|
+
parentMessageId: string,
|
|
340
|
+
content: string,
|
|
341
|
+
attachments?: PendingAttachment[]
|
|
342
|
+
) => Promise<Message>
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
export interface NotificationCallbacks {
|
|
346
|
+
loadSettings: (params: {
|
|
347
|
+
type: 'global' | 'channel'
|
|
348
|
+
channelId?: string
|
|
349
|
+
}) => Promise<NotificationPreferences>
|
|
350
|
+
saveSettings: (
|
|
351
|
+
params: {
|
|
352
|
+
type: 'global' | 'channel'
|
|
353
|
+
channelId?: string
|
|
354
|
+
},
|
|
355
|
+
settings: NotificationPreferences
|
|
356
|
+
) => Promise<void>
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ─── Emoji Data Constants ─────────────────────────────────────────────
|
|
360
|
+
|
|
361
|
+
export const EMOJI_CATEGORIES: EmojiCategoryInfo[] = [
|
|
362
|
+
{ id: 'search', name: 'Search', icon: '🔍', description: 'Search results' },
|
|
363
|
+
{
|
|
364
|
+
id: 'frequently_used',
|
|
365
|
+
name: 'Frequently Used',
|
|
366
|
+
icon: '🕐',
|
|
367
|
+
description: 'Your most used emojis'
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
id: 'smileys_people',
|
|
371
|
+
name: 'Smileys & People',
|
|
372
|
+
icon: '😀',
|
|
373
|
+
description: 'Faces, people, and emotions'
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
id: 'animals_nature',
|
|
377
|
+
name: 'Animals & Nature',
|
|
378
|
+
icon: '🐻',
|
|
379
|
+
description: 'Animals, plants, and nature'
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
id: 'food_drink',
|
|
383
|
+
name: 'Food & Drink',
|
|
384
|
+
icon: '🍎',
|
|
385
|
+
description: 'Food, drinks, and meals'
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: 'travel_places',
|
|
389
|
+
name: 'Travel & Places',
|
|
390
|
+
icon: '🏠',
|
|
391
|
+
description: 'Places, transport, and travel'
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
id: 'activities',
|
|
395
|
+
name: 'Activities',
|
|
396
|
+
icon: '⚽',
|
|
397
|
+
description: 'Sports, games, and activities'
|
|
398
|
+
},
|
|
399
|
+
{ id: 'objects', name: 'Objects', icon: '🔨', description: 'Tools, objects, and items' },
|
|
400
|
+
{
|
|
401
|
+
id: 'symbols',
|
|
402
|
+
name: 'Symbols',
|
|
403
|
+
icon: '❤️',
|
|
404
|
+
description: 'Symbols, signs, and icons'
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
id: 'flags',
|
|
408
|
+
name: 'Flags',
|
|
409
|
+
icon: '🏳️',
|
|
410
|
+
description: 'Country and regional flags'
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
id: 'custom',
|
|
414
|
+
name: 'Custom',
|
|
415
|
+
icon: '🎨',
|
|
416
|
+
description: 'Organization custom emojis'
|
|
417
|
+
}
|
|
418
|
+
]
|
|
419
|
+
|
|
420
|
+
export const SKIN_TONES: SkinToneInfo[] = [
|
|
421
|
+
{ id: 'default', name: 'Default', unicode: '', preview: '👋' },
|
|
422
|
+
{ id: 'light', name: 'Light', unicode: '🏻', preview: '👋🏻' },
|
|
423
|
+
{ id: 'medium-light', name: 'Medium Light', unicode: '🏼', preview: '👋🏼' },
|
|
424
|
+
{ id: 'medium', name: 'Medium', unicode: '🏽', preview: '👋🏽' },
|
|
425
|
+
{ id: 'medium-dark', name: 'Medium Dark', unicode: '🏾', preview: '👋🏾' },
|
|
426
|
+
{ id: 'dark', name: 'Dark', unicode: '🏿', preview: '👋🏿' }
|
|
427
|
+
]
|
|
428
|
+
|
|
429
|
+
// Default quick-reaction emojis (used when no user preferences available)
|
|
430
|
+
export const DEFAULT_QUICK_REACTIONS: StandardEmoji[] = [
|
|
431
|
+
{
|
|
432
|
+
unicode: '👍',
|
|
433
|
+
name: 'Thumbs Up',
|
|
434
|
+
shortcode: 'thumbsup',
|
|
435
|
+
category: 'smileys_people',
|
|
436
|
+
keywords: ['like', 'approve', 'yes']
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
unicode: '❤️',
|
|
440
|
+
name: 'Red Heart',
|
|
441
|
+
shortcode: 'heart',
|
|
442
|
+
category: 'symbols',
|
|
443
|
+
keywords: ['love', 'heart']
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
unicode: '😂',
|
|
447
|
+
name: 'Face with Tears of Joy',
|
|
448
|
+
shortcode: 'joy',
|
|
449
|
+
category: 'smileys_people',
|
|
450
|
+
keywords: ['laugh', 'lol', 'funny']
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
unicode: '🎉',
|
|
454
|
+
name: 'Party Popper',
|
|
455
|
+
shortcode: 'tada',
|
|
456
|
+
category: 'activities',
|
|
457
|
+
keywords: ['party', 'celebrate']
|
|
458
|
+
}
|
|
459
|
+
]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Currency formatting utilities
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Format a number as USD currency string.
|
|
7
|
+
* Override locale/currency by passing options.
|
|
8
|
+
*/
|
|
9
|
+
export function formatCurrency(
|
|
10
|
+
amount: number,
|
|
11
|
+
locale: string = 'en-US',
|
|
12
|
+
currency: string = 'USD'
|
|
13
|
+
): string {
|
|
14
|
+
return new Intl.NumberFormat(locale, {
|
|
15
|
+
style: 'currency',
|
|
16
|
+
currency,
|
|
17
|
+
minimumFractionDigits: 2,
|
|
18
|
+
maximumFractionDigits: 2
|
|
19
|
+
}).format(amount);
|
|
20
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DaisyUI Color Utility System
|
|
3
|
+
*
|
|
4
|
+
* Provides utilities for accessing DaisyUI semantic colors in JavaScript/Canvas contexts.
|
|
5
|
+
* Handles color format conversion and theme change detection for dynamic updates.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
let colorCache = new Map<string, string>()
|
|
9
|
+
let themeObserver: MutationObserver | null = null
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get a DaisyUI semantic color as a hex string usable in Canvas
|
|
13
|
+
* @param colorName - DaisyUI color name (e.g., "primary", "accent", "base-100")
|
|
14
|
+
* @returns Hex color string (e.g., "#3b82f6")
|
|
15
|
+
*/
|
|
16
|
+
export function getDaisyUIColor(colorName: string): string {
|
|
17
|
+
const cacheKey = colorName
|
|
18
|
+
if (colorCache.has(cacheKey)) {
|
|
19
|
+
return colorCache.get(cacheKey)!
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const fallbackColor = getColorFallback(colorName)
|
|
23
|
+
colorCache.set(cacheKey, fallbackColor)
|
|
24
|
+
return fallbackColor
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getColorFallback(colorName: string): string {
|
|
28
|
+
const currentTheme =
|
|
29
|
+
document.documentElement.getAttribute("data-theme") || "light"
|
|
30
|
+
|
|
31
|
+
const luxuryColors: Record<string, string> = {
|
|
32
|
+
primary: "#d4af37",
|
|
33
|
+
"primary-content": "#1a1a1a",
|
|
34
|
+
secondary: "#c0c0c0",
|
|
35
|
+
"secondary-content": "#1a1a1a",
|
|
36
|
+
accent: "#ffd700",
|
|
37
|
+
"accent-content": "#1a1a1a",
|
|
38
|
+
neutral: "#6b7280",
|
|
39
|
+
"neutral-content": "#f5f5f5",
|
|
40
|
+
"base-100": "#1a1a1a",
|
|
41
|
+
"base-200": "#2d2d2d",
|
|
42
|
+
"base-300": "#404040",
|
|
43
|
+
"base-content": "#f5f5f5",
|
|
44
|
+
success: "#10b981",
|
|
45
|
+
"success-content": "#1a1a1a",
|
|
46
|
+
warning: "#f59e0b",
|
|
47
|
+
"warning-content": "#1a1a1a",
|
|
48
|
+
error: "#ef4444",
|
|
49
|
+
"error-content": "#f5f5f5",
|
|
50
|
+
info: "#3b82f6",
|
|
51
|
+
"info-content": "#f5f5f5",
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const nordColors: Record<string, string> = {
|
|
55
|
+
primary: "#5e81ac",
|
|
56
|
+
"primary-content": "#eceff4",
|
|
57
|
+
secondary: "#81a1c1",
|
|
58
|
+
"secondary-content": "#2e3440",
|
|
59
|
+
accent: "#88c0d0",
|
|
60
|
+
"accent-content": "#2e3440",
|
|
61
|
+
neutral: "#4c566a",
|
|
62
|
+
"neutral-content": "#eceff4",
|
|
63
|
+
"base-100": "#2e3440",
|
|
64
|
+
"base-200": "#3b4252",
|
|
65
|
+
"base-300": "#434c5e",
|
|
66
|
+
"base-content": "#eceff4",
|
|
67
|
+
success: "#a3be8c",
|
|
68
|
+
"success-content": "#2e3440",
|
|
69
|
+
warning: "#ebcb8b",
|
|
70
|
+
"warning-content": "#2e3440",
|
|
71
|
+
error: "#bf616a",
|
|
72
|
+
"error-content": "#eceff4",
|
|
73
|
+
info: "#81a1c1",
|
|
74
|
+
"info-content": "#2e3440",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const darkColors: Record<string, string> = {
|
|
78
|
+
primary: "#661ae6",
|
|
79
|
+
"primary-content": "#ffffff",
|
|
80
|
+
secondary: "#d926a9",
|
|
81
|
+
"secondary-content": "#ffffff",
|
|
82
|
+
accent: "#1fb2a6",
|
|
83
|
+
"accent-content": "#ffffff",
|
|
84
|
+
neutral: "#2a323c",
|
|
85
|
+
"neutral-content": "#a6adbb",
|
|
86
|
+
"base-100": "#1d232a",
|
|
87
|
+
"base-200": "#191e24",
|
|
88
|
+
"base-300": "#15191e",
|
|
89
|
+
"base-content": "#a6adbb",
|
|
90
|
+
info: "#3abff8",
|
|
91
|
+
"info-content": "#002b3d",
|
|
92
|
+
success: "#36d399",
|
|
93
|
+
"success-content": "#003320",
|
|
94
|
+
warning: "#fbbd23",
|
|
95
|
+
"warning-content": "#382800",
|
|
96
|
+
error: "#f87272",
|
|
97
|
+
"error-content": "#470000",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const defaultColors: Record<string, string> = {
|
|
101
|
+
primary: "#3b82f6",
|
|
102
|
+
"primary-content": "#ffffff",
|
|
103
|
+
secondary: "#6b7280",
|
|
104
|
+
"secondary-content": "#ffffff",
|
|
105
|
+
accent: "#f97316",
|
|
106
|
+
"accent-content": "#ffffff",
|
|
107
|
+
neutral: "#6b7280",
|
|
108
|
+
"neutral-content": "#ffffff",
|
|
109
|
+
"base-100": "#ffffff",
|
|
110
|
+
"base-200": "#f1f5f9",
|
|
111
|
+
"base-300": "#e2e8f0",
|
|
112
|
+
"base-content": "#1f2937",
|
|
113
|
+
info: "#3b82f6",
|
|
114
|
+
"info-content": "#ffffff",
|
|
115
|
+
success: "#10b981",
|
|
116
|
+
"success-content": "#ffffff",
|
|
117
|
+
warning: "#f59e0b",
|
|
118
|
+
"warning-content": "#ffffff",
|
|
119
|
+
error: "#ef4444",
|
|
120
|
+
"error-content": "#ffffff",
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const fallbacks =
|
|
124
|
+
currentTheme === "luxury"
|
|
125
|
+
? luxuryColors
|
|
126
|
+
: currentTheme === "nord"
|
|
127
|
+
? nordColors
|
|
128
|
+
: currentTheme === "dark"
|
|
129
|
+
? darkColors
|
|
130
|
+
: defaultColors
|
|
131
|
+
return fallbacks[colorName] || "#6b7280"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Create a lighter version of a color
|
|
136
|
+
*/
|
|
137
|
+
export function lightenColor(hexColor: string, amount: number = 0.2): string {
|
|
138
|
+
const hex = hexColor.replace("#", "")
|
|
139
|
+
const r = parseInt(hex.substring(0, 2), 16)
|
|
140
|
+
const g = parseInt(hex.substring(2, 4), 16)
|
|
141
|
+
const b = parseInt(hex.substring(4, 6), 16)
|
|
142
|
+
|
|
143
|
+
const newR = Math.min(255, Math.floor(r + (255 - r) * amount))
|
|
144
|
+
const newG = Math.min(255, Math.floor(g + (255 - g) * amount))
|
|
145
|
+
const newB = Math.min(255, Math.floor(b + (255 - b) * amount))
|
|
146
|
+
|
|
147
|
+
return `#${((newR << 16) | (newG << 8) | newB).toString(16).padStart(6, "0")}`
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Create a darker version of a color
|
|
152
|
+
*/
|
|
153
|
+
export function darkenColor(hexColor: string, amount: number = 0.2): string {
|
|
154
|
+
const hex = hexColor.replace("#", "")
|
|
155
|
+
const r = parseInt(hex.substring(0, 2), 16)
|
|
156
|
+
const g = parseInt(hex.substring(2, 4), 16)
|
|
157
|
+
const b = parseInt(hex.substring(4, 6), 16)
|
|
158
|
+
|
|
159
|
+
const newR = Math.max(0, Math.floor(r * (1 - amount)))
|
|
160
|
+
const newG = Math.max(0, Math.floor(g * (1 - amount)))
|
|
161
|
+
const newB = Math.max(0, Math.floor(b * (1 - amount)))
|
|
162
|
+
|
|
163
|
+
return `#${((newR << 16) | (newG << 8) | newB).toString(16).padStart(6, "0")}`
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Setup theme change detection
|
|
168
|
+
* Clears color cache when theme changes and optionally calls a callback
|
|
169
|
+
*/
|
|
170
|
+
export function watchThemeChanges(onThemeChange?: () => void): () => void {
|
|
171
|
+
if (themeObserver) {
|
|
172
|
+
themeObserver.disconnect()
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
themeObserver = new MutationObserver((mutations: MutationRecord[]): void => {
|
|
176
|
+
let themeChanged = false
|
|
177
|
+
|
|
178
|
+
mutations.forEach((mutation: MutationRecord): void => {
|
|
179
|
+
if (
|
|
180
|
+
mutation.type === "attributes" &&
|
|
181
|
+
(mutation.attributeName === "data-theme" ||
|
|
182
|
+
mutation.attributeName === "class")
|
|
183
|
+
) {
|
|
184
|
+
themeChanged = true
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
if (themeChanged) {
|
|
189
|
+
colorCache.clear()
|
|
190
|
+
if (onThemeChange) {
|
|
191
|
+
onThemeChange()
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
themeObserver.observe(document.documentElement, {
|
|
197
|
+
attributes: true,
|
|
198
|
+
attributeFilter: ["data-theme", "class"],
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
return () => {
|
|
202
|
+
if (themeObserver) {
|
|
203
|
+
themeObserver.disconnect()
|
|
204
|
+
themeObserver = null
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Check if we're currently in a dark theme
|
|
211
|
+
*/
|
|
212
|
+
export function isDarkTheme(): boolean {
|
|
213
|
+
const baseContent = getDaisyUIColor("base-content")
|
|
214
|
+
const base100 = getDaisyUIColor("base-100")
|
|
215
|
+
|
|
216
|
+
const contentBrightness = getBrightness(baseContent)
|
|
217
|
+
const baseBrightness = getBrightness(base100)
|
|
218
|
+
|
|
219
|
+
return contentBrightness > baseBrightness
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function getBrightness(hexColor: string): number {
|
|
223
|
+
const hex = hexColor.replace("#", "")
|
|
224
|
+
const r = parseInt(hex.substring(0, 2), 16)
|
|
225
|
+
const g = parseInt(hex.substring(2, 4), 16)
|
|
226
|
+
const b = parseInt(hex.substring(4, 6), 16)
|
|
227
|
+
|
|
228
|
+
return (r * 299 + g * 587 + b * 114) / 1000
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Get the current theme name if available
|
|
233
|
+
*/
|
|
234
|
+
export function getCurrentTheme(): string | null {
|
|
235
|
+
return document.documentElement.getAttribute("data-theme")
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Clear the color cache manually
|
|
240
|
+
*/
|
|
241
|
+
export function clearColorCache(): void {
|
|
242
|
+
colorCache.clear()
|
|
243
|
+
}
|