@htlkg/components 0.0.1

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 (79) hide show
  1. package/dist/composables/index.js +388 -0
  2. package/dist/composables/index.js.map +1 -0
  3. package/package.json +41 -0
  4. package/src/composables/index.ts +6 -0
  5. package/src/composables/useForm.test.ts +229 -0
  6. package/src/composables/useForm.ts +130 -0
  7. package/src/composables/useFormValidation.test.ts +189 -0
  8. package/src/composables/useFormValidation.ts +83 -0
  9. package/src/composables/useModal.property.test.ts +164 -0
  10. package/src/composables/useModal.ts +43 -0
  11. package/src/composables/useNotifications.test.ts +166 -0
  12. package/src/composables/useNotifications.ts +81 -0
  13. package/src/composables/useTable.property.test.ts +198 -0
  14. package/src/composables/useTable.ts +134 -0
  15. package/src/composables/useTabs.property.test.ts +247 -0
  16. package/src/composables/useTabs.ts +101 -0
  17. package/src/data/Chart.demo.vue +340 -0
  18. package/src/data/Chart.md +525 -0
  19. package/src/data/Chart.vue +133 -0
  20. package/src/data/DataList.md +80 -0
  21. package/src/data/DataList.test.ts +69 -0
  22. package/src/data/DataList.vue +46 -0
  23. package/src/data/SearchableSelect.md +107 -0
  24. package/src/data/SearchableSelect.vue +124 -0
  25. package/src/data/Table.demo.vue +296 -0
  26. package/src/data/Table.md +588 -0
  27. package/src/data/Table.property.test.ts +548 -0
  28. package/src/data/Table.test.ts +562 -0
  29. package/src/data/Table.unit.test.ts +544 -0
  30. package/src/data/Table.vue +321 -0
  31. package/src/data/index.ts +5 -0
  32. package/src/domain/BrandCard.md +81 -0
  33. package/src/domain/BrandCard.vue +63 -0
  34. package/src/domain/BrandSelector.md +84 -0
  35. package/src/domain/BrandSelector.vue +65 -0
  36. package/src/domain/ProductBadge.md +60 -0
  37. package/src/domain/ProductBadge.vue +47 -0
  38. package/src/domain/UserAvatar.md +84 -0
  39. package/src/domain/UserAvatar.vue +60 -0
  40. package/src/domain/domain-components.property.test.ts +449 -0
  41. package/src/domain/index.ts +4 -0
  42. package/src/forms/DateRange.demo.vue +273 -0
  43. package/src/forms/DateRange.md +337 -0
  44. package/src/forms/DateRange.vue +110 -0
  45. package/src/forms/JsonSchemaForm.demo.vue +549 -0
  46. package/src/forms/JsonSchemaForm.md +112 -0
  47. package/src/forms/JsonSchemaForm.property.test.ts +817 -0
  48. package/src/forms/JsonSchemaForm.test.ts +601 -0
  49. package/src/forms/JsonSchemaForm.unit.test.ts +801 -0
  50. package/src/forms/JsonSchemaForm.vue +615 -0
  51. package/src/forms/index.ts +3 -0
  52. package/src/index.ts +17 -0
  53. package/src/navigation/Breadcrumbs.demo.vue +142 -0
  54. package/src/navigation/Breadcrumbs.md +102 -0
  55. package/src/navigation/Breadcrumbs.test.ts +69 -0
  56. package/src/navigation/Breadcrumbs.vue +58 -0
  57. package/src/navigation/Stepper.demo.vue +337 -0
  58. package/src/navigation/Stepper.md +174 -0
  59. package/src/navigation/Stepper.vue +146 -0
  60. package/src/navigation/Tabs.demo.vue +293 -0
  61. package/src/navigation/Tabs.md +163 -0
  62. package/src/navigation/Tabs.test.ts +176 -0
  63. package/src/navigation/Tabs.vue +104 -0
  64. package/src/navigation/index.ts +5 -0
  65. package/src/overlays/Alert.demo.vue +377 -0
  66. package/src/overlays/Alert.md +248 -0
  67. package/src/overlays/Alert.test.ts +166 -0
  68. package/src/overlays/Alert.vue +70 -0
  69. package/src/overlays/Drawer.md +140 -0
  70. package/src/overlays/Drawer.test.ts +92 -0
  71. package/src/overlays/Drawer.vue +76 -0
  72. package/src/overlays/Modal.demo.vue +149 -0
  73. package/src/overlays/Modal.md +385 -0
  74. package/src/overlays/Modal.test.ts +128 -0
  75. package/src/overlays/Modal.vue +86 -0
  76. package/src/overlays/Notification.md +150 -0
  77. package/src/overlays/Notification.test.ts +96 -0
  78. package/src/overlays/Notification.vue +58 -0
  79. package/src/overlays/index.ts +4 -0
