@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,76 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
open?: boolean;
|
|
6
|
+
position?: 'left' | 'right' | 'top' | 'bottom';
|
|
7
|
+
title?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
11
|
+
open: false,
|
|
12
|
+
position: 'right',
|
|
13
|
+
title: ''
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const emit = defineEmits<{
|
|
17
|
+
'update:open': [value: boolean];
|
|
18
|
+
'close': [];
|
|
19
|
+
}>();
|
|
20
|
+
|
|
21
|
+
const isOpen = computed({
|
|
22
|
+
get: () => props.open,
|
|
23
|
+
set: (value: boolean) => {
|
|
24
|
+
emit('update:open', value);
|
|
25
|
+
if (!value) emit('close');
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function close() {
|
|
30
|
+
isOpen.value = false;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Expose methods for parent components
|
|
34
|
+
defineExpose({
|
|
35
|
+
open: () => { isOpen.value = true; },
|
|
36
|
+
close: () => { isOpen.value = false; },
|
|
37
|
+
toggle: () => { isOpen.value = !isOpen.value; }
|
|
38
|
+
});
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<!-- Overlay -->
|
|
43
|
+
<div
|
|
44
|
+
v-if="isOpen"
|
|
45
|
+
class="fixed inset-0 bg-black bg-opacity-50 z-50"
|
|
46
|
+
@click="close"
|
|
47
|
+
>
|
|
48
|
+
<!-- Drawer -->
|
|
49
|
+
<div
|
|
50
|
+
class="fixed bg-white shadow-xl z-51"
|
|
51
|
+
:class="{
|
|
52
|
+
'top-0 right-0 bottom-0 w-96 max-w-[90vw]': position === 'right',
|
|
53
|
+
'top-0 left-0 bottom-0 w-96 max-w-[90vw]': position === 'left',
|
|
54
|
+
'top-0 left-0 right-0 h-96 max-h-[90vh]': position === 'top',
|
|
55
|
+
'bottom-0 left-0 right-0 h-96 max-h-[90vh]': position === 'bottom'
|
|
56
|
+
}"
|
|
57
|
+
@click.stop
|
|
58
|
+
>
|
|
59
|
+
<!-- Header -->
|
|
60
|
+
<div class="flex justify-between items-center p-4 border-b border-gray-200">
|
|
61
|
+
<h3 class="text-lg font-medium">{{ title }}</h3>
|
|
62
|
+
<button
|
|
63
|
+
@click="close"
|
|
64
|
+
class="text-2xl text-gray-500 hover:text-gray-700 bg-transparent border-none cursor-pointer"
|
|
65
|
+
>
|
|
66
|
+
×
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<!-- Content -->
|
|
71
|
+
<div class="p-4 overflow-auto">
|
|
72
|
+
<slot />
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref } from 'vue';
|
|
3
|
+
import { Modal } from '@htlkg/components/overlays';
|
|
4
|
+
|
|
5
|
+
const isOpen = ref(false);
|
|
6
|
+
const isConfirmOpen = ref(false);
|
|
7
|
+
const formOpen = ref(false);
|
|
8
|
+
const formData = ref({
|
|
9
|
+
name: '',
|
|
10
|
+
email: ''
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const openModal = () => {
|
|
14
|
+
isOpen.value = true;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const closeModal = () => {
|
|
18
|
+
isOpen.value = false;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const openConfirm = () => {
|
|
22
|
+
isConfirmOpen.value = true;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const handleConfirm = () => {
|
|
26
|
+
alert('Confirmed!');
|
|
27
|
+
isConfirmOpen.value = false;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const openForm = () => {
|
|
31
|
+
formOpen.value = true;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const handleSubmit = () => {
|
|
35
|
+
alert(`Submitted: ${formData.value.name}, ${formData.value.email}`);
|
|
36
|
+
formOpen.value = false;
|
|
37
|
+
formData.value = { name: '', email: '' };
|
|
38
|
+
};
|
|
39
|
+
</script>
|
|
40
|
+
|
|
41
|
+
<template>
|
|
42
|
+
<div class="space-y-4">
|
|
43
|
+
<div>
|
|
44
|
+
<button
|
|
45
|
+
@click="openModal"
|
|
46
|
+
class="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
|
|
47
|
+
>
|
|
48
|
+
Open Basic Modal
|
|
49
|
+
</button>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div>
|
|
53
|
+
<button
|
|
54
|
+
@click="openConfirm"
|
|
55
|
+
class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 transition-colors"
|
|
56
|
+
>
|
|
57
|
+
Open Confirmation Modal
|
|
58
|
+
</button>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div>
|
|
62
|
+
<button
|
|
63
|
+
@click="openForm"
|
|
64
|
+
class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 transition-colors"
|
|
65
|
+
>
|
|
66
|
+
Open Form Modal
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<!-- Basic Modal -->
|
|
71
|
+
<Modal v-model:open="isOpen" title="Basic Modal">
|
|
72
|
+
<div class="p-4">
|
|
73
|
+
<p class="text-gray-700">This is a basic modal with some content.</p>
|
|
74
|
+
<p class="text-gray-700 mt-2">You can close it by clicking the X button or outside the modal.</p>
|
|
75
|
+
</div>
|
|
76
|
+
<template #footer>
|
|
77
|
+
<button
|
|
78
|
+
@click="closeModal"
|
|
79
|
+
class="px-4 py-2 bg-gray-600 text-white rounded hover:bg-gray-700 transition-colors"
|
|
80
|
+
>
|
|
81
|
+
Close
|
|
82
|
+
</button>
|
|
83
|
+
</template>
|
|
84
|
+
</Modal>
|
|
85
|
+
|
|
86
|
+
<!-- Confirmation Modal -->
|
|
87
|
+
<Modal v-model:open="isConfirmOpen" title="Confirm Action">
|
|
88
|
+
<div class="p-4">
|
|
89
|
+
<p class="text-gray-700">Are you sure you want to proceed with this action?</p>
|
|
90
|
+
</div>
|
|
91
|
+
<template #footer>
|
|
92
|
+
<div class="flex gap-2">
|
|
93
|
+
<button
|
|
94
|
+
@click="isConfirmOpen = false"
|
|
95
|
+
class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400 transition-colors"
|
|
96
|
+
>
|
|
97
|
+
Cancel
|
|
98
|
+
</button>
|
|
99
|
+
<button
|
|
100
|
+
@click="handleConfirm"
|
|
101
|
+
class="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 transition-colors"
|
|
102
|
+
>
|
|
103
|
+
Confirm
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
</template>
|
|
107
|
+
</Modal>
|
|
108
|
+
|
|
109
|
+
<!-- Form Modal -->
|
|
110
|
+
<Modal v-model:open="formOpen" title="User Form">
|
|
111
|
+
<div class="p-4 space-y-4">
|
|
112
|
+
<div>
|
|
113
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Name</label>
|
|
114
|
+
<input
|
|
115
|
+
v-model="formData.name"
|
|
116
|
+
type="text"
|
|
117
|
+
class="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
|
118
|
+
placeholder="Enter name"
|
|
119
|
+
/>
|
|
120
|
+
</div>
|
|
121
|
+
<div>
|
|
122
|
+
<label class="block text-sm font-medium text-gray-700 mb-1">Email</label>
|
|
123
|
+
<input
|
|
124
|
+
v-model="formData.email"
|
|
125
|
+
type="email"
|
|
126
|
+
class="w-full px-3 py-2 border border-gray-300 rounded focus:ring-2 focus:ring-green-500 focus:border-transparent"
|
|
127
|
+
placeholder="Enter email"
|
|
128
|
+
/>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
<template #footer>
|
|
132
|
+
<div class="flex gap-2">
|
|
133
|
+
<button
|
|
134
|
+
@click="formOpen = false"
|
|
135
|
+
class="px-4 py-2 bg-gray-300 text-gray-700 rounded hover:bg-gray-400 transition-colors"
|
|
136
|
+
>
|
|
137
|
+
Cancel
|
|
138
|
+
</button>
|
|
139
|
+
<button
|
|
140
|
+
@click="handleSubmit"
|
|
141
|
+
class="px-4 py-2 bg-green-600 text-white rounded hover:bg-green-700 transition-colors"
|
|
142
|
+
>
|
|
143
|
+
Submit
|
|
144
|
+
</button>
|
|
145
|
+
</div>
|
|
146
|
+
</template>
|
|
147
|
+
</Modal>
|
|
148
|
+
</div>
|
|
149
|
+
</template>
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
# Modal Component
|
|
2
|
+
|
|
3
|
+
A stateful Vue wrapper around `@hotelinking/ui`'s `uiModal` component. Provides dialog overlays for confirmations, forms, and content display.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **v-model support**: Two-way binding for open/close state
|
|
8
|
+
- **Action buttons**: Configurable action buttons with custom events
|
|
9
|
+
- **Three sizes**: Small, medium, and large modal sizes
|
|
10
|
+
- **Slot support**: Custom header, content, and footer
|
|
11
|
+
- **Exposed methods**: Programmatic open/close/toggle control
|
|
12
|
+
- **Auto-close**: Closes on action, X button, or outside click
|
|
13
|
+
|
|
14
|
+
## Import
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { Modal } from '@htlkg/components';
|
|
18
|
+
// or
|
|
19
|
+
import { Modal } from '@htlkg/components/overlays';
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Props
|
|
23
|
+
|
|
24
|
+
| Prop | Type | Default | Description |
|
|
25
|
+
|------|------|---------|-------------|
|
|
26
|
+
| `open` | `boolean` | `false` | Control visibility (v-model) |
|
|
27
|
+
| `title` | `string` | `''` | Modal title |
|
|
28
|
+
| `content` | `string` | `''` | Modal content text |
|
|
29
|
+
| `modalName` | `string` | `'modal'` | Unique modal identifier |
|
|
30
|
+
| `actions` | `Array<{name: string, action: string, type?: string}>` | `[]` | Action buttons |
|
|
31
|
+
| `size` | `'small' \| 'medium' \| 'large'` | `'medium'` | Modal size |
|
|
32
|
+
|
|
33
|
+
## Events
|
|
34
|
+
|
|
35
|
+
| Event | Payload | Description |
|
|
36
|
+
|-------|---------|-------------|
|
|
37
|
+
| `update:open` | `boolean` | Emitted when visibility changes (v-model) |
|
|
38
|
+
| `close` | - | Emitted when modal is closed |
|
|
39
|
+
| `action` | `{modal: string, action: string}` | Emitted when action button is clicked |
|
|
40
|
+
|
|
41
|
+
## Slots
|
|
42
|
+
|
|
43
|
+
| Slot | Description |
|
|
44
|
+
|------|-------------|
|
|
45
|
+
| `default` | Modal content (replaces `content` prop) |
|
|
46
|
+
| `header` | Custom header content |
|
|
47
|
+
| `footer` | Custom footer content |
|
|
48
|
+
|
|
49
|
+
## Exposed Methods
|
|
50
|
+
|
|
51
|
+
| Method | Description |
|
|
52
|
+
|--------|-------------|
|
|
53
|
+
| `open()` | Open the modal |
|
|
54
|
+
| `close()` | Close the modal |
|
|
55
|
+
| `toggle()` | Toggle modal visibility |
|
|
56
|
+
|
|
57
|
+
## Usage Examples
|
|
58
|
+
|
|
59
|
+
### Basic Modal
|
|
60
|
+
|
|
61
|
+
```vue
|
|
62
|
+
<script setup>
|
|
63
|
+
import { ref } from 'vue';
|
|
64
|
+
import { Modal } from '@htlkg/components';
|
|
65
|
+
|
|
66
|
+
const isOpen = ref(false);
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<template>
|
|
70
|
+
<button @click="isOpen = true">Open Modal</button>
|
|
71
|
+
|
|
72
|
+
<Modal
|
|
73
|
+
v-model:open="isOpen"
|
|
74
|
+
title="Welcome"
|
|
75
|
+
content="This is a basic modal dialog."
|
|
76
|
+
/>
|
|
77
|
+
</template>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Confirmation Modal
|
|
81
|
+
|
|
82
|
+
```vue
|
|
83
|
+
<script setup>
|
|
84
|
+
import { ref } from 'vue';
|
|
85
|
+
import { Modal } from '@htlkg/components';
|
|
86
|
+
|
|
87
|
+
const showConfirm = ref(false);
|
|
88
|
+
|
|
89
|
+
const actions = [
|
|
90
|
+
{ name: 'Confirm', action: 'confirm', type: 'primary' },
|
|
91
|
+
{ name: 'Cancel', action: 'cancel', type: 'secondary' }
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const handleAction = (data) => {
|
|
95
|
+
if (data.action === 'confirm') {
|
|
96
|
+
// Proceed with action
|
|
97
|
+
console.log('Confirmed');
|
|
98
|
+
}
|
|
99
|
+
// Modal auto-closes on any action
|
|
100
|
+
};
|
|
101
|
+
</script>
|
|
102
|
+
|
|
103
|
+
<template>
|
|
104
|
+
<Modal
|
|
105
|
+
v-model:open="showConfirm"
|
|
106
|
+
title="Confirm Deletion"
|
|
107
|
+
content="Are you sure you want to delete this item? This action cannot be undone."
|
|
108
|
+
:actions="actions"
|
|
109
|
+
@action="handleAction"
|
|
110
|
+
/>
|
|
111
|
+
</template>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Modal with Custom Content
|
|
115
|
+
|
|
116
|
+
```vue
|
|
117
|
+
<script setup>
|
|
118
|
+
import { ref } from 'vue';
|
|
119
|
+
import { Modal } from '@htlkg/components';
|
|
120
|
+
|
|
121
|
+
const isOpen = ref(false);
|
|
122
|
+
</script>
|
|
123
|
+
|
|
124
|
+
<template>
|
|
125
|
+
<Modal
|
|
126
|
+
v-model:open="isOpen"
|
|
127
|
+
title="User Details"
|
|
128
|
+
>
|
|
129
|
+
<div class="space-y-4">
|
|
130
|
+
<div>
|
|
131
|
+
<label class="font-medium">Name:</label>
|
|
132
|
+
<p>John Doe</p>
|
|
133
|
+
</div>
|
|
134
|
+
<div>
|
|
135
|
+
<label class="font-medium">Email:</label>
|
|
136
|
+
<p>john@example.com</p>
|
|
137
|
+
</div>
|
|
138
|
+
<div>
|
|
139
|
+
<label class="font-medium">Role:</label>
|
|
140
|
+
<p>Administrator</p>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</Modal>
|
|
144
|
+
</template>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Form Modal
|
|
148
|
+
|
|
149
|
+
```vue
|
|
150
|
+
<script setup>
|
|
151
|
+
import { ref } from 'vue';
|
|
152
|
+
import { Modal } from '@htlkg/components';
|
|
153
|
+
|
|
154
|
+
const isOpen = ref(false);
|
|
155
|
+
const formData = ref({ name: '', email: '' });
|
|
156
|
+
|
|
157
|
+
const actions = [
|
|
158
|
+
{ name: 'Save', action: 'save', type: 'primary' },
|
|
159
|
+
{ name: 'Cancel', action: 'cancel', type: 'secondary' }
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
const handleAction = (data) => {
|
|
163
|
+
if (data.action === 'save') {
|
|
164
|
+
// Save form data
|
|
165
|
+
console.log('Saving:', formData.value);
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
<template>
|
|
171
|
+
<Modal
|
|
172
|
+
v-model:open="isOpen"
|
|
173
|
+
title="Create User"
|
|
174
|
+
:actions="actions"
|
|
175
|
+
@action="handleAction"
|
|
176
|
+
>
|
|
177
|
+
<form class="space-y-4">
|
|
178
|
+
<div>
|
|
179
|
+
<label class="block text-sm font-medium mb-1">Name</label>
|
|
180
|
+
<input
|
|
181
|
+
v-model="formData.name"
|
|
182
|
+
type="text"
|
|
183
|
+
class="w-full px-3 py-2 border rounded"
|
|
184
|
+
/>
|
|
185
|
+
</div>
|
|
186
|
+
<div>
|
|
187
|
+
<label class="block text-sm font-medium mb-1">Email</label>
|
|
188
|
+
<input
|
|
189
|
+
v-model="formData.email"
|
|
190
|
+
type="email"
|
|
191
|
+
class="w-full px-3 py-2 border rounded"
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
</form>
|
|
195
|
+
</Modal>
|
|
196
|
+
</template>
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Modal Sizes
|
|
200
|
+
|
|
201
|
+
```vue
|
|
202
|
+
<script setup>
|
|
203
|
+
import { ref } from 'vue';
|
|
204
|
+
import { Modal } from '@htlkg/components';
|
|
205
|
+
|
|
206
|
+
const smallModal = ref(false);
|
|
207
|
+
const mediumModal = ref(false);
|
|
208
|
+
const largeModal = ref(false);
|
|
209
|
+
</script>
|
|
210
|
+
|
|
211
|
+
<template>
|
|
212
|
+
<div class="space-x-2">
|
|
213
|
+
<button @click="smallModal = true">Small Modal</button>
|
|
214
|
+
<button @click="mediumModal = true">Medium Modal</button>
|
|
215
|
+
<button @click="largeModal = true">Large Modal</button>
|
|
216
|
+
</div>
|
|
217
|
+
|
|
218
|
+
<Modal
|
|
219
|
+
v-model:open="smallModal"
|
|
220
|
+
title="Small Modal"
|
|
221
|
+
content="This is a small modal."
|
|
222
|
+
size="small"
|
|
223
|
+
/>
|
|
224
|
+
|
|
225
|
+
<Modal
|
|
226
|
+
v-model:open="mediumModal"
|
|
227
|
+
title="Medium Modal"
|
|
228
|
+
content="This is a medium modal (default)."
|
|
229
|
+
size="medium"
|
|
230
|
+
/>
|
|
231
|
+
|
|
232
|
+
<Modal
|
|
233
|
+
v-model:open="largeModal"
|
|
234
|
+
title="Large Modal"
|
|
235
|
+
content="This is a large modal for more content."
|
|
236
|
+
size="large"
|
|
237
|
+
/>
|
|
238
|
+
</template>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Custom Header and Footer
|
|
242
|
+
|
|
243
|
+
```vue
|
|
244
|
+
<script setup>
|
|
245
|
+
import { ref } from 'vue';
|
|
246
|
+
import { Modal } from '@htlkg/components';
|
|
247
|
+
|
|
248
|
+
const isOpen = ref(false);
|
|
249
|
+
</script>
|
|
250
|
+
|
|
251
|
+
<template>
|
|
252
|
+
<Modal v-model:open="isOpen">
|
|
253
|
+
<template #header>
|
|
254
|
+
<div class="flex items-center gap-2">
|
|
255
|
+
<span class="text-2xl">🎉</span>
|
|
256
|
+
<h2 class="text-xl font-bold">Congratulations!</h2>
|
|
257
|
+
</div>
|
|
258
|
+
</template>
|
|
259
|
+
|
|
260
|
+
<p>You've successfully completed the setup process.</p>
|
|
261
|
+
|
|
262
|
+
<template #footer>
|
|
263
|
+
<div class="flex justify-end gap-2">
|
|
264
|
+
<button @click="isOpen = false" class="btn btn-secondary">
|
|
265
|
+
Close
|
|
266
|
+
</button>
|
|
267
|
+
<button @click="handleNext" class="btn btn-primary">
|
|
268
|
+
Next Step
|
|
269
|
+
</button>
|
|
270
|
+
</div>
|
|
271
|
+
</template>
|
|
272
|
+
</Modal>
|
|
273
|
+
</template>
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Programmatic Control
|
|
277
|
+
|
|
278
|
+
```vue
|
|
279
|
+
<script setup>
|
|
280
|
+
import { ref } from 'vue';
|
|
281
|
+
import { Modal } from '@htlkg/components';
|
|
282
|
+
|
|
283
|
+
const modalRef = ref();
|
|
284
|
+
|
|
285
|
+
const openModal = () => modalRef.value?.open();
|
|
286
|
+
const closeModal = () => modalRef.value?.close();
|
|
287
|
+
const toggleModal = () => modalRef.value?.toggle();
|
|
288
|
+
</script>
|
|
289
|
+
|
|
290
|
+
<template>
|
|
291
|
+
<div class="space-x-2">
|
|
292
|
+
<button @click="openModal">Open</button>
|
|
293
|
+
<button @click="closeModal">Close</button>
|
|
294
|
+
<button @click="toggleModal">Toggle</button>
|
|
295
|
+
</div>
|
|
296
|
+
|
|
297
|
+
<Modal
|
|
298
|
+
ref="modalRef"
|
|
299
|
+
title="Controlled Modal"
|
|
300
|
+
content="This modal is controlled programmatically."
|
|
301
|
+
/>
|
|
302
|
+
</template>
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Nested Modals
|
|
306
|
+
|
|
307
|
+
```vue
|
|
308
|
+
<script setup>
|
|
309
|
+
import { ref } from 'vue';
|
|
310
|
+
import { Modal } from '@htlkg/components';
|
|
311
|
+
|
|
312
|
+
const firstModal = ref(false);
|
|
313
|
+
const secondModal = ref(false);
|
|
314
|
+
</script>
|
|
315
|
+
|
|
316
|
+
<template>
|
|
317
|
+
<button @click="firstModal = true">Open First Modal</button>
|
|
318
|
+
|
|
319
|
+
<Modal
|
|
320
|
+
v-model:open="firstModal"
|
|
321
|
+
title="First Modal"
|
|
322
|
+
modalName="first-modal"
|
|
323
|
+
>
|
|
324
|
+
<p>This is the first modal.</p>
|
|
325
|
+
<button @click="secondModal = true" class="btn btn-primary mt-4">
|
|
326
|
+
Open Second Modal
|
|
327
|
+
</button>
|
|
328
|
+
</Modal>
|
|
329
|
+
|
|
330
|
+
<Modal
|
|
331
|
+
v-model:open="secondModal"
|
|
332
|
+
title="Second Modal"
|
|
333
|
+
modalName="second-modal"
|
|
334
|
+
>
|
|
335
|
+
<p>This is a nested modal.</p>
|
|
336
|
+
</Modal>
|
|
337
|
+
</template>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Best Practices
|
|
341
|
+
|
|
342
|
+
### UX Guidelines
|
|
343
|
+
|
|
344
|
+
- Use modals sparingly - they interrupt user flow
|
|
345
|
+
- Provide clear action buttons (primary and secondary)
|
|
346
|
+
- Always include a way to close (X button, Cancel, or outside click)
|
|
347
|
+
- Use appropriate sizes based on content
|
|
348
|
+
- Keep content concise and focused
|
|
349
|
+
- Use confirmation modals for destructive actions
|
|
350
|
+
|
|
351
|
+
### Accessibility
|
|
352
|
+
|
|
353
|
+
- Modal traps focus when open
|
|
354
|
+
- Escape key closes the modal
|
|
355
|
+
- Focus returns to trigger element on close
|
|
356
|
+
- Use semantic HTML in modal content
|
|
357
|
+
- Provide clear button labels
|
|
358
|
+
|
|
359
|
+
### Performance
|
|
360
|
+
|
|
361
|
+
- Lazy load modal content when possible
|
|
362
|
+
- Avoid nesting too many modals
|
|
363
|
+
- Clean up event listeners on unmount
|
|
364
|
+
- Use v-if instead of v-show for heavy content
|
|
365
|
+
|
|
366
|
+
## Design System Integration
|
|
367
|
+
|
|
368
|
+
This component wraps `@hotelinking/ui`'s `uiModal` component, providing:
|
|
369
|
+
|
|
370
|
+
- Reactive state management with Vue 3 Composition API
|
|
371
|
+
- v-model support for open/close state
|
|
372
|
+
- Exposed methods for programmatic control
|
|
373
|
+
- Event handling for actions
|
|
374
|
+
- Slot support for custom content
|
|
375
|
+
- Consistent API with other overlay components
|
|
376
|
+
|
|
377
|
+
## Related Components
|
|
378
|
+
|
|
379
|
+
- **Alert**: For inline alerts and notifications
|
|
380
|
+
- **Notification**: For toast-style notifications
|
|
381
|
+
- **Drawer**: For side panel overlays
|
|
382
|
+
|
|
383
|
+
## Demo
|
|
384
|
+
|
|
385
|
+
See the [Modal demo page](/components/modal) for interactive examples.
|