@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.
- package/dist/composables/index.js +388 -0
- package/dist/composables/index.js.map +1 -0
- package/package.json +41 -0
- package/src/composables/index.ts +6 -0
- package/src/composables/useForm.test.ts +229 -0
- package/src/composables/useForm.ts +130 -0
- package/src/composables/useFormValidation.test.ts +189 -0
- package/src/composables/useFormValidation.ts +83 -0
- package/src/composables/useModal.property.test.ts +164 -0
- package/src/composables/useModal.ts +43 -0
- package/src/composables/useNotifications.test.ts +166 -0
- package/src/composables/useNotifications.ts +81 -0
- package/src/composables/useTable.property.test.ts +198 -0
- package/src/composables/useTable.ts +134 -0
- package/src/composables/useTabs.property.test.ts +247 -0
- package/src/composables/useTabs.ts +101 -0
- package/src/data/Chart.demo.vue +340 -0
- package/src/data/Chart.md +525 -0
- package/src/data/Chart.vue +133 -0
- package/src/data/DataList.md +80 -0
- package/src/data/DataList.test.ts +69 -0
- package/src/data/DataList.vue +46 -0
- package/src/data/SearchableSelect.md +107 -0
- package/src/data/SearchableSelect.vue +124 -0
- package/src/data/Table.demo.vue +296 -0
- package/src/data/Table.md +588 -0
- package/src/data/Table.property.test.ts +548 -0
- package/src/data/Table.test.ts +562 -0
- package/src/data/Table.unit.test.ts +544 -0
- package/src/data/Table.vue +321 -0
- package/src/data/index.ts +5 -0
- package/src/domain/BrandCard.md +81 -0
- package/src/domain/BrandCard.vue +63 -0
- package/src/domain/BrandSelector.md +84 -0
- package/src/domain/BrandSelector.vue +65 -0
- package/src/domain/ProductBadge.md +60 -0
- package/src/domain/ProductBadge.vue +47 -0
- package/src/domain/UserAvatar.md +84 -0
- package/src/domain/UserAvatar.vue +60 -0
- package/src/domain/domain-components.property.test.ts +449 -0
- package/src/domain/index.ts +4 -0
- package/src/forms/DateRange.demo.vue +273 -0
- package/src/forms/DateRange.md +337 -0
- package/src/forms/DateRange.vue +110 -0
- package/src/forms/JsonSchemaForm.demo.vue +549 -0
- package/src/forms/JsonSchemaForm.md +112 -0
- package/src/forms/JsonSchemaForm.property.test.ts +817 -0
- package/src/forms/JsonSchemaForm.test.ts +601 -0
- package/src/forms/JsonSchemaForm.unit.test.ts +801 -0
- package/src/forms/JsonSchemaForm.vue +615 -0
- package/src/forms/index.ts +3 -0
- package/src/index.ts +17 -0
- package/src/navigation/Breadcrumbs.demo.vue +142 -0
- package/src/navigation/Breadcrumbs.md +102 -0
- package/src/navigation/Breadcrumbs.test.ts +69 -0
- package/src/navigation/Breadcrumbs.vue +58 -0
- package/src/navigation/Stepper.demo.vue +337 -0
- package/src/navigation/Stepper.md +174 -0
- package/src/navigation/Stepper.vue +146 -0
- package/src/navigation/Tabs.demo.vue +293 -0
- package/src/navigation/Tabs.md +163 -0
- package/src/navigation/Tabs.test.ts +176 -0
- package/src/navigation/Tabs.vue +104 -0
- package/src/navigation/index.ts +5 -0
- package/src/overlays/Alert.demo.vue +377 -0
- package/src/overlays/Alert.md +248 -0
- package/src/overlays/Alert.test.ts +166 -0
- package/src/overlays/Alert.vue +70 -0
- package/src/overlays/Drawer.md +140 -0
- package/src/overlays/Drawer.test.ts +92 -0
- package/src/overlays/Drawer.vue +76 -0
- package/src/overlays/Modal.demo.vue +149 -0
- package/src/overlays/Modal.md +385 -0
- package/src/overlays/Modal.test.ts +128 -0
- package/src/overlays/Modal.vue +86 -0
- package/src/overlays/Notification.md +150 -0
- package/src/overlays/Notification.test.ts +96 -0
- package/src/overlays/Notification.vue +58 -0
- 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
|