@htlkg/components 0.0.11 → 0.0.13

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 (112) hide show
  1. package/dist/{AdminWrapper.vue_vue_type_script_setup_true_lang-B32IylcT.js → AdminWrapper.vue_vue_type_script_setup_true_lang-BhnWQ-b0.js} +26 -29
  2. package/dist/AdminWrapper.vue_vue_type_script_setup_true_lang-BhnWQ-b0.js.map +1 -0
  3. package/dist/Alert.vue_vue_type_script_setup_true_lang-DxPCS-Hx.js.map +1 -1
  4. package/dist/DateRange.vue_vue_type_script_setup_true_lang-BLVg1Hah.js.map +1 -1
  5. package/dist/ProductBadge.vue_vue_type_script_setup_true_lang-Cmr2f4Cy.js.map +1 -1
  6. package/dist/components.css +4 -4
  7. package/dist/composables/index.js +23 -22
  8. package/dist/data/index.js +10 -10
  9. package/dist/{filterHelpers-DgRyoYSa.js → filterHelpers-DpHSlTuh.js} +11 -11
  10. package/dist/filterHelpers-DpHSlTuh.js.map +1 -0
  11. package/dist/index-QK97OdqQ.js.map +1 -1
  12. package/dist/index.js +34 -33
  13. package/dist/navigation/index.js +1 -1
  14. package/dist/{useAdminPage-GhgXp0x8.js → useAdminPage-AgWRvw6o.js} +150 -26
  15. package/dist/useAdminPage-AgWRvw6o.js.map +1 -0
  16. package/package.json +3 -3
  17. package/src/composables/index.ts +1 -0
  18. package/src/composables/useJsonForm.test.ts +272 -0
  19. package/src/composables/useJsonForm.ts +261 -0
  20. package/src/composables/useModal.test.ts +264 -0
  21. package/src/composables/useModal.ts +54 -8
  22. package/src/data/Chart/index.ts +2 -0
  23. package/src/data/DataList/index.ts +1 -0
  24. package/src/data/{DataTable.vue → DataTable/DataTable.vue} +2 -2
  25. package/src/data/DataTable/index.ts +8 -0
  26. package/src/data/SearchableSelect/index.ts +1 -0
  27. package/src/data/Table/index.ts +1 -0
  28. package/src/data/index.ts +5 -15
  29. package/src/domain/BrandCard/index.ts +1 -0
  30. package/src/domain/BrandSelector/index.ts +1 -0
  31. package/src/domain/ProductBadge/index.ts +1 -0
  32. package/src/domain/UserAvatar/index.ts +1 -0
  33. package/src/domain/index.ts +4 -4
  34. package/src/forms/DateRange/index.ts +2 -0
  35. package/src/forms/JsonSchemaForm/index.ts +1 -0
  36. package/src/forms/index.ts +2 -3
  37. package/src/navigation/{AdminWrapper.vue → AdminWrapper/AdminWrapper.vue} +41 -30
  38. package/src/navigation/AdminWrapper/index.ts +1 -0
  39. package/src/navigation/Breadcrumbs/index.ts +1 -0
  40. package/src/navigation/Stepper/index.ts +2 -0
  41. package/src/navigation/Tabs/index.ts +2 -0
  42. package/src/navigation/index.ts +4 -6
  43. package/src/overlays/Alert/index.ts +1 -0
  44. package/src/overlays/Drawer/index.ts +1 -0
  45. package/src/overlays/Modal/index.ts +1 -0
  46. package/src/overlays/Notification/index.ts +1 -0
  47. package/src/overlays/index.ts +4 -4
  48. package/src/patterns/DASHBOARD_PAGE.md +642 -0
  49. package/src/patterns/DETAIL_PAGE.md +446 -0
  50. package/src/patterns/FORM_PAGE.md +439 -0
  51. package/src/patterns/LIST_PAGE.md +340 -0
  52. package/src/patterns/PAGE_PATTERNS.md +110 -0
  53. package/src/patterns/WIZARD_PAGE.md +733 -0
  54. package/dist/AdminWrapper.vue_vue_type_script_setup_true_lang-B32IylcT.js.map +0 -1
  55. package/dist/filterHelpers-DgRyoYSa.js.map +0 -1
  56. package/dist/useAdminPage-GhgXp0x8.js.map +0 -1
  57. package/src/data/Table.vue +0 -295
  58. /package/src/data/{Chart.demo.vue → Chart/Chart.demo.vue} +0 -0
  59. /package/src/data/{Chart.md → Chart/Chart.md} +0 -0
  60. /package/src/data/{Chart.vue → Chart/Chart.vue} +0 -0
  61. /package/src/data/{DataList.md → DataList/DataList.md} +0 -0
  62. /package/src/data/{DataList.test.ts → DataList/DataList.test.ts} +0 -0
  63. /package/src/data/{DataList.vue → DataList/DataList.vue} +0 -0
  64. /package/src/data/{SearchableSelect.md → SearchableSelect/SearchableSelect.md} +0 -0
  65. /package/src/data/{SearchableSelect.vue → SearchableSelect/SearchableSelect.vue} +0 -0
  66. /package/src/data/{Table.demo.vue → Table/Table.demo.vue} +0 -0
  67. /package/src/data/{Table.md → Table/Table.md} +0 -0
  68. /package/src/data/{Table.property.test.ts → Table/Table.property.test.ts} +0 -0
  69. /package/src/data/{Table.test.ts → Table/Table.test.ts} +0 -0
  70. /package/src/data/{Table.unit.test.ts → Table/Table.unit.test.ts} +0 -0
  71. /package/src/domain/{BrandCard.md → BrandCard/BrandCard.md} +0 -0
  72. /package/src/domain/{BrandCard.vue → BrandCard/BrandCard.vue} +0 -0
  73. /package/src/domain/{BrandSelector.md → BrandSelector/BrandSelector.md} +0 -0
  74. /package/src/domain/{BrandSelector.vue → BrandSelector/BrandSelector.vue} +0 -0
  75. /package/src/domain/{ProductBadge.md → ProductBadge/ProductBadge.md} +0 -0
  76. /package/src/domain/{ProductBadge.vue → ProductBadge/ProductBadge.vue} +0 -0
  77. /package/src/domain/{UserAvatar.md → UserAvatar/UserAvatar.md} +0 -0
  78. /package/src/domain/{UserAvatar.vue → UserAvatar/UserAvatar.vue} +0 -0
  79. /package/src/forms/{DateRange.demo.vue → DateRange/DateRange.demo.vue} +0 -0
  80. /package/src/forms/{DateRange.md → DateRange/DateRange.md} +0 -0
  81. /package/src/forms/{DateRange.vue → DateRange/DateRange.vue} +0 -0
  82. /package/src/forms/{JsonSchemaForm.demo.vue → JsonSchemaForm/JsonSchemaForm.demo.vue} +0 -0
  83. /package/src/forms/{JsonSchemaForm.md → JsonSchemaForm/JsonSchemaForm.md} +0 -0
  84. /package/src/forms/{JsonSchemaForm.property.test.ts → JsonSchemaForm/JsonSchemaForm.property.test.ts} +0 -0
  85. /package/src/forms/{JsonSchemaForm.test.ts → JsonSchemaForm/JsonSchemaForm.test.ts} +0 -0
  86. /package/src/forms/{JsonSchemaForm.unit.test.ts → JsonSchemaForm/JsonSchemaForm.unit.test.ts} +0 -0
  87. /package/src/forms/{JsonSchemaForm.vue → JsonSchemaForm/JsonSchemaForm.vue} +0 -0
  88. /package/src/navigation/{Breadcrumbs.demo.vue → Breadcrumbs/Breadcrumbs.demo.vue} +0 -0
  89. /package/src/navigation/{Breadcrumbs.md → Breadcrumbs/Breadcrumbs.md} +0 -0
  90. /package/src/navigation/{Breadcrumbs.test.ts → Breadcrumbs/Breadcrumbs.test.ts} +0 -0
  91. /package/src/navigation/{Breadcrumbs.vue → Breadcrumbs/Breadcrumbs.vue} +0 -0
  92. /package/src/navigation/{Stepper.demo.vue → Stepper/Stepper.demo.vue} +0 -0
  93. /package/src/navigation/{Stepper.md → Stepper/Stepper.md} +0 -0
  94. /package/src/navigation/{Stepper.vue → Stepper/Stepper.vue} +0 -0
  95. /package/src/navigation/{Tabs.demo.vue → Tabs/Tabs.demo.vue} +0 -0
  96. /package/src/navigation/{Tabs.md → Tabs/Tabs.md} +0 -0
  97. /package/src/navigation/{Tabs.test.ts → Tabs/Tabs.test.ts} +0 -0
  98. /package/src/navigation/{Tabs.vue → Tabs/Tabs.vue} +0 -0
  99. /package/src/overlays/{Alert.demo.vue → Alert/Alert.demo.vue} +0 -0
  100. /package/src/overlays/{Alert.md → Alert/Alert.md} +0 -0
  101. /package/src/overlays/{Alert.test.ts → Alert/Alert.test.ts} +0 -0
  102. /package/src/overlays/{Alert.vue → Alert/Alert.vue} +0 -0
  103. /package/src/overlays/{Drawer.md → Drawer/Drawer.md} +0 -0
  104. /package/src/overlays/{Drawer.test.ts → Drawer/Drawer.test.ts} +0 -0
  105. /package/src/overlays/{Drawer.vue → Drawer/Drawer.vue} +0 -0
  106. /package/src/overlays/{Modal.demo.vue → Modal/Modal.demo.vue} +0 -0
  107. /package/src/overlays/{Modal.md → Modal/Modal.md} +0 -0
  108. /package/src/overlays/{Modal.test.ts → Modal/Modal.test.ts} +0 -0
  109. /package/src/overlays/{Modal.vue → Modal/Modal.vue} +0 -0
  110. /package/src/overlays/{Notification.md → Notification/Notification.md} +0 -0
  111. /package/src/overlays/{Notification.test.ts → Notification/Notification.test.ts} +0 -0
  112. /package/src/overlays/{Notification.vue → Notification/Notification.vue} +0 -0
