@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,248 @@
|
|
|
1
|
+
# Alert Component
|
|
2
|
+
|
|
3
|
+
A stateful Vue wrapper around `@hotelinking/ui`'s `uiAlert` component. Provides visual alerts for information, success, warnings, and errors with optional actions.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Four alert types**: info, success, warning, danger
|
|
8
|
+
- **v-model support**: Two-way binding for show/hide state
|
|
9
|
+
- **Action buttons**: Optional clickable actions with custom events
|
|
10
|
+
- **Loading state**: Skeleton loading indicator
|
|
11
|
+
- **Rich content**: Slot support for custom HTML content
|
|
12
|
+
- **Exposed methods**: Programmatic show/hide/toggle control
|
|
13
|
+
|
|
14
|
+
## Import
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { Alert } from '@htlkg/components';
|
|
18
|
+
// or
|
|
19
|
+
import { Alert } from '@htlkg/components/overlays';
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Props
|
|
23
|
+
|
|
24
|
+
| Prop | Type | Default | Description |
|
|
25
|
+
|------|------|---------|-------------|
|
|
26
|
+
| `title` | `string` | required | Alert title |
|
|
27
|
+
| `type` | `'info' \| 'success' \| 'warning' \| 'danger'` | `'info'` | Alert type (affects colors and icon) |
|
|
28
|
+
| `actions` | `Array<{name: string, event: string}>` | `[]` | Action buttons to display |
|
|
29
|
+
| `loading` | `boolean` | `false` | Show loading skeleton |
|
|
30
|
+
| `show` | `boolean` | `true` | Control visibility (v-model) |
|
|
31
|
+
|
|
32
|
+
## Events
|
|
33
|
+
|
|
34
|
+
| Event | Payload | Description |
|
|
35
|
+
|-------|---------|-------------|
|
|
36
|
+
| `update:show` | `boolean` | Emitted when visibility changes (v-model) |
|
|
37
|
+
| `alertEvent` | `string` | Emitted when action button is clicked (event value) |
|
|
38
|
+
| `close` | - | Emitted when alert is hidden |
|
|
39
|
+
|
|
40
|
+
## Slots
|
|
41
|
+
|
|
42
|
+
| Slot | Description |
|
|
43
|
+
|------|-------------|
|
|
44
|
+
| `default` | Alert content (supports HTML) |
|
|
45
|
+
|
|
46
|
+
## Exposed Methods
|
|
47
|
+
|
|
48
|
+
| Method | Description |
|
|
49
|
+
|--------|-------------|
|
|
50
|
+
| `show()` | Show the alert |
|
|
51
|
+
| `hide()` | Hide the alert |
|
|
52
|
+
| `toggle()` | Toggle alert visibility |
|
|
53
|
+
|
|
54
|
+
## Usage Examples
|
|
55
|
+
|
|
56
|
+
### Basic Info Alert
|
|
57
|
+
|
|
58
|
+
```vue
|
|
59
|
+
<script setup>
|
|
60
|
+
import { ref } from 'vue';
|
|
61
|
+
import { Alert } from '@htlkg/components';
|
|
62
|
+
|
|
63
|
+
const showAlert = ref(true);
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<template>
|
|
67
|
+
<Alert
|
|
68
|
+
v-model:show="showAlert"
|
|
69
|
+
title="System Update"
|
|
70
|
+
type="info"
|
|
71
|
+
>
|
|
72
|
+
We've updated our Design System to version 1.0.
|
|
73
|
+
</Alert>
|
|
74
|
+
</template>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Success Alert
|
|
78
|
+
|
|
79
|
+
```vue
|
|
80
|
+
<template>
|
|
81
|
+
<Alert
|
|
82
|
+
title="Operation Completed"
|
|
83
|
+
type="success"
|
|
84
|
+
>
|
|
85
|
+
The reservation has been created successfully.
|
|
86
|
+
</Alert>
|
|
87
|
+
</template>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Warning Alert with Actions
|
|
91
|
+
|
|
92
|
+
```vue
|
|
93
|
+
<script setup>
|
|
94
|
+
import { ref } from 'vue';
|
|
95
|
+
import { Alert } from '@htlkg/components';
|
|
96
|
+
|
|
97
|
+
const showWarning = ref(true);
|
|
98
|
+
|
|
99
|
+
const actions = [
|
|
100
|
+
{ name: 'Confirm', event: 'confirm' },
|
|
101
|
+
{ name: 'Cancel', event: 'cancel' }
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const handleAction = (event) => {
|
|
105
|
+
if (event === 'confirm') {
|
|
106
|
+
// Proceed with action
|
|
107
|
+
showWarning.value = false;
|
|
108
|
+
} else if (event === 'cancel') {
|
|
109
|
+
showWarning.value = false;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
</script>
|
|
113
|
+
|
|
114
|
+
<template>
|
|
115
|
+
<Alert
|
|
116
|
+
v-model:show="showWarning"
|
|
117
|
+
title="Confirm Deletion"
|
|
118
|
+
type="warning"
|
|
119
|
+
:actions="actions"
|
|
120
|
+
@alertEvent="handleAction"
|
|
121
|
+
>
|
|
122
|
+
This action cannot be undone. Are you sure?
|
|
123
|
+
</Alert>
|
|
124
|
+
</template>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Danger Alert
|
|
128
|
+
|
|
129
|
+
```vue
|
|
130
|
+
<template>
|
|
131
|
+
<Alert
|
|
132
|
+
title="Error Processing Request"
|
|
133
|
+
type="danger"
|
|
134
|
+
>
|
|
135
|
+
Could not connect to the server. Please try again later.
|
|
136
|
+
</Alert>
|
|
137
|
+
</template>
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Loading State
|
|
141
|
+
|
|
142
|
+
```vue
|
|
143
|
+
<script setup>
|
|
144
|
+
import { ref, onMounted } from 'vue';
|
|
145
|
+
import { Alert } from '@htlkg/components';
|
|
146
|
+
|
|
147
|
+
const isLoading = ref(true);
|
|
148
|
+
|
|
149
|
+
onMounted(async () => {
|
|
150
|
+
await fetchData();
|
|
151
|
+
isLoading.value = false;
|
|
152
|
+
});
|
|
153
|
+
</script>
|
|
154
|
+
|
|
155
|
+
<template>
|
|
156
|
+
<Alert
|
|
157
|
+
title="Loading Information..."
|
|
158
|
+
type="info"
|
|
159
|
+
:loading="isLoading"
|
|
160
|
+
>
|
|
161
|
+
Preparing system update
|
|
162
|
+
</Alert>
|
|
163
|
+
</template>
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Rich Content with HTML
|
|
167
|
+
|
|
168
|
+
```vue
|
|
169
|
+
<template>
|
|
170
|
+
<Alert
|
|
171
|
+
title="New Features Available"
|
|
172
|
+
type="success"
|
|
173
|
+
:actions="[
|
|
174
|
+
{ name: 'Explore', event: 'explore' },
|
|
175
|
+
{ name: 'Later', event: 'later' }
|
|
176
|
+
]"
|
|
177
|
+
>
|
|
178
|
+
<p class="mb-2">
|
|
179
|
+
<strong>Update 1.0:</strong> We've added new features:
|
|
180
|
+
</p>
|
|
181
|
+
<ul class="list-disc list-inside space-y-1">
|
|
182
|
+
<li>Improved pricing calendar</li>
|
|
183
|
+
<li>New notification system</li>
|
|
184
|
+
<li>Performance optimization</li>
|
|
185
|
+
</ul>
|
|
186
|
+
</Alert>
|
|
187
|
+
</template>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Programmatic Control
|
|
191
|
+
|
|
192
|
+
```vue
|
|
193
|
+
<script setup>
|
|
194
|
+
import { ref } from 'vue';
|
|
195
|
+
import { Alert } from '@htlkg/components';
|
|
196
|
+
|
|
197
|
+
const alertRef = ref();
|
|
198
|
+
|
|
199
|
+
const showAlert = () => alertRef.value?.show();
|
|
200
|
+
const hideAlert = () => alertRef.value?.hide();
|
|
201
|
+
const toggleAlert = () => alertRef.value?.toggle();
|
|
202
|
+
</script>
|
|
203
|
+
|
|
204
|
+
<template>
|
|
205
|
+
<div>
|
|
206
|
+
<button @click="showAlert">Show</button>
|
|
207
|
+
<button @click="hideAlert">Hide</button>
|
|
208
|
+
<button @click="toggleAlert">Toggle</button>
|
|
209
|
+
|
|
210
|
+
<Alert
|
|
211
|
+
ref="alertRef"
|
|
212
|
+
title="Controlled Alert"
|
|
213
|
+
type="info"
|
|
214
|
+
>
|
|
215
|
+
This alert is controlled programmatically.
|
|
216
|
+
</Alert>
|
|
217
|
+
</div>
|
|
218
|
+
</template>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Alert Types
|
|
222
|
+
|
|
223
|
+
| Type | Color Scheme | Icon | Use Case |
|
|
224
|
+
|------|--------------|------|----------|
|
|
225
|
+
| `info` | Lime | ℹ️ Information | General information, updates |
|
|
226
|
+
| `success` | Green | ✅ Check | Successful operations, confirmations |
|
|
227
|
+
| `warning` | Yellow | ⚠️ Warning | Important warnings, confirmations needed |
|
|
228
|
+
| `danger` | Red | ❌ Error | Errors, critical issues |
|
|
229
|
+
|
|
230
|
+
## Design System Integration
|
|
231
|
+
|
|
232
|
+
This component wraps `@hotelinking/ui`'s `uiAlert` component, providing:
|
|
233
|
+
|
|
234
|
+
- Reactive state management with Vue 3 Composition API
|
|
235
|
+
- v-model support for show/hide state
|
|
236
|
+
- Exposed methods for programmatic control
|
|
237
|
+
- Event handling for action buttons
|
|
238
|
+
- Consistent API with other overlay components (Modal, Notification, Drawer)
|
|
239
|
+
|
|
240
|
+
## Related Components
|
|
241
|
+
|
|
242
|
+
- **Notification**: For toast-style notifications
|
|
243
|
+
- **Modal**: For dialog overlays
|
|
244
|
+
- **Drawer**: For side panel overlays
|
|
245
|
+
|
|
246
|
+
## Demo
|
|
247
|
+
|
|
248
|
+
See the [Alert demo page](/components/alert) for interactive examples.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import Alert from './Alert.vue';
|
|
4
|
+
|
|
5
|
+
describe('Alert Component', () => {
|
|
6
|
+
it('renders with basic props', () => {
|
|
7
|
+
const wrapper = mount(Alert, {
|
|
8
|
+
props: {
|
|
9
|
+
title: 'Test Alert',
|
|
10
|
+
type: 'info'
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
expect(wrapper.exists()).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('renders different alert types', () => {
|
|
18
|
+
const types = ['info', 'success', 'warning', 'danger'] as const;
|
|
19
|
+
|
|
20
|
+
types.forEach(type => {
|
|
21
|
+
const wrapper = mount(Alert, {
|
|
22
|
+
props: {
|
|
23
|
+
title: 'Test Alert',
|
|
24
|
+
type
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(wrapper.exists()).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('renders with actions', () => {
|
|
33
|
+
const actions = [
|
|
34
|
+
{ name: 'Confirm', event: 'confirm' },
|
|
35
|
+
{ name: 'Cancel', event: 'cancel' }
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
const wrapper = mount(Alert, {
|
|
39
|
+
props: {
|
|
40
|
+
title: 'Test Alert',
|
|
41
|
+
type: 'warning',
|
|
42
|
+
actions
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
expect(wrapper.exists()).toBe(true);
|
|
47
|
+
expect(wrapper.props('actions')).toEqual(actions);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('emits alertEvent when action is triggered', async () => {
|
|
51
|
+
const wrapper = mount(Alert, {
|
|
52
|
+
props: {
|
|
53
|
+
title: 'Test Alert',
|
|
54
|
+
type: 'info',
|
|
55
|
+
actions: [{ name: 'OK', event: 'ok' }]
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const component = wrapper.vm as any;
|
|
60
|
+
if (component.handleAlertEvent) {
|
|
61
|
+
component.handleAlertEvent('ok');
|
|
62
|
+
expect(wrapper.emitted('alertEvent')).toBeTruthy();
|
|
63
|
+
expect(wrapper.emitted('alertEvent')?.[0]).toEqual(['ok']);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('renders with show prop', () => {
|
|
68
|
+
const wrapper = mount(Alert, {
|
|
69
|
+
props: {
|
|
70
|
+
title: 'Test Alert',
|
|
71
|
+
type: 'info',
|
|
72
|
+
show: true
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(wrapper.exists()).toBe(true);
|
|
77
|
+
expect(wrapper.props('show')).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('hides when show is false', () => {
|
|
81
|
+
const wrapper = mount(Alert, {
|
|
82
|
+
props: {
|
|
83
|
+
title: 'Test Alert',
|
|
84
|
+
type: 'info',
|
|
85
|
+
show: false
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
expect(wrapper.find('div').exists()).toBe(false);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('supports v-model for show state', async () => {
|
|
93
|
+
const wrapper = mount(Alert, {
|
|
94
|
+
props: {
|
|
95
|
+
title: 'Test Alert',
|
|
96
|
+
type: 'info',
|
|
97
|
+
show: false,
|
|
98
|
+
'onUpdate:show': (value: boolean) => wrapper.setProps({ show: value })
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const component = wrapper.vm as any;
|
|
103
|
+
component.show();
|
|
104
|
+
|
|
105
|
+
await wrapper.vm.$nextTick();
|
|
106
|
+
expect(wrapper.emitted('update:show')).toBeTruthy();
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('exposes show, hide, and toggle methods', () => {
|
|
110
|
+
const wrapper = mount(Alert, {
|
|
111
|
+
props: {
|
|
112
|
+
title: 'Test Alert',
|
|
113
|
+
type: 'info'
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const component = wrapper.vm as any;
|
|
118
|
+
expect(component.show).toBeDefined();
|
|
119
|
+
expect(component.hide).toBeDefined();
|
|
120
|
+
expect(component.toggle).toBeDefined();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('renders with loading state', () => {
|
|
124
|
+
const wrapper = mount(Alert, {
|
|
125
|
+
props: {
|
|
126
|
+
title: 'Loading Alert',
|
|
127
|
+
type: 'info',
|
|
128
|
+
loading: true
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(wrapper.exists()).toBe(true);
|
|
133
|
+
expect(wrapper.props('loading')).toBe(true);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('renders slot content', () => {
|
|
137
|
+
const wrapper = mount(Alert, {
|
|
138
|
+
props: {
|
|
139
|
+
title: 'Test Alert',
|
|
140
|
+
type: 'info'
|
|
141
|
+
},
|
|
142
|
+
slots: {
|
|
143
|
+
default: '<p>Custom alert content</p>'
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
expect(wrapper.html()).toContain('Custom alert content');
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('emits close event when hidden', async () => {
|
|
151
|
+
const wrapper = mount(Alert, {
|
|
152
|
+
props: {
|
|
153
|
+
title: 'Test Alert',
|
|
154
|
+
type: 'info',
|
|
155
|
+
show: true
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const component = wrapper.vm as any;
|
|
160
|
+
component.hide();
|
|
161
|
+
|
|
162
|
+
await wrapper.vm.$nextTick();
|
|
163
|
+
expect(wrapper.emitted('close')).toBeTruthy();
|
|
164
|
+
expect(wrapper.emitted('update:show')).toBeTruthy();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import { uiAlert, type UiAlertInterface } from '@hotelinking/ui';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
title: string;
|
|
7
|
+
type?: UiAlertInterface['type'];
|
|
8
|
+
actions?: UiAlertInterface['actions'];
|
|
9
|
+
loading?: boolean;
|
|
10
|
+
show?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
14
|
+
type: 'info',
|
|
15
|
+
actions: () => [],
|
|
16
|
+
loading: false,
|
|
17
|
+
show: true
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const emit = defineEmits<{
|
|
21
|
+
'update:show': [value: boolean];
|
|
22
|
+
'alertEvent': [event: string];
|
|
23
|
+
'close': [];
|
|
24
|
+
}>();
|
|
25
|
+
|
|
26
|
+
// Internal state synced with v-model
|
|
27
|
+
const isVisible = computed({
|
|
28
|
+
get: () => props.show,
|
|
29
|
+
set: (value: boolean) => {
|
|
30
|
+
emit('update:show', value);
|
|
31
|
+
if (!value) {
|
|
32
|
+
emit('close');
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// Convert to uiAlert format
|
|
38
|
+
const alertConfig = computed<UiAlertInterface>(() => ({
|
|
39
|
+
title: props.title,
|
|
40
|
+
type: props.type,
|
|
41
|
+
actions: props.actions,
|
|
42
|
+
loading: props.loading
|
|
43
|
+
}));
|
|
44
|
+
|
|
45
|
+
// Handle alert action events from uiAlert
|
|
46
|
+
function handleAlertEvent(event: string) {
|
|
47
|
+
emit('alertEvent', event);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Expose methods for parent components
|
|
51
|
+
defineExpose({
|
|
52
|
+
show: () => { isVisible.value = true; },
|
|
53
|
+
hide: () => { isVisible.value = false; },
|
|
54
|
+
toggle: () => { isVisible.value = !isVisible.value; }
|
|
55
|
+
});
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<template>
|
|
59
|
+
<div v-if="isVisible">
|
|
60
|
+
<uiAlert
|
|
61
|
+
:title="alertConfig.title"
|
|
62
|
+
:type="alertConfig.type"
|
|
63
|
+
:actions="alertConfig.actions"
|
|
64
|
+
:loading="alertConfig.loading"
|
|
65
|
+
@alert-event="handleAlertEvent"
|
|
66
|
+
>
|
|
67
|
+
<slot />
|
|
68
|
+
</uiAlert>
|
|
69
|
+
</div>
|
|
70
|
+
</template>
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# Drawer Component
|
|
2
|
+
|
|
3
|
+
A side panel overlay component for displaying additional content without leaving the current page.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Four positions**: Left, right, top, bottom
|
|
8
|
+
- **v-model support**: Two-way binding for open/close state
|
|
9
|
+
- **Overlay backdrop**: Darkens background when open
|
|
10
|
+
- **Exposed methods**: Programmatic open/close/toggle control
|
|
11
|
+
- **Responsive**: Adapts to mobile screens
|
|
12
|
+
|
|
13
|
+
## Import
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { Drawer } from '@htlkg/components';
|
|
17
|
+
// or
|
|
18
|
+
import { Drawer } from '@htlkg/components/overlays';
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Props
|
|
22
|
+
|
|
23
|
+
| Prop | Type | Default | Description |
|
|
24
|
+
|------|------|---------|-------------|
|
|
25
|
+
| `open` | `boolean` | `false` | Control visibility (v-model) |
|
|
26
|
+
| `position` | `'left' \| 'right' \| 'top' \| 'bottom'` | `'right'` | Drawer position |
|
|
27
|
+
| `title` | `string` | `''` | Drawer title |
|
|
28
|
+
|
|
29
|
+
## Events
|
|
30
|
+
|
|
31
|
+
| Event | Payload | Description |
|
|
32
|
+
|-------|---------|-------------|
|
|
33
|
+
| `update:open` | `boolean` | Emitted when visibility changes |
|
|
34
|
+
| `close` | - | Emitted when drawer is closed |
|
|
35
|
+
|
|
36
|
+
## Slots
|
|
37
|
+
|
|
38
|
+
| Slot | Description |
|
|
39
|
+
|------|-------------|
|
|
40
|
+
| `default` | Drawer content |
|
|
41
|
+
|
|
42
|
+
## Exposed Methods
|
|
43
|
+
|
|
44
|
+
| Method | Description |
|
|
45
|
+
|--------|-------------|
|
|
46
|
+
| `open()` | Open the drawer |
|
|
47
|
+
| `close()` | Close the drawer |
|
|
48
|
+
| `toggle()` | Toggle drawer visibility |
|
|
49
|
+
|
|
50
|
+
## Usage Examples
|
|
51
|
+
|
|
52
|
+
### Basic Drawer
|
|
53
|
+
|
|
54
|
+
```vue
|
|
55
|
+
<script setup>
|
|
56
|
+
import { ref } from 'vue';
|
|
57
|
+
import { Drawer } from '@htlkg/components';
|
|
58
|
+
|
|
59
|
+
const isOpen = ref(false);
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<template>
|
|
63
|
+
<button @click="isOpen = true">Open Drawer</button>
|
|
64
|
+
|
|
65
|
+
<Drawer
|
|
66
|
+
v-model:open="isOpen"
|
|
67
|
+
title="Settings"
|
|
68
|
+
>
|
|
69
|
+
<div class="space-y-4">
|
|
70
|
+
<h3>User Settings</h3>
|
|
71
|
+
<p>Configure your preferences here.</p>
|
|
72
|
+
</div>
|
|
73
|
+
</Drawer>
|
|
74
|
+
</template>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Left Drawer
|
|
78
|
+
|
|
79
|
+
```vue
|
|
80
|
+
<template>
|
|
81
|
+
<Drawer
|
|
82
|
+
v-model:open="isOpen"
|
|
83
|
+
position="left"
|
|
84
|
+
title="Navigation"
|
|
85
|
+
>
|
|
86
|
+
<nav>
|
|
87
|
+
<ul>
|
|
88
|
+
<li><a href="/dashboard">Dashboard</a></li>
|
|
89
|
+
<li><a href="/users">Users</a></li>
|
|
90
|
+
<li><a href="/settings">Settings</a></li>
|
|
91
|
+
</ul>
|
|
92
|
+
</nav>
|
|
93
|
+
</Drawer>
|
|
94
|
+
</template>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Filter Drawer
|
|
98
|
+
|
|
99
|
+
```vue
|
|
100
|
+
<script setup>
|
|
101
|
+
import { ref } from 'vue';
|
|
102
|
+
import { Drawer } from '@htlkg/components';
|
|
103
|
+
|
|
104
|
+
const showFilters = ref(false);
|
|
105
|
+
const filters = ref({
|
|
106
|
+
status: '',
|
|
107
|
+
dateRange: ''
|
|
108
|
+
});
|
|
109
|
+
</script>
|
|
110
|
+
|
|
111
|
+
<template>
|
|
112
|
+
<button @click="showFilters = true">Filters</button>
|
|
113
|
+
|
|
114
|
+
<Drawer
|
|
115
|
+
v-model:open="showFilters"
|
|
116
|
+
title="Filter Options"
|
|
117
|
+
position="right"
|
|
118
|
+
>
|
|
119
|
+
<form class="space-y-4">
|
|
120
|
+
<div>
|
|
121
|
+
<label>Status</label>
|
|
122
|
+
<select v-model="filters.status">
|
|
123
|
+
<option value="">All</option>
|
|
124
|
+
<option value="active">Active</option>
|
|
125
|
+
<option value="inactive">Inactive</option>
|
|
126
|
+
</select>
|
|
127
|
+
</div>
|
|
128
|
+
<div>
|
|
129
|
+
<label>Date Range</label>
|
|
130
|
+
<input v-model="filters.dateRange" type="date" />
|
|
131
|
+
</div>
|
|
132
|
+
<button type="submit">Apply Filters</button>
|
|
133
|
+
</form>
|
|
134
|
+
</Drawer>
|
|
135
|
+
</template>
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Demo
|
|
139
|
+
|
|
140
|
+
See the [Drawer demo page](/components/drawer) for interactive examples.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
import Drawer from './Drawer.vue';
|
|
4
|
+
|
|
5
|
+
describe('Drawer Component', () => {
|
|
6
|
+
it('renders with basic props', () => {
|
|
7
|
+
const wrapper = mount(Drawer, {
|
|
8
|
+
props: {
|
|
9
|
+
open: true,
|
|
10
|
+
title: 'Test Drawer'
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
expect(wrapper.exists()).toBe(true);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('supports v-model for open state', async () => {
|
|
18
|
+
const wrapper = mount(Drawer, {
|
|
19
|
+
props: {
|
|
20
|
+
open: false,
|
|
21
|
+
'onUpdate:open': (value: boolean) => wrapper.setProps({ open: value })
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
expect(wrapper.props('open')).toBe(false);
|
|
26
|
+
|
|
27
|
+
const component = wrapper.vm as any;
|
|
28
|
+
if (component.open) {
|
|
29
|
+
component.open();
|
|
30
|
+
await wrapper.vm.$nextTick();
|
|
31
|
+
expect(wrapper.emitted('update:open')).toBeTruthy();
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('emits close event when drawer is closed', async () => {
|
|
36
|
+
const wrapper = mount(Drawer, {
|
|
37
|
+
props: {
|
|
38
|
+
open: true
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const component = wrapper.vm as any;
|
|
43
|
+
if (component.close) {
|
|
44
|
+
component.close();
|
|
45
|
+
await wrapper.vm.$nextTick();
|
|
46
|
+
expect(wrapper.emitted('update:open')).toBeTruthy();
|
|
47
|
+
expect(wrapper.emitted('close')).toBeTruthy();
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('exposes open and close methods', () => {
|
|
52
|
+
const wrapper = mount(Drawer, {
|
|
53
|
+
props: {
|
|
54
|
+
open: false
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const component = wrapper.vm as any;
|
|
59
|
+
if (component.open && component.close) {
|
|
60
|
+
expect(typeof component.open).toBe('function');
|
|
61
|
+
expect(typeof component.close).toBe('function');
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('renders with different positions', () => {
|
|
66
|
+
const positions = ['left', 'right', 'top', 'bottom'];
|
|
67
|
+
|
|
68
|
+
positions.forEach(position => {
|
|
69
|
+
const wrapper = mount(Drawer, {
|
|
70
|
+
props: {
|
|
71
|
+
open: true,
|
|
72
|
+
position: position as any
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(wrapper.exists()).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('renders slot content', () => {
|
|
81
|
+
const wrapper = mount(Drawer, {
|
|
82
|
+
props: {
|
|
83
|
+
open: true
|
|
84
|
+
},
|
|
85
|
+
slots: {
|
|
86
|
+
default: '<div class="drawer-content">Custom Content</div>'
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
expect(wrapper.html()).toContain('drawer-content');
|
|
91
|
+
});
|
|
92
|
+
});
|