@htlkg/components 0.0.1 → 0.0.2
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 +206 -19
- package/dist/composables/index.js.map +1 -1
- package/package.json +40 -40
- package/src/composables/__test-useTable__.ts +34 -0
- package/src/composables/index.ts +1 -1
- package/src/composables/useTable.md +350 -0
- package/src/composables/useTable.ts +328 -27
- package/src/data/Table.demo.vue +26 -10
- package/src/data/Table.vue +13 -39
- package/src/forms/JsonSchemaForm.test.ts +98 -168
- package/src/forms/JsonSchemaForm.unit.test.ts +97 -45
- package/src/forms/JsonSchemaForm.vue +17 -1
- package/src/index.ts +3 -0
- package/src/navigation/AdminWrapper.vue +198 -0
- package/src/navigation/Tabs.test.ts +55 -27
- package/src/navigation/index.ts +1 -0
- package/src/overlays/Alert.test.ts +39 -74
- package/src/overlays/Drawer.test.ts +57 -23
- package/src/overlays/Modal.test.ts +54 -42
- package/src/stores/index.ts +7 -0
- package/src/stores/user.ts +33 -0
- package/src/test-setup.ts +235 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User Store
|
|
3
|
+
*
|
|
4
|
+
* Nanostore for sharing user data across components
|
|
5
|
+
* without props (lower hydration cost)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { atom } from 'nanostores';
|
|
9
|
+
|
|
10
|
+
export interface User {
|
|
11
|
+
username?: string;
|
|
12
|
+
email?: string;
|
|
13
|
+
avatar?: string;
|
|
14
|
+
isAdmin?: boolean;
|
|
15
|
+
attributes?: {
|
|
16
|
+
email?: string;
|
|
17
|
+
[key: string]: any;
|
|
18
|
+
};
|
|
19
|
+
[key: string]: any;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// User store
|
|
23
|
+
export const $user = atom<User | null>(null);
|
|
24
|
+
|
|
25
|
+
// Helper function to set user
|
|
26
|
+
export function setUser(user: User | null) {
|
|
27
|
+
$user.set(user);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Helper function to clear user
|
|
31
|
+
export function clearUser() {
|
|
32
|
+
$user.set(null);
|
|
33
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { config } from '@vue/test-utils';
|
|
2
|
+
import { h, defineComponent } from 'vue';
|
|
3
|
+
|
|
4
|
+
// Stub for uiInput from @hotelinking/ui
|
|
5
|
+
const uiInputStub = defineComponent({
|
|
6
|
+
name: 'uiInput',
|
|
7
|
+
props: ['name', 'label', 'type', 'value', 'placeholder', 'error', 'color', 'loading', 'requiredText', 'min', 'max', 'autocomplete'],
|
|
8
|
+
emits: ['input-changed'],
|
|
9
|
+
setup(props, { emit }) {
|
|
10
|
+
return () => h('div', { class: 'ui-input' }, [
|
|
11
|
+
props.label && h('label', props.label),
|
|
12
|
+
h('input', {
|
|
13
|
+
name: props.name,
|
|
14
|
+
value: props.value,
|
|
15
|
+
type: props.type || 'text',
|
|
16
|
+
placeholder: props.placeholder,
|
|
17
|
+
disabled: props.loading,
|
|
18
|
+
onInput: (e: Event) => emit('input-changed', { value: (e.target as HTMLInputElement).value })
|
|
19
|
+
}),
|
|
20
|
+
props.error && h('span', { class: 'error' }, props.error)
|
|
21
|
+
]);
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Stub for uiSelect from @hotelinking/ui
|
|
26
|
+
const uiSelectStub = defineComponent({
|
|
27
|
+
name: 'uiSelect',
|
|
28
|
+
props: ['label', 'items', 'select', 'placeholder', 'error', 'color', 'loading', 'requiredText'],
|
|
29
|
+
emits: ['select-changed'],
|
|
30
|
+
setup(props, { emit }) {
|
|
31
|
+
return () => h('div', { class: 'ui-select' }, [
|
|
32
|
+
props.label && h('label', props.label),
|
|
33
|
+
h('select', {
|
|
34
|
+
value: props.select?.id,
|
|
35
|
+
disabled: props.loading,
|
|
36
|
+
onChange: (e: Event) => {
|
|
37
|
+
const value = (e.target as HTMLSelectElement).value;
|
|
38
|
+
const item = props.items?.find((i: any) => i.id === value);
|
|
39
|
+
emit('select-changed', item);
|
|
40
|
+
}
|
|
41
|
+
}, props.items?.map((opt: any) =>
|
|
42
|
+
h('option', { value: opt.id }, opt.label || opt.name)
|
|
43
|
+
)),
|
|
44
|
+
props.error && h('span', { class: 'error' }, props.error)
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Stub for uiToggle from @hotelinking/ui
|
|
50
|
+
const uiToggleStub = defineComponent({
|
|
51
|
+
name: 'uiToggle',
|
|
52
|
+
props: ['item', 'checked', 'loading'],
|
|
53
|
+
emits: ['toggle-changed'],
|
|
54
|
+
setup(props, { emit }) {
|
|
55
|
+
return () => h('div', { class: 'ui-toggle' }, [
|
|
56
|
+
h('input', {
|
|
57
|
+
type: 'checkbox',
|
|
58
|
+
checked: props.checked,
|
|
59
|
+
disabled: props.loading,
|
|
60
|
+
onChange: (e: Event) => emit('toggle-changed', { active: (e.target as HTMLInputElement).checked })
|
|
61
|
+
}),
|
|
62
|
+
props.item?.title && h('label', props.item.title)
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Stub for uiTextArea from @hotelinking/ui
|
|
68
|
+
const uiTextAreaStub = defineComponent({
|
|
69
|
+
name: 'uiTextArea',
|
|
70
|
+
props: ['name', 'label', 'value', 'placeholder', 'error', 'color', 'loading', 'requiredText', 'rows'],
|
|
71
|
+
emits: ['input-changed'],
|
|
72
|
+
setup(props, { emit }) {
|
|
73
|
+
return () => h('div', { class: 'ui-textarea' }, [
|
|
74
|
+
props.label && h('label', props.label),
|
|
75
|
+
h('textarea', {
|
|
76
|
+
name: props.name,
|
|
77
|
+
value: props.value,
|
|
78
|
+
placeholder: props.placeholder,
|
|
79
|
+
disabled: props.loading,
|
|
80
|
+
rows: props.rows,
|
|
81
|
+
onInput: (e: Event) => emit('input-changed', { value: (e.target as HTMLTextAreaElement).value })
|
|
82
|
+
}),
|
|
83
|
+
props.error && h('span', { class: 'error' }, props.error)
|
|
84
|
+
]);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Stub for uiRangeSlider from @hotelinking/ui
|
|
89
|
+
const uiRangeSliderStub = defineComponent({
|
|
90
|
+
name: 'uiRangeSlider',
|
|
91
|
+
props: ['label', 'min', 'max', 'sliderValue', 'loading', 'requiredText'],
|
|
92
|
+
emits: ['sliderUpdated'],
|
|
93
|
+
setup(props, { emit }) {
|
|
94
|
+
return () => h('div', { class: 'ui-slider' }, [
|
|
95
|
+
props.label && h('label', props.label),
|
|
96
|
+
h('input', {
|
|
97
|
+
type: 'range',
|
|
98
|
+
value: props.sliderValue,
|
|
99
|
+
min: props.min,
|
|
100
|
+
max: props.max,
|
|
101
|
+
disabled: props.loading,
|
|
102
|
+
onInput: (e: Event) => emit('sliderUpdated', Number((e.target as HTMLInputElement).value))
|
|
103
|
+
})
|
|
104
|
+
]);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Stub for uiButton from @hotelinking/ui
|
|
109
|
+
const uiButtonStub = defineComponent({
|
|
110
|
+
name: 'uiButton',
|
|
111
|
+
props: ['type', 'color', 'loading', 'disabled'],
|
|
112
|
+
setup(props, { slots }) {
|
|
113
|
+
return () => h('button', {
|
|
114
|
+
class: 'ui-button',
|
|
115
|
+
type: props.type || 'button',
|
|
116
|
+
disabled: props.disabled || props.loading
|
|
117
|
+
}, slots.default?.());
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Stub for uiAlert from @hotelinking/ui
|
|
122
|
+
const uiAlertStub = defineComponent({
|
|
123
|
+
name: 'uiAlert',
|
|
124
|
+
props: ['title', 'type', 'actions', 'loading'],
|
|
125
|
+
emits: ['alert-event'],
|
|
126
|
+
setup(props, { slots, emit }) {
|
|
127
|
+
return () => h('div', { class: 'ui-alert', 'data-type': props.type }, [
|
|
128
|
+
h('div', { class: 'alert-title' }, props.title),
|
|
129
|
+
slots.default?.(),
|
|
130
|
+
props.actions?.map((action: any) =>
|
|
131
|
+
h('button', {
|
|
132
|
+
class: 'alert-action',
|
|
133
|
+
onClick: () => emit('alert-event', action.event)
|
|
134
|
+
}, action.name)
|
|
135
|
+
)
|
|
136
|
+
]);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Stub for uiModal from @hotelinking/ui
|
|
141
|
+
const uiModalStub = defineComponent({
|
|
142
|
+
name: 'UiModal',
|
|
143
|
+
props: ['title', 'content', 'modalName', 'open', 'actions'],
|
|
144
|
+
emits: ['modalAction'],
|
|
145
|
+
setup(props, { slots, emit }) {
|
|
146
|
+
return () => props.open ? h('div', { class: 'ui-modal' }, [
|
|
147
|
+
h('div', { class: 'modal-title' }, props.title),
|
|
148
|
+
h('div', { class: 'modal-content' }, props.content),
|
|
149
|
+
slots.default?.(),
|
|
150
|
+
slots.header?.(),
|
|
151
|
+
slots.footer?.(),
|
|
152
|
+
props.actions?.map((action: any) =>
|
|
153
|
+
h('button', {
|
|
154
|
+
class: 'modal-action',
|
|
155
|
+
onClick: () => emit('modalAction', { modal: props.modalName, action: action.value })
|
|
156
|
+
}, action.text)
|
|
157
|
+
)
|
|
158
|
+
]) : null;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Stub for uiTag from @hotelinking/ui
|
|
163
|
+
const uiTagStub = defineComponent({
|
|
164
|
+
name: 'uiTag',
|
|
165
|
+
props: ['color', 'size'],
|
|
166
|
+
setup(props, { slots }) {
|
|
167
|
+
return () => h('span', {
|
|
168
|
+
class: `ui-tag ui-tag-${props.color || 'gray'}`
|
|
169
|
+
}, slots.default?.());
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Stub for uiAvatar from @hotelinking/ui
|
|
174
|
+
const uiAvatarStub = defineComponent({
|
|
175
|
+
name: 'uiAvatar',
|
|
176
|
+
props: ['src', 'alt', 'size', 'initials'],
|
|
177
|
+
setup(props) {
|
|
178
|
+
return () => h('div', { class: 'ui-avatar' }, [
|
|
179
|
+
props.src
|
|
180
|
+
? h('img', { src: props.src, alt: props.alt })
|
|
181
|
+
: h('span', { class: 'initials' }, props.initials)
|
|
182
|
+
]);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Register global stubs
|
|
187
|
+
config.global.stubs = {
|
|
188
|
+
uiAlert: uiAlertStub,
|
|
189
|
+
uiModal: uiModalStub,
|
|
190
|
+
UiModal: uiModalStub,
|
|
191
|
+
uiButton: uiButtonStub,
|
|
192
|
+
uiInput: uiInputStub,
|
|
193
|
+
uiToggle: uiToggleStub,
|
|
194
|
+
uiSelect: uiSelectStub,
|
|
195
|
+
uiRangeSlider: uiRangeSliderStub,
|
|
196
|
+
uiTextArea: uiTextAreaStub,
|
|
197
|
+
uiTag: uiTagStub,
|
|
198
|
+
uiAvatar: uiAvatarStub,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Mock ResizeObserver
|
|
202
|
+
if (typeof global.ResizeObserver === 'undefined') {
|
|
203
|
+
global.ResizeObserver = class ResizeObserver {
|
|
204
|
+
observe() {}
|
|
205
|
+
unobserve() {}
|
|
206
|
+
disconnect() {}
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Mock IntersectionObserver
|
|
211
|
+
if (typeof global.IntersectionObserver === 'undefined') {
|
|
212
|
+
global.IntersectionObserver = class IntersectionObserver {
|
|
213
|
+
constructor() {}
|
|
214
|
+
observe() {}
|
|
215
|
+
unobserve() {}
|
|
216
|
+
disconnect() {}
|
|
217
|
+
root = null;
|
|
218
|
+
rootMargin = '';
|
|
219
|
+
thresholds = [];
|
|
220
|
+
takeRecords() { return []; }
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export {
|
|
225
|
+
uiAlertStub,
|
|
226
|
+
uiModalStub,
|
|
227
|
+
uiButtonStub,
|
|
228
|
+
uiInputStub,
|
|
229
|
+
uiToggleStub,
|
|
230
|
+
uiSelectStub,
|
|
231
|
+
uiRangeSliderStub,
|
|
232
|
+
uiTextAreaStub,
|
|
233
|
+
uiTagStub,
|
|
234
|
+
uiAvatarStub
|
|
235
|
+
};
|