@@ -0,0 +1,446 @@
1
+ # Detail Page Pattern
2
+
3
+ Display comprehensive information about a single entity with related data, cards, and navigation.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────────────────┐
9
+ │ AdminLayout │
10
+ ├─────────────────────────────────────────────────────────────┤
11
+ │ Page Header (entity title + breadcrumbs) │
12
+ ├─────────────────────────────────────────────────────────────┤
13
+ │ Back Button + Action Dropdown │
14
+ ├─────────────────────────────────────────────────────────────┤
15
+ │ Card Grid (1-3 columns) │
16
+ │ ├── Info Card 1 (Guest Info, Primary Data) │
17
+ │ ├── Info Card 2 (Summary, Secondary Data) │
18
+ │ └── Info Card 3 (Status, Payment, etc.) │
19
+ ├─────────────────────────────────────────────────────────────┤
20
+ │ Related Data Section │
21
+ │ ├── Principal/Owner Card │
22
+ │ └── Related Items (companions, reservations) │
23
+ ├─────────────────────────────────────────────────────────────┤
24
+ │ Timeline / Activity Log │
25
+ └─────────────────────────────────────────────────────────────┘
26
+ ```
27
+
28
+ ## CRM Template Reference
29
+
30
+ Based on: `ui/src/components/Templates/CRM/BookingDetails.vue`
31
+
32
+ ```vue
33
+ <!-- CRM Template Structure (raw @hotelinking/ui) -->
34
+ <uiWrapper :sidebar :topbar>
35
+ <uiViewHeader :pages="pages" :title="`Booking ID: ${bookingData.bookingId}`" />
36
+
37
+ <!-- Back + Actions -->
38
+ <div class="flex justify-between items-center mb-4">
39
+ <uiButton :icon="ArrowLeftIcon" @click="handleBack">Back to Bookings</uiButton>
40
+ <uiDropdown :items="contactOptions" @optionSelected="handleOption" />
41
+ </div>
42
+
43
+ <!-- Card Grid -->
44
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
45
+ <!-- Guest Information Card -->
46
+ <div class="bg-white rounded-lg border border-gray-200 shadow">
47
+ <div class="px-6 py-5 border-b border-gray-200">
48
+ <h3 class="text-lg font-bold">Guest Information</h3>
49
+ </div>
50
+ <div class="px-6 py-5 space-y-8">
51
+ <div class="flex items-start gap-3">
52
+ <EnvelopeIcon class="h-4 w-4 text-gray-400" />
53
+ <div>
54
+ <div class="text-sm font-medium text-gray-500">Email</div>
55
+ <div class="text-sm text-gray-900">{{ data.email }}</div>
56
+ </div>
57
+ </div>
58
+ <!-- More fields... -->
59
+ </div>
60
+ </div>
61
+ <!-- More cards... -->
62
+ </div>
63
+
64
+ <!-- Timeline -->
65
+ <uiTimeline :timeline="timelineData" />
66
+ </uiWrapper>
67
+ ```
68
+
69
+ ## Enhanced Pattern (with @htlkg/*)
70
+
71
+ ### Astro Page
72
+
73
+ ```astro
74
+ ---
75
+ // src/pages/[brandId]/admin/booking/[bookingId].astro
76
+ import Layout from '@/layouts/Layout.astro';
77
+ import BookingDetails from '@/components/admin/BookingDetails.vue';
78
+ import { getBooking } from '@/services/booking';
79
+
80
+ export const prerender = false;
81
+
82
+ const { brandId, bookingId } = Astro.params;
83
+ const booking = await getBooking(bookingId);
84
+
85
+ if (!booking) {
86
+ return Astro.redirect('/404');
87
+ }
88
+
89
+ const layoutProps = {
90
+ title: `Booking ${booking.id}`,
91
+ breadcrumbs: [
92
+ { name: 'Bookings', href: `/${brandId}/admin/bookings` },
93
+ { name: booking.id, current: true },
94
+ ],
95
+ };
96
+ ---
97
+
98
+ <Layout {...layoutProps}>
99
+ <BookingDetails
100
+ client:load
101
+ booking={booking}
102
+ brandId={brandId}
103
+ />
104
+ </Layout>
105
+ ```
106
+
107
+ ### Vue Component
108
+
109
+ ```vue
110
+ <!-- src/components/admin/BookingDetails.vue -->
111
+ <script setup lang="ts">
112
+ import { computed } from 'vue';
113
+ import { Modal } from '@htlkg/components/overlays';
114
+ import { useModal, useConfirmation, useNotifications } from '@htlkg/components/composables';
115
+ import { uiButton, uiDropdown, uiTag, uiTimeline } from '@hotelinking/ui';
116
+ import {
117
+ ArrowLeftIcon,
118
+ EnvelopeIcon,
119
+ PhoneIcon,
120
+ MapPinIcon,
121
+ CalendarIcon,
122
+ CreditCardIcon,
123
+ UserIcon,
124
+ UsersIcon,
125
+ } from '@heroicons/vue/24/outline';
126
+
127
+ interface Props {
128
+ booking: Booking;
129
+ brandId: string;
130
+ }
131
+
132
+ const props = defineProps<Props>();
133
+ const emit = defineEmits<{
134
+ back: [];
135
+ edit: [booking: Booking];
136
+ }>();
137
+
138
+ // Modal for editing guest
139
+ const editModal = useModal<Guest>();
140
+
141
+ // Confirmation for cancel booking
142
+ const confirmation = useConfirmation();
143
+ const { notify } = useNotifications();
144
+
145
+ // Action dropdown items
146
+ const actionItems = [
147
+ { name: 'Contact guest via:', value: '0', active: true },
148
+ { name: 'Email', value: 'email' },
149
+ { name: 'Phone', value: 'phone' },
150
+ { name: 'SMS', value: 'sms' },
151
+ ];
152
+
153
+ // Timeline data
154
+ const timelineData = computed(() => ({
155
+ id: 'booking-timeline',
156
+ footerText: 'View all events',
157
+ items: props.booking.timeline.map((event, index) => ({
158
+ id: index + 1,
159
+ content: event.description,
160
+ date: formatDate(event.date),
161
+ datetime: event.date,
162
+ icon: event.icon,
163
+ iconBackground: 'default',
164
+ })),
165
+ }));
166
+
167
+ // Handlers
168
+ function handleBack() {
169
+ emit('back');
170
+ }
171
+
172
+ function handleAction(item: { value: string }) {
173
+ if (item.value === 'email') {
174
+ window.location.href = `mailto:${props.booking.guest.email}`;
175
+ }
176
+ }
177
+
178
+ async function handleCancelBooking() {
179
+ const confirmed = await confirmation.confirmDestructive('Cancel Booking');
180
+ if (confirmed) {
181
+ try {
182
+ await cancelBooking(props.booking.id);
183
+ notify({ type: 'success', message: 'Booking cancelled' });
184
+ } catch (error) {
185
+ notify({ type: 'error', message: 'Failed to cancel booking' });
186
+ }
187
+ }
188
+ }
189
+
190
+ function formatDate(dateString: string): string {
191
+ return new Date(dateString).toLocaleDateString('en-US', {
192
+ year: 'numeric',
193
+ month: 'short',
194
+ day: 'numeric',
195
+ });
196
+ }
197
+
198
+ function getInitials(name: string): string {
199
+ return name.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2);
200
+ }
201
+ </script>
202
+
203
+ <template>
204
+ <!-- Back Button + Actions -->
205
+ <div class="flex justify-between items-center mb-4">
206
+ <uiButton :icon="ArrowLeftIcon" @click="handleBack">
207
+ Back to Bookings
208
+ </uiButton>
209
+ <uiDropdown
210
+ :items="actionItems"
211
+ position="right"
212
+ @optionSelected="handleAction"
213
+ />
214
+ </div>
215
+
216
+ <!-- Card Grid -->
217
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-8">
218
+ <!-- Guest Information Card -->
219
+ <div class="bg-white rounded-lg border border-gray-200 shadow overflow-hidden">
220
+ <div class="px-6 py-5 border-b border-gray-200">
221
+ <div class="flex items-center gap-2 mb-2">
222
+ <UserIcon class="h-5 w-5 text-gray-400" />
223
+ <h3 class="text-lg font-bold text-gray-900">Guest Information</h3>
224
+ </div>
225
+ <p class="text-sm text-gray-500">
226
+ {{ booking.guest.name }} • {{ booking.guest.memberStatus }}
227
+ </p>
228
+ </div>
229
+ <div class="px-6 py-5 space-y-6">
230
+ <!-- Email -->
231
+ <div class="flex items-start gap-3">
232
+ <EnvelopeIcon class="h-4 w-4 text-gray-400 mt-0.5" />
233
+ <div>
234
+ <div class="text-sm font-medium text-gray-500">Email</div>
235
+ <div class="text-sm text-gray-900">{{ booking.guest.email }}</div>
236
+ </div>
237
+ </div>
238
+ <!-- Phone -->
239
+ <div class="flex items-start gap-3">
240
+ <PhoneIcon class="h-4 w-4 text-gray-400 mt-0.5" />
241
+ <div>
242
+ <div class="text-sm font-medium text-gray-500">Phone</div>
243
+ <div class="text-sm text-gray-900">{{ booking.guest.phone }}</div>
244
+ </div>
245
+ </div>
246
+ <!-- Address -->
247
+ <div class="flex items-start gap-3">
248
+ <MapPinIcon class="h-4 w-4 text-gray-400 mt-0.5" />
249
+ <div>
250
+ <div class="text-sm font-medium text-gray-500">Address</div>
251
+ <div class="text-sm text-gray-900">{{ booking.guest.address }}</div>
252
+ </div>
253
+ </div>
254
+ <!-- Preferences Tags -->
255
+ <div class="pt-4 border-t border-gray-200">
256
+ <div class="text-sm font-medium text-gray-500 mb-3">Preferences</div>
257
+ <div class="flex flex-wrap gap-2">
258
+ <uiTag
259
+ v-for="pref in booking.guest.preferences"
260
+ :key="pref"
261
+ color="gray"
262
+ size="small"
263
+ >
264
+ {{ pref }}
265
+ </uiTag>
266
+ </div>
267
+ </div>
268
+ </div>
269
+ </div>
270
+
271
+ <!-- Booking Summary Card -->
272
+ <div class="bg-white rounded-lg border border-gray-200 shadow overflow-hidden">
273
+ <div class="px-6 py-5 border-b border-gray-200">
274
+ <div class="flex items-center gap-2">
275
+ <CalendarIcon class="h-5 w-5 text-gray-400" />
276
+ <h3 class="text-lg font-bold text-gray-900">Booking Summary</h3>
277
+ </div>
278
+ </div>
279
+ <div class="px-6 py-5 space-y-4">
280
+ <div class="flex items-start gap-3">
281
+ <CalendarIcon class="h-4 w-4 text-gray-400 mt-0.5" />
282
+ <div>
283
+ <div class="text-sm font-medium text-gray-500">Check-in</div>
284
+ <div class="text-sm text-gray-900">{{ formatDate(booking.checkin) }}</div>
285
+ </div>
286
+ </div>
287
+ <div class="flex items-start gap-3">
288
+ <CalendarIcon class="h-4 w-4 text-gray-400 mt-0.5" />
289
+ <div>
290
+ <div class="text-sm font-medium text-gray-500">Check-out</div>
291
+ <div class="text-sm text-gray-900">{{ formatDate(booking.checkout) }}</div>
292
+ </div>
293
+ </div>
294
+ <div class="flex items-start gap-3">
295
+ <CalendarIcon class="h-4 w-4 text-gray-400 mt-0.5" />
296
+ <div>
297
+ <div class="text-sm font-medium text-gray-500">Booking Date</div>
298
+ <div class="text-sm text-gray-900">{{ formatDate(booking.createdAt) }}</div>
299
+ </div>
300
+ </div>
301
+ </div>
302
+ </div>
303
+
304
+ <!-- Payment Details Card -->
305
+ <div class="bg-white rounded-lg border border-gray-200 shadow overflow-hidden">
306
+ <div class="px-6 py-5 border-b border-gray-200">
307
+ <div class="flex items-center gap-2">
308
+ <CreditCardIcon class="h-5 w-5 text-gray-400" />
309
+ <h3 class="text-lg font-bold text-gray-900">Payment Details</h3>
310
+ </div>
311
+ </div>
312
+ <div class="px-6 py-5 space-y-4">
313
+ <div class="flex items-start gap-3">
314
+ <CreditCardIcon class="h-4 w-4 text-gray-400 mt-0.5" />
315
+ <div>
316
+ <div class="text-sm font-medium text-gray-500">Amount</div>
317
+ <div class="text-sm text-gray-900">{{ booking.payment.amount }}</div>
318
+ </div>
319
+ </div>
320
+ <div class="flex items-start gap-3">
321
+ <div class="h-4 w-4" /> <!-- Spacer -->
322
+ <div>
323
+ <div class="text-sm font-medium text-gray-500">Status</div>
324
+ <div class="mt-1">
325
+ <uiTag :color="booking.payment.status === 'Paid' ? 'green' : 'yellow'" size="small">
326
+ {{ booking.payment.status }}
327
+ </uiTag>
328
+ </div>
329
+ </div>
330
+ </div>
331
+ </div>
332
+ </div>
333
+ </div>
334
+
335
+ <!-- Companions Section -->
336
+ <div class="bg-white rounded-lg border border-gray-200 shadow overflow-hidden mb-8">
337
+ <div class="px-6 py-5 border-b border-gray-200">
338
+ <div class="flex items-center gap-2">
339
+ <UsersIcon class="h-5 w-5 text-gray-400" />
340
+ <h3 class="text-lg font-bold text-gray-900">Booking Companions</h3>
341
+ </div>
342
+ </div>
343
+ <div class="px-6 py-5 space-y-4">
344
+ <div
345
+ v-for="companion in booking.companions"
346
+ :key="companion.email"
347
+ class="flex items-center justify-between"
348
+ >
349
+ <div class="flex items-center gap-4">
350
+ <div class="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center">
351
+ <span class="text-sm font-medium text-gray-600">
352
+ {{ getInitials(companion.name) }}
353
+ </span>
354
+ </div>
355
+ <div>
356
+ <div class="font-semibold text-gray-900">{{ companion.name }}</div>
357
+ <div class="text-sm text-gray-500">{{ companion.email }}</div>
358
+ </div>
359
+ </div>
360
+ <uiTag color="gray" size="small">{{ companion.relationship }}</uiTag>
361
+ </div>
362
+ </div>
363
+ </div>
364
+
365
+ <!-- Timeline -->
366
+ <uiTimeline
367
+ name="Timeline"
368
+ :timeline="timelineData"
369
+ @timeline-event-clicked="(id) => console.log('Event clicked:', id)"
370
+ @timeline-footer-clicked="() => console.log('View all events')"
371
+ />
372
+
373
+ <!-- Edit Guest Modal -->
374
+ <Modal
375
+ :open="editModal.isOpen.value"
376
+ :title="`Edit ${editModal.data.value?.name || 'Guest'}`"
377
+ @close="editModal.close"
378
+ >
379
+ <!-- Edit form here -->
380
+ </Modal>
381
+ </template>
382
+ ```
383
+
384
+ ## Key Patterns
385
+
386
+ ### Info Card Structure
387
+
388
+ ```vue
389
+ <div class="bg-white rounded-lg border border-gray-200 shadow overflow-hidden">
390
+ <!-- Header with icon and title -->
391
+ <div class="px-6 py-5 border-b border-gray-200">
392
+ <div class="flex items-center gap-2 mb-2">
393
+ <IconComponent class="h-5 w-5 text-gray-400" />
394
+ <h3 class="text-lg font-bold text-gray-900">Card Title</h3>
395
+ </div>
396
+ <p class="text-sm text-gray-500">Subtitle or description</p>
397
+ </div>
398
+
399
+ <!-- Content with icon-labeled fields -->
400
+ <div class="px-6 py-5 space-y-6">
401
+ <div class="flex items-start gap-3">
402
+ <FieldIcon class="h-4 w-4 text-gray-400 mt-0.5" />
403
+ <div>
404
+ <div class="text-sm font-medium text-gray-500">Label</div>
405
+ <div class="text-sm text-gray-900">Value</div>
406
+ </div>
407
+ </div>
408
+ <!-- More fields... -->
409
+ </div>
410
+ </div>
411
+ ```
412
+
413
+ ### Grid Layouts
414
+
415
+ ```vue
416
+ <!-- 3-column on desktop, 1 on mobile -->
417
+ <div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
418
+
419
+ <!-- 2-column on desktop -->
420
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
421
+ ```
422
+
423
+ ### Back Navigation
424
+
425
+ ```vue
426
+ <div class="flex justify-between items-center mb-4">
427
+ <uiButton :icon="ArrowLeftIcon" @click="emit('back')">
428
+ Back to List
429
+ </uiButton>
430
+ <uiDropdown :items="actions" @optionSelected="handleAction" />
431
+ </div>
432
+ ```
433
+
434
+ ## Checklist
435
+
436
+ - [ ] Astro page with entity data fetch
437
+ - [ ] Vue component with card-based layout
438
+ - [ ] Back button navigation
439
+ - [ ] Action dropdown for entity operations
440
+ - [ ] Info cards with icon-labeled fields (3-column grid)
441
+ - [ ] Tags for status, preferences, etc.
442
+ - [ ] Related data sections (companions, related items)
443
+ - [ ] Timeline/activity log
444
+ - [ ] `useModal` for edit dialogs
445
+ - [ ] `useConfirmation` for destructive actions
446
+ - [ ] Loading/skeleton states