@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,321 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, computed } from 'vue';
|
|
3
|
+
import {
|
|
4
|
+
type TableItemType,
|
|
5
|
+
type UiDropdownItemType,
|
|
6
|
+
type UiModalInterface,
|
|
7
|
+
type UiTableInterface,
|
|
8
|
+
uiTable
|
|
9
|
+
} from '@hotelinking/ui';
|
|
10
|
+
|
|
11
|
+
export interface Column {
|
|
12
|
+
name: string;
|
|
13
|
+
value: string;
|
|
14
|
+
tooltip?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
// Data
|
|
19
|
+
items: any[];
|
|
20
|
+
columns: Column[];
|
|
21
|
+
|
|
22
|
+
// State
|
|
23
|
+
loading?: boolean;
|
|
24
|
+
currentPage?: number;
|
|
25
|
+
totalPages?: number;
|
|
26
|
+
totalItems?: number;
|
|
27
|
+
pageSize?: number;
|
|
28
|
+
orderedBy?: string;
|
|
29
|
+
orderDirection?: 'asc' | 'desc';
|
|
30
|
+
|
|
31
|
+
// Selection
|
|
32
|
+
actions?: Array<{ name: string; id: string }>;
|
|
33
|
+
selectAllItemsModal?: UiModalInterface;
|
|
34
|
+
resetSelected?: boolean;
|
|
35
|
+
|
|
36
|
+
// Filters (now integrated in uiTable)
|
|
37
|
+
smartFilterCategories?: Array<{
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
componentType: 'uiInput' | 'uiSelect';
|
|
41
|
+
defaultProps: any;
|
|
42
|
+
}>;
|
|
43
|
+
filterLiterals?: {
|
|
44
|
+
filters: string;
|
|
45
|
+
contains: string;
|
|
46
|
+
is: string;
|
|
47
|
+
and: string;
|
|
48
|
+
or: string;
|
|
49
|
+
deleteAll: string;
|
|
50
|
+
filter: string;
|
|
51
|
+
};
|
|
52
|
+
filters?: {
|
|
53
|
+
logicOperator: string;
|
|
54
|
+
filters: Array<{
|
|
55
|
+
name: string;
|
|
56
|
+
label: string;
|
|
57
|
+
type: 'uiInput' | 'uiSelect';
|
|
58
|
+
value: string | undefined;
|
|
59
|
+
}>;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Additional features
|
|
63
|
+
tableActionsDropdown?: { items: UiDropdownItemType[]; loading?: boolean };
|
|
64
|
+
tableActionButtons?: Array<{
|
|
65
|
+
text: string;
|
|
66
|
+
id: string;
|
|
67
|
+
color?: string;
|
|
68
|
+
size?: string;
|
|
69
|
+
disabled?: boolean;
|
|
70
|
+
icon?: any;
|
|
71
|
+
loading?: boolean;
|
|
72
|
+
}>;
|
|
73
|
+
noResults?: {
|
|
74
|
+
title: string;
|
|
75
|
+
message: string;
|
|
76
|
+
actions?: Array<{ action: string; text: string }>;
|
|
77
|
+
items?: UiDropdownItemType[];
|
|
78
|
+
select?: UiDropdownItemType;
|
|
79
|
+
};
|
|
80
|
+
hiddenColumns?: number[];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
84
|
+
loading: false,
|
|
85
|
+
currentPage: 1,
|
|
86
|
+
totalPages: 1,
|
|
87
|
+
totalItems: 0,
|
|
88
|
+
pageSize: 10,
|
|
89
|
+
orderedBy: '',
|
|
90
|
+
orderDirection: 'asc',
|
|
91
|
+
resetSelected: false,
|
|
92
|
+
filterLiterals: () => ({
|
|
93
|
+
filters: 'Smart Filters',
|
|
94
|
+
contains: 'contains',
|
|
95
|
+
is: 'is',
|
|
96
|
+
and: 'and',
|
|
97
|
+
or: 'or',
|
|
98
|
+
deleteAll: 'Delete All',
|
|
99
|
+
filter: 'Filter'
|
|
100
|
+
})
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const emit = defineEmits<{
|
|
104
|
+
'update:currentPage': [page: number];
|
|
105
|
+
'update:pageSize': [size: number];
|
|
106
|
+
'update:selected': [items: any[]];
|
|
107
|
+
'update:orderedBy': [key: string];
|
|
108
|
+
'update:orderDirection': [direction: 'asc' | 'desc'];
|
|
109
|
+
'update:resetSelected': [value: boolean];
|
|
110
|
+
'table-action': [data: { action: string; items: Array<string | number> }];
|
|
111
|
+
'table-action-selected': [item: any];
|
|
112
|
+
'table-action-button-clicked': [data: { id: string; text: string }];
|
|
113
|
+
'smart-filters-sent': [filters: any];
|
|
114
|
+
'smart-filters-cleared': [];
|
|
115
|
+
'smart-filter-deleted': [index: number];
|
|
116
|
+
'custom-emit': [data: any];
|
|
117
|
+
'modal-action': [data: { modal: string; action: string }];
|
|
118
|
+
'select-all-items': [];
|
|
119
|
+
'deselect-all-items': [];
|
|
120
|
+
'no-results-action': [action: string];
|
|
121
|
+
'no-results-option-selected': [item: any];
|
|
122
|
+
'columns-visibility-changed': [data: { index: number; hidden: boolean }];
|
|
123
|
+
'order-by': [data: { value: string; orderDirection: 'asc' | 'desc' }];
|
|
124
|
+
'change-page': [page: number];
|
|
125
|
+
'change-page-size': [size: number | string];
|
|
126
|
+
}>();
|
|
127
|
+
|
|
128
|
+
// State management
|
|
129
|
+
const internalHiddenColumns = ref<number[]>(props.hiddenColumns || []);
|
|
130
|
+
const selectedItemIds = ref<Set<string | number>>(new Set());
|
|
131
|
+
|
|
132
|
+
// Convert columns to uiTable header format
|
|
133
|
+
const tableHeader = computed<UiTableInterface['header']>(() =>
|
|
134
|
+
props.columns.map(col => ({
|
|
135
|
+
name: col.name,
|
|
136
|
+
value: col.value,
|
|
137
|
+
tooltip: col.tooltip
|
|
138
|
+
}))
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Convert items to uiTable format
|
|
142
|
+
const tableItems = computed<TableItemType[]>(() =>
|
|
143
|
+
props.items.map((item, index) => ({
|
|
144
|
+
id: item.id || index,
|
|
145
|
+
disabled: item.disabled || false,
|
|
146
|
+
row: props.columns.map(col => {
|
|
147
|
+
const key = col.value || col.name.toLowerCase();
|
|
148
|
+
const value = item[key];
|
|
149
|
+
// Return the value as-is if it's already formatted for uiTable
|
|
150
|
+
if (typeof value === 'object' && value !== null && 'type' in value) {
|
|
151
|
+
return value;
|
|
152
|
+
}
|
|
153
|
+
return String(value ?? '');
|
|
154
|
+
})
|
|
155
|
+
}))
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Page size options for uiTable
|
|
159
|
+
const pageSizeOptions = computed(() => [
|
|
160
|
+
{ name: '10', value: '10', active: props.pageSize === 10 },
|
|
161
|
+
{ name: '25', value: '25', active: props.pageSize === 25 },
|
|
162
|
+
{ name: '50', value: '50', active: props.pageSize === 50 },
|
|
163
|
+
{ name: '100', value: '100', active: props.pageSize === 100 }
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
// Event handlers
|
|
167
|
+
function handleOrderBy(event: { value: string; orderDirection: 'asc' | 'desc' }) {
|
|
168
|
+
emit('update:orderedBy', event.value);
|
|
169
|
+
emit('update:orderDirection', event.orderDirection);
|
|
170
|
+
emit('order-by', event);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function handleChangePage(page: number) {
|
|
174
|
+
emit('update:currentPage', page);
|
|
175
|
+
emit('change-page', page);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function handleChangePageSize(size: number | string) {
|
|
179
|
+
const numSize = typeof size === 'string' ? Number(size) : size;
|
|
180
|
+
emit('update:pageSize', numSize);
|
|
181
|
+
emit('change-page-size', size);
|
|
182
|
+
emit('update:currentPage', 1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handleTableAction(data: { action: string; items: Array<string | number> }) {
|
|
186
|
+
selectedItemIds.value = new Set(data.items);
|
|
187
|
+
updateSelectedItems();
|
|
188
|
+
emit('table-action', data);
|
|
189
|
+
resetSelectedItems();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function handleTableActionSelected(item: any) {
|
|
193
|
+
emit('table-action-selected', item);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function handleTableActionButtonClicked(data: { id: string; text: string }) {
|
|
197
|
+
emit('table-action-button-clicked', data);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function handleSmartFiltersSent(filters: any) {
|
|
201
|
+
emit('smart-filters-sent', filters);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function handleSmartFiltersCleared() {
|
|
205
|
+
emit('smart-filters-cleared');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function handleSmartFilterDeleted(index: number) {
|
|
209
|
+
emit('smart-filter-deleted', index);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function handleCustomEmit(data: any) {
|
|
213
|
+
emit('custom-emit', data);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function handleColumnsVisibilityChanged(event: { index: number; hidden: boolean }) {
|
|
217
|
+
if (event.hidden) {
|
|
218
|
+
if (!internalHiddenColumns.value.includes(event.index)) {
|
|
219
|
+
internalHiddenColumns.value.push(event.index);
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
const index = internalHiddenColumns.value.indexOf(event.index);
|
|
223
|
+
if (index > -1) {
|
|
224
|
+
internalHiddenColumns.value.splice(index, 1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
emit('columns-visibility-changed', event);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function handleModalAction(data: { modal: string; action: string }) {
|
|
231
|
+
emit('modal-action', data);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function handleSelectAllItems() {
|
|
235
|
+
selectedItemIds.value = new Set(props.items.map(item => item.id));
|
|
236
|
+
updateSelectedItems();
|
|
237
|
+
emit('select-all-items');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function handleDeselectAllItems() {
|
|
241
|
+
selectedItemIds.value.clear();
|
|
242
|
+
updateSelectedItems();
|
|
243
|
+
emit('deselect-all-items');
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function handleNoResultsAction(action: string) {
|
|
247
|
+
emit('no-results-action', action);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function handleNoResultsOptionSelected(item: any) {
|
|
251
|
+
emit('no-results-option-selected', item);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function handleSelectedItemsDeleted() {
|
|
255
|
+
emit('update:resetSelected', false);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function updateSelectedItems() {
|
|
259
|
+
const selected = props.items.filter(item => selectedItemIds.value.has(item.id));
|
|
260
|
+
emit('update:selected', selected);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Expose methods for parent components
|
|
264
|
+
defineExpose({
|
|
265
|
+
clearSelection: () => {
|
|
266
|
+
emit('update:resetSelected', true);
|
|
267
|
+
selectedItemIds.value.clear();
|
|
268
|
+
updateSelectedItems();
|
|
269
|
+
},
|
|
270
|
+
getHiddenColumns: () => internalHiddenColumns.value,
|
|
271
|
+
getSelectedItems: () => Array.from(selectedItemIds.value)
|
|
272
|
+
});
|
|
273
|
+
</script>
|
|
274
|
+
|
|
275
|
+
<template>
|
|
276
|
+
<uiTable
|
|
277
|
+
:loading="loading"
|
|
278
|
+
:header="tableHeader"
|
|
279
|
+
:items="tableItems"
|
|
280
|
+
:ordered-by="orderedBy"
|
|
281
|
+
:order-direction="orderDirection"
|
|
282
|
+
:actions="(actions as any)"
|
|
283
|
+
:hidden-columns="internalHiddenColumns"
|
|
284
|
+
:reset-selected="resetSelected"
|
|
285
|
+
:select-all-items-modal="(selectAllItemsModal as any)"
|
|
286
|
+
:smart-filter-categories="smartFilterCategories"
|
|
287
|
+
:filter-literals="filterLiterals"
|
|
288
|
+
:filters="filters"
|
|
289
|
+
:table-actions-dropdown="tableActionsDropdown"
|
|
290
|
+
:table-action-buttons="tableActionButtons"
|
|
291
|
+
:pagination-current="currentPage"
|
|
292
|
+
:pagination-total="totalPages"
|
|
293
|
+
:pagination-total-items="totalItems"
|
|
294
|
+
:page-size-options="pageSizeOptions"
|
|
295
|
+
:current-page-size="pageSize"
|
|
296
|
+
:no-results="noResults"
|
|
297
|
+
@order-by="handleOrderBy"
|
|
298
|
+
@table-action="handleTableAction"
|
|
299
|
+
@table-action-selected="handleTableActionSelected"
|
|
300
|
+
@table-action-button-clicked="handleTableActionButtonClicked"
|
|
301
|
+
@smart-filters-sent="handleSmartFiltersSent"
|
|
302
|
+
@smart-filters-cleared="handleSmartFiltersCleared"
|
|
303
|
+
@smart-filter-deleted="handleSmartFilterDeleted"
|
|
304
|
+
@change-page="handleChangePage"
|
|
305
|
+
@change-page-size="handleChangePageSize"
|
|
306
|
+
@custom-emit="handleCustomEmit"
|
|
307
|
+
@columns-visibility-changed="handleColumnsVisibilityChanged"
|
|
308
|
+
@selected-items-deleted="handleSelectedItemsDeleted"
|
|
309
|
+
@modal-action="handleModalAction"
|
|
310
|
+
@select-all-items="handleSelectAllItems"
|
|
311
|
+
@deselect-all-items="handleDeselectAllItems"
|
|
312
|
+
@no-results-action="handleNoResultsAction"
|
|
313
|
+
@no-results-option-selected="handleNoResultsOptionSelected"
|
|
314
|
+
/>
|
|
315
|
+
</template>
|
|
316
|
+
|
|
317
|
+
<style scoped>
|
|
318
|
+
.table-component {
|
|
319
|
+
width: 100%;
|
|
320
|
+
}
|
|
321
|
+
</style>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { default as Table } from './Table.vue';
|
|
2
|
+
export { default as DataList } from './DataList.vue';
|
|
3
|
+
export { default as SearchableSelect } from './SearchableSelect.vue';
|
|
4
|
+
export { default as Chart } from './Chart.vue';
|
|
5
|
+
export type { ChartSeries, ChartOptions, ChartDateRange, ChartAnnotation, NpsLiterals } from './Chart.vue';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# BrandCard Component
|
|
2
|
+
|
|
3
|
+
A card component for displaying brand information with status indicators. Wraps `@hotelinking/ui`'s `uiCard` component.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Status indicators**: Visual tags for brand status
|
|
8
|
+
- **Logo display**: Brand logo support
|
|
9
|
+
- **Click handling**: Emits click events
|
|
10
|
+
- **Loading state**: Skeleton loading indicator
|
|
11
|
+
|
|
12
|
+
## Import
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { BrandCard } from '@htlkg/components';
|
|
16
|
+
// or
|
|
17
|
+
import { BrandCard } from '@htlkg/components/domain';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
|------|------|---------|-------------|
|
|
24
|
+
| `brand` | `Brand` | required | Brand information |
|
|
25
|
+
| `showStatus` | `boolean` | `true` | Display status tag |
|
|
26
|
+
| `loading` | `boolean` | `false` | Show loading state |
|
|
27
|
+
|
|
28
|
+
### Brand Interface
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
interface Brand {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
logo?: string;
|
|
35
|
+
status?: 'active' | 'inactive' | 'maintenance' | 'suspended';
|
|
36
|
+
timezone?: string;
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Events
|
|
41
|
+
|
|
42
|
+
| Event | Payload | Description |
|
|
43
|
+
|-------|---------|-------------|
|
|
44
|
+
| `click` | `Brand` | Brand card clicked |
|
|
45
|
+
|
|
46
|
+
## Exposed Methods
|
|
47
|
+
|
|
48
|
+
| Method | Description |
|
|
49
|
+
|--------|-------------|
|
|
50
|
+
| `getBrand()` | Get brand object |
|
|
51
|
+
|
|
52
|
+
## Usage Examples
|
|
53
|
+
|
|
54
|
+
```vue
|
|
55
|
+
<script setup>
|
|
56
|
+
import { BrandCard } from '@htlkg/components';
|
|
57
|
+
|
|
58
|
+
const brand = {
|
|
59
|
+
id: '1',
|
|
60
|
+
name: 'Hotel Paradise',
|
|
61
|
+
logo: 'https://example.com/logo.png',
|
|
62
|
+
status: 'active',
|
|
63
|
+
timezone: 'America/New_York'
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const handleClick = (brand) => {
|
|
67
|
+
console.log('Selected brand:', brand);
|
|
68
|
+
};
|
|
69
|
+
</script>
|
|
70
|
+
|
|
71
|
+
<template>
|
|
72
|
+
<BrandCard
|
|
73
|
+
:brand="brand"
|
|
74
|
+
@click="handleClick"
|
|
75
|
+
/>
|
|
76
|
+
</template>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Demo
|
|
80
|
+
|
|
81
|
+
See the [BrandCard demo page](/components/brand-card) for interactive examples.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import { uiCard } from '@hotelinking/ui';
|
|
4
|
+
|
|
5
|
+
interface Brand {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
logo?: string;
|
|
9
|
+
status?: 'active' | 'inactive' | 'maintenance' | 'suspended';
|
|
10
|
+
timezone?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
brand: Brand;
|
|
15
|
+
showStatus?: boolean;
|
|
16
|
+
loading?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
20
|
+
showStatus: true,
|
|
21
|
+
loading: false
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const emit = defineEmits<{
|
|
25
|
+
'click': [brand: Brand];
|
|
26
|
+
}>();
|
|
27
|
+
|
|
28
|
+
// Convert status to tag format
|
|
29
|
+
const tags = computed(() => {
|
|
30
|
+
if (!props.showStatus || !props.brand.status) return [];
|
|
31
|
+
|
|
32
|
+
const statusMap = {
|
|
33
|
+
active: { name: 'Active', color: 'green' as const },
|
|
34
|
+
inactive: { name: 'Inactive', color: 'gray' as const },
|
|
35
|
+
maintenance: { name: 'Maintenance', color: 'yellow' as const },
|
|
36
|
+
suspended: { name: 'Suspended', color: 'red' as const }
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
return [statusMap[props.brand.status]];
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Handle card selection
|
|
43
|
+
function handleCardSelected(data: any) {
|
|
44
|
+
emit('click', props.brand);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Expose methods for parent components
|
|
48
|
+
defineExpose({
|
|
49
|
+
getBrand: () => props.brand
|
|
50
|
+
});
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<template>
|
|
54
|
+
<uiCard
|
|
55
|
+
:id="brand.id"
|
|
56
|
+
:name="brand.name"
|
|
57
|
+
:type="brand.timezone || 'Brand'"
|
|
58
|
+
:logo="brand.logo || ''"
|
|
59
|
+
:tags="tags"
|
|
60
|
+
:loading="loading"
|
|
61
|
+
@card-selected="handleCardSelected"
|
|
62
|
+
/>
|
|
63
|
+
</template>
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# BrandSelector Component
|
|
2
|
+
|
|
3
|
+
A grid-based brand selection component with v-model support.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **v-model support**: Two-way binding for selected brand
|
|
8
|
+
- **Grid layout**: Responsive grid display
|
|
9
|
+
- **Loading state**: Loading indicator
|
|
10
|
+
- **Exposed methods**: Programmatic selection control
|
|
11
|
+
|
|
12
|
+
## Import
|
|
13
|
+
|
|
14
|
+
```typescript
|
|
15
|
+
import { BrandSelector } from '@htlkg/components';
|
|
16
|
+
// or
|
|
17
|
+
import { BrandSelector } from '@htlkg/components/domain';
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Props
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Description |
|
|
23
|
+
|------|------|---------|-------------|
|
|
24
|
+
| `modelValue` | `Brand \| null` | `null` | Selected brand (v-model) |
|
|
25
|
+
| `brands` | `Brand[]` | `[]` | Available brands |
|
|
26
|
+
| `loading` | `boolean` | `false` | Show loading state |
|
|
27
|
+
|
|
28
|
+
### Brand Interface
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
interface Brand {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
logo?: string;
|
|
35
|
+
type?: string;
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Events
|
|
40
|
+
|
|
41
|
+
| Event | Payload | Description |
|
|
42
|
+
|-------|---------|-------------|
|
|
43
|
+
| `update:modelValue` | `Brand \| null` | Selection changed |
|
|
44
|
+
| `change` | `Brand \| null` | Selection changed |
|
|
45
|
+
|
|
46
|
+
## Exposed Methods
|
|
47
|
+
|
|
48
|
+
| Method | Description |
|
|
49
|
+
|--------|-------------|
|
|
50
|
+
| `getSelected()` | Get selected brand |
|
|
51
|
+
| `clearSelection()` | Clear selection |
|
|
52
|
+
|
|
53
|
+
## Usage Examples
|
|
54
|
+
|
|
55
|
+
```vue
|
|
56
|
+
<script setup>
|
|
57
|
+
import { ref } from 'vue';
|
|
58
|
+
import { BrandSelector } from '@htlkg/components';
|
|
59
|
+
|
|
60
|
+
const selectedBrand = ref(null);
|
|
61
|
+
|
|
62
|
+
const brands = [
|
|
63
|
+
{ id: '1', name: 'Hotel Paradise', logo: '/logo1.png' },
|
|
64
|
+
{ id: '2', name: 'Resort Oasis', logo: '/logo2.png' },
|
|
65
|
+
{ id: '3', name: 'City Inn', logo: '/logo3.png' }
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
const handleChange = (brand) => {
|
|
69
|
+
console.log('Selected:', brand);
|
|
70
|
+
};
|
|
71
|
+
</script>
|
|
72
|
+
|
|
73
|
+
<template>
|
|
74
|
+
<BrandSelector
|
|
75
|
+
v-model="selectedBrand"
|
|
76
|
+
:brands="brands"
|
|
77
|
+
@change="handleChange"
|
|
78
|
+
/>
|
|
79
|
+
</template>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Demo
|
|
83
|
+
|
|
84
|
+
See the [BrandSelector demo page](/components/brand-selector) for interactive examples.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import { uiCard } from '@hotelinking/ui';
|
|
4
|
+
|
|
5
|
+
interface Brand {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
logo?: string;
|
|
9
|
+
type?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
modelValue?: Brand | null;
|
|
14
|
+
brands?: Brand[];
|
|
15
|
+
loading?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
19
|
+
modelValue: null,
|
|
20
|
+
brands: () => [],
|
|
21
|
+
loading: false
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const emit = defineEmits<{
|
|
25
|
+
'update:modelValue': [value: Brand | null];
|
|
26
|
+
'change': [value: Brand | null];
|
|
27
|
+
}>();
|
|
28
|
+
|
|
29
|
+
function selectBrand(data: any) {
|
|
30
|
+
const brand = props.brands.find(b => b.id === data.id);
|
|
31
|
+
if (brand) {
|
|
32
|
+
emit('update:modelValue', brand);
|
|
33
|
+
emit('change', brand);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Expose methods for parent components
|
|
38
|
+
defineExpose({
|
|
39
|
+
getSelected: () => props.modelValue,
|
|
40
|
+
clearSelection: () => {
|
|
41
|
+
emit('update:modelValue', null);
|
|
42
|
+
emit('change', null);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<div class="w-full">
|
|
49
|
+
<div v-if="loading" class="text-center py-4 text-gray-500">
|
|
50
|
+
Loading brands...
|
|
51
|
+
</div>
|
|
52
|
+
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
53
|
+
<uiCard
|
|
54
|
+
v-for="brand in brands"
|
|
55
|
+
:key="brand.id"
|
|
56
|
+
:id="brand.id"
|
|
57
|
+
:name="brand.name"
|
|
58
|
+
:type="brand.type || 'Brand'"
|
|
59
|
+
:logo="brand.logo || ''"
|
|
60
|
+
:loading="false"
|
|
61
|
+
@card-selected="selectBrand"
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</template>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# ProductBadge Component
|
|
2
|
+
|
|
3
|
+
A badge component for displaying product information with status indicators. Wraps `@hotelinking/ui`'s `uiTag` component.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Status colors**: Visual indicators for enabled/disabled/pending states
|
|
8
|
+
- **Version display**: Optional version number
|
|
9
|
+
- **Loading state**: Skeleton loading indicator
|
|
10
|
+
|
|
11
|
+
## Import
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { ProductBadge } from '@htlkg/components';
|
|
15
|
+
// or
|
|
16
|
+
import { ProductBadge } from '@htlkg/components/domain';
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Props
|
|
20
|
+
|
|
21
|
+
| Prop | Type | Default | Description |
|
|
22
|
+
|------|------|---------|-------------|
|
|
23
|
+
| `product` | `Product` | required | Product information |
|
|
24
|
+
| `showVersion` | `boolean` | `false` | Display version number |
|
|
25
|
+
| `loading` | `boolean` | `false` | Show loading state |
|
|
26
|
+
|
|
27
|
+
### Product Interface
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
interface Product {
|
|
31
|
+
name: string;
|
|
32
|
+
status?: 'enabled' | 'disabled' | 'pending';
|
|
33
|
+
version?: string;
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Usage Examples
|
|
38
|
+
|
|
39
|
+
```vue
|
|
40
|
+
<script setup>
|
|
41
|
+
import { ProductBadge } from '@htlkg/components';
|
|
42
|
+
|
|
43
|
+
const product = {
|
|
44
|
+
name: 'Booking Engine',
|
|
45
|
+
status: 'enabled',
|
|
46
|
+
version: '2.1.0'
|
|
47
|
+
};
|
|
48
|
+
</script>
|
|
49
|
+
|
|
50
|
+
<template>
|
|
51
|
+
<ProductBadge
|
|
52
|
+
:product="product"
|
|
53
|
+
:showVersion="true"
|
|
54
|
+
/>
|
|
55
|
+
</template>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Demo
|
|
59
|
+
|
|
60
|
+
See the [ProductBadge demo page](/components/product-badge) for interactive examples.
|