@@ -0,0 +1,273 @@
1
+ <template>
2
+ <div class="space-y-8">
3
+ <div>
4
+ <h2 class="text-2xl font-bold mb-4">DateRange Component Demo</h2>
5
+ <p class="text-gray-600 mb-6">
6
+ A date range selector with start/end dates and search button.
7
+ </p>
8
+ </div>
9
+
10
+ <!-- Basic Example -->
11
+ <div class="border rounded-lg p-6">
12
+ <h3 class="text-lg font-semibold mb-4">Basic Usage</h3>
13
+ <DateRange
14
+ v-model="basicRange"
15
+ label="Select date range"
16
+ @search="handleBasicSearch"
17
+ />
18
+ <div class="mt-4 p-4 bg-gray-50 rounded">
19
+ <p class="text-sm font-medium mb-2">Current v-model value:</p>
20
+ <pre class="text-xs bg-white p-2 rounded border">{{ JSON.stringify(basicRange, null, 2) }}</pre>
21
+ </div>
22
+ <div v-if="basicResult" class="mt-4 p-4 bg-blue-50 rounded">
23
+ <p class="text-sm font-medium mb-2">Search clicked with:</p>
24
+ <pre class="text-xs bg-white p-2 rounded border">{{ JSON.stringify(basicResult, null, 2) }}</pre>
25
+ </div>
26
+ </div>
27
+
28
+ <!-- Custom Literals -->
29
+ <div class="border rounded-lg p-6">
30
+ <h3 class="text-lg font-semibold mb-4">Custom Literals</h3>
31
+ <DateRange
32
+ v-model="customRange"
33
+ :literals="customLiterals"
34
+ label="Report Period"
35
+ @search="handleCustomSearch"
36
+ />
37
+ <div class="mt-4 p-4 bg-gray-50 rounded">
38
+ <p class="text-sm font-medium mb-2">Current v-model value:</p>
39
+ <pre class="text-xs bg-white p-2 rounded border">{{ JSON.stringify(customRange, null, 2) }}</pre>
40
+ </div>
41
+ <div v-if="customResult" class="mt-4 p-4 bg-green-50 rounded">
42
+ <p class="text-sm font-medium mb-2">Report generated for:</p>
43
+ <pre class="text-xs bg-white p-2 rounded border">{{ JSON.stringify(customResult, null, 2) }}</pre>
44
+ </div>
45
+ </div>
46
+
47
+ <!-- With Validation -->
48
+ <div class="border rounded-lg p-6">
49
+ <h3 class="text-lg font-semibold mb-4">With Validation</h3>
50
+ <DateRange
51
+ ref="validationRef"
52
+ v-model="validationRange"
53
+ :error="validationError"
54
+ :color="validationError ? 'red' : undefined"
55
+ label="Booking Dates"
56
+ @search="handleValidation"
57
+ />
58
+ <div class="mt-4 p-4 bg-gray-50 rounded">
59
+ <p class="text-sm font-medium mb-2">Current v-model value:</p>
60
+ <pre class="text-xs bg-white p-2 rounded border">{{ JSON.stringify(validationRange, null, 2) }}</pre>
61
+ <p v-if="validationError" class="text-sm text-red-600 mt-2 font-medium">
62
+ Error: {{ validationError }}
63
+ </p>
64
+ </div>
65
+ <button
66
+ @click="resetValidation"
67
+ class="mt-4 px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
68
+ >
69
+ Reset
70
+ </button>
71
+ </div>
72
+
73
+ <!-- Loading State -->
74
+ <div class="border rounded-lg p-6">
75
+ <h3 class="text-lg font-semibold mb-4">Loading State</h3>
76
+ <DateRange
77
+ v-model="loadingRange"
78
+ :loading="isLoading"
79
+ label="Loading example"
80
+ @search="handleLoadingSearch"
81
+ />
82
+ <div class="mt-4 p-4 bg-gray-50 rounded">
83
+ <p class="text-sm font-medium mb-2">Current v-model value:</p>
84
+ <pre class="text-xs bg-white p-2 rounded border">{{ JSON.stringify(loadingRange, null, 2) }}</pre>
85
+ <p class="text-sm mt-2">Loading state: <span class="font-mono">{{ isLoading }}</span></p>
86
+ </div>
87
+ <button
88
+ @click="toggleLoading"
89
+ class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
90
+ >
91
+ {{ isLoading ? 'Stop Loading' : 'Start Loading' }}
92
+ </button>
93
+ </div>
94
+
95
+ <!-- Disabled State -->
96
+ <div class="border rounded-lg p-6">
97
+ <h3 class="text-lg font-semibold mb-4">Disabled State</h3>
98
+ <DateRange
99
+ v-model="disabledRange"
100
+ :disabled="isDisabled"
101
+ label="Disabled example"
102
+ />
103
+ <div class="mt-4 p-4 bg-gray-50 rounded">
104
+ <p class="text-sm font-medium mb-2">Current v-model value:</p>
105
+ <pre class="text-xs bg-white p-2 rounded border">{{ JSON.stringify(disabledRange, null, 2) }}</pre>
106
+ <p class="text-sm mt-2">Disabled state: <span class="font-mono">{{ isDisabled }}</span></p>
107
+ </div>
108
+ <button
109
+ @click="isDisabled = !isDisabled"
110
+ class="mt-4 px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"
111
+ >
112
+ {{ isDisabled ? 'Enable' : 'Disable' }}
113
+ </button>
114
+ </div>
115
+
116
+ <!-- Programmatic Control -->
117
+ <div class="border rounded-lg p-6">
118
+ <h3 class="text-lg font-semibold mb-4">Programmatic Control</h3>
119
+ <DateRange
120
+ ref="programmaticRef"
121
+ v-model="programmaticRange"
122
+ label="Controlled example"
123
+ />
124
+ <div class="mt-4 p-4 bg-gray-50 rounded">
125
+ <p class="text-sm font-medium mb-2">Current v-model value:</p>
126
+ <pre class="text-xs bg-white p-2 rounded border">{{ JSON.stringify(programmaticRange, null, 2) }}</pre>
127
+ </div>
128
+ <div class="mt-4 space-x-2">
129
+ <button
130
+ @click="setLastWeek"
131
+ class="px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600"
132
+ >
133
+ Last Week
134
+ </button>
135
+ <button
136
+ @click="setLastMonth"
137
+ class="px-4 py-2 bg-purple-500 text-white rounded hover:bg-purple-600"
138
+ >
139
+ Last Month
140
+ </button>
141
+ <button
142
+ @click="resetProgrammatic"
143
+ class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-600"
144
+ >
145
+ Reset
146
+ </button>
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </template>
151
+
152
+ <script setup lang="ts">
153
+ import { ref } from 'vue';
154
+ import { DateRange } from '@htlkg/components';
155
+ import type { DateRangeValue } from '@htlkg/components';
156
+
157
+ // Basic example
158
+ const basicRange = ref<DateRangeValue>({
159
+ from: '',
160
+ to: new Date().toISOString().slice(0, 16)
161
+ });
162
+ const basicResult = ref<DateRangeValue | null>(null);
163
+
164
+ function handleBasicSearch(dates: DateRangeValue | undefined) {
165
+ basicResult.value = dates || null;
166
+ }
167
+
168
+ // Custom literals
169
+ const customLiterals = {
170
+ from: 'Start Date',
171
+ to: 'End Date',
172
+ search: 'Generate Report'
173
+ };
174
+ const customRange = ref<DateRangeValue>({
175
+ from: '',
176
+ to: new Date().toISOString().slice(0, 16)
177
+ });
178
+ const customResult = ref<DateRangeValue | null>(null);
179
+
180
+ function handleCustomSearch(dates: DateRangeValue | undefined) {
181
+ customResult.value = dates || null;
182
+ }
183
+
184
+ // Validation example
185
+ const validationRef = ref<InstanceType<typeof DateRange>>();
186
+ const validationRange = ref<DateRangeValue>({
187
+ from: '',
188
+ to: ''
189
+ });
190
+ const validationError = ref('');
191
+
192
+ function handleValidation(dates: DateRangeValue | undefined) {
193
+ if (!dates || !dates.from || !dates.to) {
194
+ validationError.value = 'Both dates are required';
195
+ return;
196
+ }
197
+
198
+ const fromDate = new Date(dates.from);
199
+ const toDate = new Date(dates.to);
200
+
201
+ if (fromDate > toDate) {
202
+ validationError.value = 'Start date must be before end date';
203
+ return;
204
+ }
205
+
206
+ const diffDays = Math.ceil((toDate.getTime() - fromDate.getTime()) / (1000 * 60 * 60 * 24));
207
+ if (diffDays > 365) {
208
+ validationError.value = 'Maximum range is 1 year';
209
+ return;
210
+ }
211
+
212
+ validationError.value = '';
213
+ alert(`Valid range: ${dates.from} to ${dates.to}`);
214
+ }
215
+
216
+ function resetValidation() {
217
+ validationRef.value?.reset();
218
+ validationError.value = '';
219
+ }
220
+
221
+ // Loading example
222
+ const isLoading = ref(false);
223
+ const loadingRange = ref<DateRangeValue>({
224
+ from: '',
225
+ to: new Date().toISOString().slice(0, 16)
226
+ });
227
+
228
+ function toggleLoading() {
229
+ isLoading.value = !isLoading.value;
230
+ }
231
+
232
+ function handleLoadingSearch(dates: DateRangeValue | undefined) {
233
+ if (dates) {
234
+ alert(`Searching from ${dates.from} to ${dates.to}`);
235
+ }
236
+ }
237
+
238
+ // Disabled example
239
+ const isDisabled = ref(true);
240
+ const disabledRange = ref<DateRangeValue>({
241
+ from: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, 16),
242
+ to: new Date().toISOString().slice(0, 16)
243
+ });
244
+
245
+ // Programmatic control
246
+ const programmaticRef = ref<InstanceType<typeof DateRange>>();
247
+ const programmaticRange = ref<DateRangeValue>({
248
+ from: '',
249
+ to: new Date().toISOString().slice(0, 16)
250
+ });
251
+
252
+ function setLastWeek() {
253
+ const to = new Date();
254
+ const from = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
255
+ programmaticRef.value?.setValue({
256
+ from: from.toISOString().slice(0, 16),
257
+ to: to.toISOString().slice(0, 16)
258
+ });
259
+ }
260
+
261
+ function setLastMonth() {
262
+ const to = new Date();
263
+ const from = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
264
+ programmaticRef.value?.setValue({
265
+ from: from.toISOString().slice(0, 16),
266
+ to: to.toISOString().slice(0, 16)
267
+ });
268
+ }
269
+
270
+ function resetProgrammatic() {
271
+ programmaticRef.value?.reset();
272
+ }
273
+ </script>
@@ -0,0 +1,337 @@
1
+ # DateRange Component
2
+
3
+ A date range selector component that allows users to choose a start and end date with a search button. Built on top of `uiDateRange` from `@hotelinking/ui`.
4
+
5
+ ## Features
6
+
7
+ - **Dual date inputs**: Separate inputs for start and end dates using `datetime-local` type
8
+ - **Visual addons**: "From" and "To" labels appear as addons in the inputs
9
+ - **Search button**: Dedicated button to trigger the search action
10
+ - **v-model support**: Two-way binding for the date range value
11
+ - **Validation states**: Support for error messages and color states
12
+ - **Loading state**: Shows skeleton loaders while data is loading
13
+ - **Disabled state**: Can disable all inputs and button
14
+ - **Responsive**: Adapts between horizontal (desktop) and vertical (mobile) layouts
15
+ - **Customizable literals**: All text labels are configurable
16
+ - **Max date constraint**: Both inputs have max set to current date/time
17
+ - **Exposed methods**: Programmatic control via `reset()`, `getValue()`, `setValue()`
18
+
19
+ ## Import
20
+
21
+ ```typescript
22
+ import { DateRange } from '@htlkg/components';
23
+ import type { DateRangeValue, DateRangeLiterals } from '@htlkg/components';
24
+ ```
25
+
26
+ ## Props
27
+
28
+ | Prop | Type | Default | Description |
29
+ |------|------|---------|-------------|
30
+ | `id` | `string` | auto-generated | Unique identifier for the component |
31
+ | `modelValue` | `DateRangeValue` | `{ from: '', to: now }` | Current date range value |
32
+ | `literals` | `DateRangeLiterals` | `{ from: 'From', to: 'To', search: 'Search' }` | Text labels for the component |
33
+ | `loading` | `boolean` | `false` | Shows skeleton loaders when true |
34
+ | `color` | `string` | `undefined` | Color for validation states (green, red, yellow, etc.) |
35
+ | `error` | `string` | `undefined` | Error message to display below inputs |
36
+ | `disabled` | `boolean` | `false` | Disables all inputs and button |
37
+ | `label` | `string` | `undefined` | Label text above the first input |
38
+ | `placeholder` | `string` | `undefined` | Placeholder text for inputs |
39
+
40
+ ### Type Definitions
41
+
42
+ ```typescript
43
+ interface DateRangeValue {
44
+ from: Date | string;
45
+ to: Date | string;
46
+ }
47
+
48
+ interface DateRangeLiterals {
49
+ from: string;
50
+ to: string;
51
+ search: string;
52
+ }
53
+ ```
54
+
55
+ ## Events
56
+
57
+ | Event | Payload | Description |
58
+ |-------|---------|-------------|
59
+ | `update:modelValue` | `DateRangeValue \| undefined` | Emitted when search button is clicked (v-model support) |
60
+ | `search` | `DateRangeValue \| undefined` | Emitted when search button is clicked |
61
+ | `change` | `DateRangeValue \| undefined` | Emitted when search button is clicked |
62
+
63
+ ## Exposed Methods
64
+
65
+ | Method | Parameters | Returns | Description |
66
+ |--------|------------|---------|-------------|
67
+ | `reset()` | - | `void` | Resets the date range to default values |
68
+ | `getValue()` | - | `DateRangeValue` | Returns the current date range value |
69
+ | `setValue(value)` | `DateRangeValue` | `void` | Sets the date range value programmatically |
70
+
71
+ ## Usage Examples
72
+
73
+ ### Basic Usage
74
+
75
+ ```vue
76
+ <template>
77
+ <DateRange
78
+ v-model="dateRange"
79
+ label="Select date range"
80
+ @search="handleSearch"
81
+ />
82
+ </template>
83
+
84
+ <script setup lang="ts">
85
+ import { ref } from 'vue';
86
+ import { DateRange } from '@htlkg/components';
87
+ import type { DateRangeValue } from '@htlkg/components';
88
+
89
+ const dateRange = ref<DateRangeValue>({
90
+ from: '',
91
+ to: new Date().toISOString().slice(0, 16)
92
+ });
93
+
94
+ function handleSearch(dates: DateRangeValue | undefined) {
95
+ if (dates) {
96
+ console.log('Search from:', dates.from, 'to:', dates.to);
97
+ // Perform search with date range
98
+ }
99
+ }
100
+ </script>
101
+ ```
102
+
103
+ ### Report Filter
104
+
105
+ ```vue
106
+ <template>
107
+ <DateRange
108
+ v-model="reportDates"
109
+ :literals="literals"
110
+ :loading="loading"
111
+ :error="error"
112
+ :color="error ? 'red' : undefined"
113
+ label="Report period"
114
+ @search="generateReport"
115
+ />
116
+ </template>
117
+
118
+ <script setup lang="ts">
119
+ import { ref } from 'vue';
120
+ import { DateRange } from '@htlkg/components';
121
+ import type { DateRangeValue } from '@htlkg/components';
122
+
123
+ const loading = ref(false);
124
+ const error = ref('');
125
+
126
+ const literals = {
127
+ from: 'Start date',
128
+ to: 'End date',
129
+ search: 'Generate Report'
130
+ };
131
+
132
+ const reportDates = ref<DateRangeValue>({
133
+ from: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString().slice(0, 16),
134
+ to: new Date().toISOString().slice(0, 16)
135
+ });
136
+
137
+ async function generateReport(dates: DateRangeValue | undefined) {
138
+ if (!dates) return;
139
+
140
+ // Validate date range
141
+ const from = new Date(dates.from);
142
+ const to = new Date(dates.to);
143
+ const diffDays = Math.ceil((to.getTime() - from.getTime()) / (1000 * 60 * 60 * 24));
144
+
145
+ if (diffDays > 365) {
146
+ error.value = 'Maximum range is 1 year';
147
+ return;
148
+ }
149
+
150
+ error.value = '';
151
+ loading.value = true;
152
+
153
+ try {
154
+ // await fetchReport(dates.from, dates.to);
155
+ console.log('Generating report...');
156
+ } finally {
157
+ loading.value = false;
158
+ }
159
+ }
160
+ </script>
161
+ ```
162
+
163
+ ### With Validation
164
+
165
+ ```vue
166
+ <template>
167
+ <DateRange
168
+ ref="dateRangeRef"
169
+ v-model="dates"
170
+ :error="errorMessage"
171
+ :color="errorMessage ? 'red' : 'green'"
172
+ label="Booking dates"
173
+ @search="validateAndSearch"
174
+ />
175
+ </template>
176
+
177
+ <script setup lang="ts">
178
+ import { ref } from 'vue';
179
+ import { DateRange } from '@htlkg/components';
180
+ import type { DateRangeValue } from '@htlkg/components';
181
+
182
+ const dateRangeRef = ref<InstanceType<typeof DateRange>>();
183
+ const errorMessage = ref('');
184
+
185
+ const dates = ref<DateRangeValue>({
186
+ from: '',
187
+ to: ''
188
+ });
189
+
190
+ function validateAndSearch(dates: DateRangeValue | undefined) {
191
+ if (!dates || !dates.from || !dates.to) {
192
+ errorMessage.value = 'Both dates are required';
193
+ return;
194
+ }
195
+
196
+ const fromDate = new Date(dates.from);
197
+ const toDate = new Date(dates.to);
198
+
199
+ if (fromDate > toDate) {
200
+ errorMessage.value = 'Start date must be before end date';
201
+ return;
202
+ }
203
+
204
+ errorMessage.value = '';
205
+ // Proceed with search
206
+ console.log('Valid date range:', dates);
207
+ }
208
+
209
+ // Programmatic control
210
+ function resetDates() {
211
+ dateRangeRef.value?.reset();
212
+ errorMessage.value = '';
213
+ }
214
+ </script>
215
+ ```
216
+
217
+ ### Custom Literals (Internationalization)
218
+
219
+ ```vue
220
+ <template>
221
+ <DateRange
222
+ v-model="dateRange"
223
+ :literals="spanishLiterals"
224
+ label="Rango de fechas"
225
+ @search="handleSearch"
226
+ />
227
+ </template>
228
+
229
+ <script setup lang="ts">
230
+ import { ref } from 'vue';
231
+ import { DateRange } from '@htlkg/components';
232
+ import type { DateRangeValue } from '@htlkg/components';
233
+
234
+ const spanishLiterals = {
235
+ from: 'Desde',
236
+ to: 'Hasta',
237
+ search: 'Buscar'
238
+ };
239
+
240
+ const dateRange = ref<DateRangeValue>({
241
+ from: '',
242
+ to: new Date().toISOString().slice(0, 16)
243
+ });
244
+
245
+ function handleSearch(dates: DateRangeValue | undefined) {
246
+ console.log('Búsqueda:', dates);
247
+ }
248
+ </script>
249
+ ```
250
+
251
+ ### Disabled State
252
+
253
+ ```vue
254
+ <template>
255
+ <DateRange
256
+ v-model="dateRange"
257
+ :disabled="isProcessing"
258
+ label="Date range"
259
+ @search="handleSearch"
260
+ />
261
+ </template>
262
+
263
+ <script setup lang="ts">
264
+ import { ref } from 'vue';
265
+ import { DateRange } from '@htlkg/components';
266
+
267
+ const isProcessing = ref(false);
268
+ const dateRange = ref({
269
+ from: '',
270
+ to: new Date().toISOString().slice(0, 16)
271
+ });
272
+
273
+ async function handleSearch(dates) {
274
+ isProcessing.value = true;
275
+ try {
276
+ // Process search
277
+ } finally {
278
+ isProcessing.value = false;
279
+ }
280
+ }
281
+ </script>
282
+ ```
283
+
284
+ ## TypeScript Support
285
+
286
+ The component is fully typed with TypeScript:
287
+
288
+ ```typescript
289
+ import { DateRange } from '@htlkg/components';
290
+ import type { DateRangeValue, DateRangeLiterals } from '@htlkg/components';
291
+
292
+ // Type-safe date range
293
+ const dateRange = ref<DateRangeValue>({
294
+ from: '2024-01-01T00:00',
295
+ to: '2024-12-31T23:59'
296
+ });
297
+
298
+ // Type-safe literals
299
+ const literals: DateRangeLiterals = {
300
+ from: 'Start',
301
+ to: 'End',
302
+ search: 'Apply'
303
+ };
304
+
305
+ // Type-safe ref
306
+ const dateRangeRef = ref<InstanceType<typeof DateRange>>();
307
+ dateRangeRef.value?.reset();
308
+ ```
309
+
310
+ ## Best Practices
311
+
312
+ 1. **Always validate date ranges**: Check that start date is before end date
313
+ 2. **Set reasonable limits**: Consider adding maximum range validation (e.g., 1 year)
314
+ 3. **Provide clear error messages**: Use the `error` prop to guide users
315
+ 4. **Use appropriate literals**: Customize text based on context (e.g., "Check-in" / "Check-out")
316
+ 5. **Handle empty values**: Check for undefined or empty dates before processing
317
+ 6. **Consider time zones**: Be aware that datetime-local uses local time
318
+ 7. **Provide default values**: Set sensible defaults (e.g., last 7 days)
319
+ 8. **Use loading state**: Show loading while fetching data based on date range
320
+
321
+ ## Accessibility
322
+
323
+ - Uses native `datetime-local` inputs for better accessibility
324
+ - Proper label association with inputs
325
+ - Keyboard navigation support
326
+ - Screen reader friendly
327
+ - Error messages are properly announced
328
+
329
+ ## Demo
330
+
331
+ See the component in action at `/demo/components/date-range`
332
+
333
+ ## Related Components
334
+
335
+ - [JsonSchemaForm](./JsonSchemaForm.md) - For complex forms with date fields
336
+ - [Table](../data/Table.md) - Often used with date range filters
337
+ - [Chart](../data/Chart.md) - Commonly filtered by date ranges