@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,340 @@
1
+ # List Page Pattern
2
+
3
+ Display collections of data with filtering, sorting, pagination, and bulk actions.
4
+
5
+ ## Architecture
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────────────────┐
9
+ │ AdminLayout │
10
+ ├─────────────────────────────────────────────────────────────┤
11
+ │ Page Header (title + breadcrumbs) │
12
+ ├─────────────────────────────────────────────────────────────┤
13
+ │ Optional: Tabs (for multiple views) │
14
+ ├─────────────────────────────────────────────────────────────┤
15
+ │ Optional: Stats Grid (summary metrics) │
16
+ ├─────────────────────────────────────────────────────────────┤
17
+ │ DataTable │
18
+ │ ├── Smart Filters │
19
+ │ ├── Column Headers (sortable) │
20
+ │ ├── Data Rows (selectable) │
21
+ │ ├── Row Actions │
22
+ │ └── Pagination │
23
+ ├─────────────────────────────────────────────────────────────┤
24
+ │ Optional: Modal (for row actions) │
25
+ └─────────────────────────────────────────────────────────────┘
26
+ ```
27
+
28
+ ## CRM Template Reference
29
+
30
+ Based on: `ui/src/components/Templates/CRM/ContactsAll.vue`
31
+
32
+ ```vue
33
+ <!-- CRM Template Structure (raw @hotelinking/ui) -->
34
+ <uiWrapper :sidebar :topbar>
35
+ <uiViewHeader :pages="pages" title="Contacts" />
36
+ <uiTabs :tabs="tabs" @tabClicked="handleTabClick" />
37
+
38
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
39
+ <uiStats v-for="stat in stats" :item="stat" />
40
+ </div>
41
+
42
+ <uiTable
43
+ :header="tableHeader"
44
+ :items="tableItems"
45
+ :smartFilterCategories="smartFilterCategories"
46
+ :paginationCurrent="paginationCurrent"
47
+ @smartFiltersSent="handleSmartFilters"
48
+ @changePage="handlePageChange"
49
+ />
50
+ </uiWrapper>
51
+ ```
52
+
53
+ ## Enhanced Pattern (with @htlkg/*)
54
+
55
+ ### Astro Page
56
+
57
+ ```astro
58
+ ---
59
+ // src/pages/admin/contacts.astro
60
+ import Layout from '@/layouts/Layout.astro';
61
+ import ContactsView from '@/components/admin/ContactsView.vue';
62
+ import { createListPage } from '@htlkg/astro';
63
+ import { $contacts } from '@/stores/contacts';
64
+
65
+ export const prerender = false;
66
+
67
+ const { layoutProps, initialState, items } = await createListPage(Astro, {
68
+ title: 'Contacts',
69
+ pageId: 'contacts',
70
+ fetchFn: async () => {
71
+ // Fetch contacts from API or database
72
+ return { data: await getContacts() };
73
+ },
74
+ store: $contacts,
75
+ filterableFields: [
76
+ { key: 'name', type: 'text', graphql: false },
77
+ { key: 'email', type: 'text', graphql: false },
78
+ { key: 'status', type: 'select', graphql: false, options: [
79
+ { id: 'active', name: 'Active' },
80
+ { id: 'inactive', name: 'Inactive' },
81
+ ]},
82
+ ],
83
+ searchableFields: ['name', 'email'],
84
+ defaultSort: { key: 'name', order: 'asc' },
85
+ defaultPageSize: 25,
86
+ });
87
+ ---
88
+
89
+ <Layout {...layoutProps}>
90
+ <ContactsView client:load initialState={initialState} />
91
+ </Layout>
92
+ ```
93
+
94
+ ### Vue Component
95
+
96
+ ```vue
97
+ <!-- src/components/admin/ContactsView.vue -->
98
+ <script setup lang="ts">
99
+ import { computed, ref } from 'vue';
100
+ import { useStore } from '@nanostores/vue';
101
+ import { DataTable, columns } from '@htlkg/components/data';
102
+ import { Modal } from '@htlkg/components/overlays';
103
+ import { useTable, useModal, useTabs } from '@htlkg/components/composables';
104
+ import { $contacts } from '@/stores/contacts';
105
+
106
+ interface Props {
107
+ initialState: {
108
+ pageSize: number;
109
+ sortKey: string;
110
+ sortOrder: 'asc' | 'desc';
111
+ };
112
+ }
113
+
114
+ const props = defineProps<Props>();
115
+
116
+ // Data from store
117
+ const contacts = useStore($contacts);
118
+
119
+ // Tab state
120
+ const { activeTab, tabs, setActiveTab } = useTabs({
121
+ tabs: [
122
+ { id: 'all', label: 'All Contacts' },
123
+ { id: 'tags', label: 'Tags' },
124
+ { id: 'segments', label: 'Segments' },
125
+ ],
126
+ defaultTab: 'all',
127
+ });
128
+
129
+ // Table state
130
+ const {
131
+ items,
132
+ currentPage,
133
+ pageSize,
134
+ totalPages,
135
+ sortKey,
136
+ sortOrder,
137
+ goToPage,
138
+ setPageSize,
139
+ setSortBy,
140
+ setFilter,
141
+ } = useTable({
142
+ items: contacts,
143
+ pageSize: props.initialState.pageSize,
144
+ defaultSort: {
145
+ key: props.initialState.sortKey,
146
+ order: props.initialState.sortOrder,
147
+ },
148
+ });
149
+
150
+ // Modal for contact details
151
+ const contactModal = useModal<Contact>();
152
+
153
+ // Table columns
154
+ const tableColumns = computed(() => [
155
+ columns.link('name', 'Name', {
156
+ onClick: (row) => contactModal.open(row),
157
+ }),
158
+ columns.text('email', 'Email'),
159
+ columns.text('phone', 'Phone'),
160
+ columns.text('country', 'Country'),
161
+ columns.tag('status', 'Status', {
162
+ colorMap: {
163
+ active: 'green',
164
+ inactive: 'gray',
165
+ vip: 'yellow',
166
+ },
167
+ }),
168
+ columns.tag('loyaltyTier', 'Loyalty Tier'),
169
+ columns.text('tags', 'Tags'),
170
+ ]);
171
+
172
+ // Stats for header
173
+ const stats = computed(() => [
174
+ {
175
+ id: 'total',
176
+ name: 'Total Contacts',
177
+ stat: contacts.value.length.toLocaleString(),
178
+ icon: 'UsersIcon',
179
+ color: 'green',
180
+ },
181
+ {
182
+ id: 'new',
183
+ name: 'New This Month',
184
+ stat: '1.2K',
185
+ icon: 'PlusIcon',
186
+ color: 'green',
187
+ },
188
+ ]);
189
+
190
+ // Row actions
191
+ const rowActions = [
192
+ { name: 'Edit', id: 'edit' },
193
+ { name: 'Delete', id: 'delete' },
194
+ ];
195
+
196
+ function handleRowAction(action: string, items: number[]) {
197
+ if (action === 'delete') {
198
+ // Handle delete
199
+ }
200
+ }
201
+ </script>
202
+
203
+ <template>
204
+ <!-- Tabs -->
205
+ <div class="mb-6">
206
+ <nav class="flex space-x-4">
207
+ <button
208
+ v-for="tab in tabs"
209
+ :key="tab.id"
210
+ :class="[
211
+ 'px-3 py-2 text-sm font-medium rounded-md',
212
+ activeTab === tab.id
213
+ ? 'bg-gray-100 text-gray-900'
214
+ : 'text-gray-500 hover:text-gray-700'
215
+ ]"
216
+ @click="setActiveTab(tab.id)"
217
+ >
218
+ {{ tab.label }}
219
+ </button>
220
+ </nav>
221
+ </div>
222
+
223
+ <!-- Stats Grid -->
224
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-8">
225
+ <div
226
+ v-for="stat in stats"
227
+ :key="stat.id"
228
+ class="bg-white rounded-lg border border-gray-200 p-6"
229
+ >
230
+ <p class="text-sm font-medium text-gray-500">{{ stat.name }}</p>
231
+ <p class="mt-1 text-3xl font-semibold text-gray-900">{{ stat.stat }}</p>
232
+ </div>
233
+ </div>
234
+
235
+ <!-- Data Table -->
236
+ <DataTable
237
+ :columns="tableColumns"
238
+ :items="items"
239
+ :current-page="currentPage"
240
+ :page-size="pageSize"
241
+ :total-pages="totalPages"
242
+ :sort-key="sortKey"
243
+ :sort-order="sortOrder"
244
+ :actions="rowActions"
245
+ @page-change="goToPage"
246
+ @page-size-change="setPageSize"
247
+ @sort-change="setSortBy"
248
+ @action="handleRowAction"
249
+ />
250
+
251
+ <!-- Contact Detail Modal -->
252
+ <Modal
253
+ :open="contactModal.isOpen.value"
254
+ :title="contactModal.data.value?.name || 'Contact Details'"
255
+ @close="contactModal.close"
256
+ >
257
+ <div v-if="contactModal.data.value" class="space-y-4">
258
+ <p><strong>Email:</strong> {{ contactModal.data.value.email }}</p>
259
+ <p><strong>Phone:</strong> {{ contactModal.data.value.phone }}</p>
260
+ <p><strong>Status:</strong> {{ contactModal.data.value.status }}</p>
261
+ </div>
262
+ </Modal>
263
+ </template>
264
+ ```
265
+
266
+ ## Key Components
267
+
268
+ ### DataTable
269
+
270
+ ```typescript
271
+ import { DataTable, columns } from '@htlkg/components/data';
272
+
273
+ // Column helpers
274
+ columns.text('field', 'Label') // Plain text
275
+ columns.link('field', 'Label', options) // Clickable link
276
+ columns.tag('field', 'Label', options) // Tag/badge
277
+ columns.date('field', 'Label', format) // Formatted date
278
+ columns.currency('field', 'Label') // Currency formatting
279
+ columns.custom('field', 'Label', render) // Custom render function
280
+ ```
281
+
282
+ ### useTable Composable
283
+
284
+ ```typescript
285
+ import { useTable } from '@htlkg/components/composables';
286
+
287
+ const {
288
+ items, // Paginated, sorted, filtered items
289
+ currentPage, // Current page number
290
+ pageSize, // Items per page
291
+ totalPages, // Total page count
292
+ totalItems, // Total item count
293
+ sortKey, // Current sort field
294
+ sortOrder, // 'asc' | 'desc'
295
+ filters, // Active filters
296
+ goToPage, // Navigate to page
297
+ setPageSize, // Change page size
298
+ setSortBy, // Change sort
299
+ setFilter, // Apply filter
300
+ clearFilters, // Clear all filters
301
+ } = useTable(options);
302
+ ```
303
+
304
+ ### Smart Filters
305
+
306
+ ```typescript
307
+ const filterableFields = [
308
+ {
309
+ key: 'name',
310
+ type: 'text', // Text input filter
311
+ placeholder: 'Search by name...',
312
+ },
313
+ {
314
+ key: 'status',
315
+ type: 'select', // Dropdown filter
316
+ options: [
317
+ { id: 'active', name: 'Active' },
318
+ { id: 'inactive', name: 'Inactive' },
319
+ ],
320
+ },
321
+ {
322
+ key: 'createdAt',
323
+ type: 'date', // Date range filter
324
+ },
325
+ ];
326
+ ```
327
+
328
+ ## Checklist
329
+
330
+ - [ ] Astro page with `createListPage` factory
331
+ - [ ] Vue component with `DataTable`
332
+ - [ ] `useTable` composable for state
333
+ - [ ] Column definitions with helpers
334
+ - [ ] Smart filters for searchable fields
335
+ - [ ] Pagination controls
336
+ - [ ] Sort by column headers
337
+ - [ ] Row actions (edit, delete, etc.)
338
+ - [ ] Optional: Tabs for multiple views
339
+ - [ ] Optional: Stats grid at top
340
+ - [ ] Optional: Modal for row details
@@ -0,0 +1,110 @@
1
+ # Page Patterns Overview
2
+
3
+ This documentation describes the standard page patterns used in Hotelinking applications. These patterns are based on the CRM template examples in `@hotelinking/ui` but enhanced to use `@htlkg/*` shared packages.
4
+
5
+ ## Pattern Selection Guide
6
+
7
+ | Pattern | Use When | Example |
8
+ |---------|----------|---------|
9
+ | **List Page** | Displaying collections of data with filtering, sorting, pagination | Contacts, Campaigns, Bookings |
10
+ | **Detail Page** | Showing comprehensive info about a single entity | Booking Details, Contact Profile |
11
+ | **Form Page** | Creating or editing entity data | Settings, Brand Configuration |
12
+ | **Wizard Page** | Multi-step data entry with progress indicator | Campaign Creation, Onboarding |
13
+ | **Dashboard Page** | Displaying metrics, KPIs, and analytics | Overview, Analytics |
14
+
15
+ ## Architecture
16
+
17
+ ```
18
+ ┌─────────────────────────────────────────────────────────────┐
19
+ │ Astro Page (.astro) │
20
+ │ - Server-side data fetching │
21
+ │ - Pass data to Vue islands │
22
+ └─────────────────────────────────────────────────────────────┘
23
+
24
+
25
+ ┌─────────────────────────────────────────────────────────────┐
26
+ │ @htlkg/astro Layout (AdminLayout, BrandLayout) │
27
+ │ - Sidebar navigation │
28
+ │ - Topbar with user menu │
29
+ │ - Page structure │
30
+ └─────────────────────────────────────────────────────────────┘
31
+
32
+
33
+ ┌─────────────────────────────────────────────────────────────┐
34
+ │ Vue Component (client:load) │
35
+ │ - Interactive UI │
36
+ │ - Uses @htlkg/components │
37
+ │ - Uses composables for state │
38
+ └─────────────────────────────────────────────────────────────┘
39
+ ```
40
+
41
+ ## CRM to @htlkg/* Mapping
42
+
43
+ | CRM Template Component | @htlkg/* Replacement |
44
+ |-----------------------|----------------------|
45
+ | `uiWrapper` | `AdminLayout` from `@htlkg/astro/layouts` |
46
+ | `uiViewHeader` | Built into layout or `PageHeader` component |
47
+ | `uiTable` | `DataTable` from `@htlkg/components/data` + `useTable` |
48
+ | `uiModal` | `Modal` from `@htlkg/components/overlays` + `useModal` |
49
+ | `uiTabs` | `Tabs` from `@htlkg/components/navigation` + `useTabs` |
50
+ | `uiStats` | Stats pattern with grid layout |
51
+ | Manual form state | `JsonSchemaForm` + `useJsonForm` composable |
52
+ | `uiStepsV4` | `Stepper` from `@htlkg/components/navigation` + `useWizard` |
53
+
54
+ ## Key Composables
55
+
56
+ | Composable | Purpose | Import |
57
+ |------------|---------|--------|
58
+ | `useTable` | Table state (sort, filter, pagination) | `@htlkg/components/composables` |
59
+ | `useModal` | Modal visibility and data context | `@htlkg/components/composables` |
60
+ | `useJsonForm` | Form state with isDirty, confirmOnLeave | `@htlkg/components/composables` |
61
+ | `useConfirmation` | Delete/reset confirmation dialogs | `@htlkg/components/composables` |
62
+ | `useTabs` | Tab navigation state | `@htlkg/components/composables` |
63
+ | `useWizard` | Multi-step wizard navigation | `@htlkg/components/composables` |
64
+ | `useNotifications` | Toast notifications | `@htlkg/components/composables` |
65
+
66
+ ## Pattern Documentation
67
+
68
+ - [LIST_PAGE.md](./LIST_PAGE.md) - List/table page pattern
69
+ - [DETAIL_PAGE.md](./DETAIL_PAGE.md) - Detail page pattern
70
+ - [FORM_PAGE.md](./FORM_PAGE.md) - Form page pattern
71
+ - [WIZARD_PAGE.md](./WIZARD_PAGE.md) - Multi-step wizard pattern
72
+ - [DASHBOARD_PAGE.md](./DASHBOARD_PAGE.md) - Dashboard page pattern
73
+
74
+ ## Common Structure
75
+
76
+ All patterns follow this basic structure:
77
+
78
+ ```astro
79
+ ---
80
+ // 1. Imports
81
+ import Layout from '@/layouts/Layout.astro';
82
+ import MyComponent from '@/components/MyComponent.vue';
83
+
84
+ // 2. Server-side data fetching
85
+ const data = await fetchData();
86
+
87
+ // 3. Page metadata
88
+ const title = 'Page Title';
89
+ const breadcrumbs = [
90
+ { name: 'Home', href: '/' },
91
+ { name: 'Current', current: true }
92
+ ];
93
+ ---
94
+
95
+ <!-- 4. Layout wrapper -->
96
+ <Layout title={title} breadcrumbs={breadcrumbs}>
97
+ <!-- 5. Vue island for interactivity -->
98
+ <MyComponent client:load initialData={data} />
99
+ </Layout>
100
+ ```
101
+
102
+ ## Best Practices
103
+
104
+ 1. **Data Fetching**: Always fetch data server-side in Astro frontmatter
105
+ 2. **Hydration**: Use `client:load` for interactive components
106
+ 3. **State**: Use composables from `@htlkg/components/composables`
107
+ 4. **Forms**: Use `JsonSchemaForm` for all forms except very simple ones
108
+ 5. **Modals**: Use `useModal` composable with data context
109
+ 6. **Tables**: Use `DataTable` with `useTable` for consistent behavior
110
+ 7. **Validation**: Let JsonSchemaForm handle validation via JSON Schema