@teammodlr/ui-kit 1.0.4 → 1.0.6

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.
Files changed (147) hide show
  1. package/package.json +2 -1
  2. package/src/__storybook__/argTypes.ts +42 -0
  3. package/src/__storybook__/createStory.ts +15 -0
  4. package/src/__storybook__/index.ts +2 -0
  5. package/src/components/avatar/Avatar.css +69 -0
  6. package/src/components/avatar/Avatar.vue +102 -0
  7. package/src/components/avatar/examples/basic.vue +6 -0
  8. package/src/components/avatar/examples/custom-size.vue +7 -0
  9. package/src/components/avatar/examples/index.stories.ts +28 -0
  10. package/src/components/avatar/examples/index.ts +5 -0
  11. package/src/components/avatar/examples/sizes.vue +33 -0
  12. package/src/components/avatar/examples/slots.vue +7 -0
  13. package/src/components/avatar/examples/variants.vue +18 -0
  14. package/src/components/avatargroup/AvatarGroup.css +8 -0
  15. package/src/components/avatargroup/AvatarGroup.vue +32 -0
  16. package/src/components/avatargroup/examples/basic.vue +11 -0
  17. package/src/components/avatargroup/examples/index.stories.ts +28 -0
  18. package/src/components/avatargroup/examples/index.ts +1 -0
  19. package/src/components/breadcrumb/Breadcrumb.css +46 -0
  20. package/src/components/breadcrumb/Breadcrumb.vue +51 -0
  21. package/src/components/breadcrumb/examples/basic.vue +9 -0
  22. package/src/components/breadcrumb/examples/index.stories.ts +20 -0
  23. package/src/components/breadcrumb/examples/index.ts +1 -0
  24. package/src/components/breadcrumbitem/BreadcrumbItem.vue +66 -0
  25. package/src/components/breadcrumbitem/index.ts +1 -0
  26. package/src/components/button/Button.css +378 -0
  27. package/src/components/button/Button.vue +108 -0
  28. package/src/components/button/examples/basic.vue +3 -0
  29. package/src/components/button/examples/colors-secondary.vue +8 -0
  30. package/src/components/button/examples/colors.vue +8 -0
  31. package/src/components/button/examples/disabled.vue +3 -0
  32. package/src/components/button/examples/icon.vue +10 -0
  33. package/src/components/button/examples/index.stories.ts +33 -0
  34. package/src/components/button/examples/index.ts +7 -0
  35. package/src/components/button/examples/rounded.vue +7 -0
  36. package/src/components/button/examples/size.vue +7 -0
  37. package/src/components/buttongroup/ButtonGroup.vue +47 -0
  38. package/src/components/buttongroup/examples/basic.vue +9 -0
  39. package/src/components/buttongroup/examples/index.stories.ts +21 -0
  40. package/src/components/buttongroup/examples/index.ts +1 -0
  41. package/src/components/checkbox/Checkbox.css +185 -0
  42. package/src/components/checkbox/Checkbox.vue +140 -0
  43. package/src/components/checkbox/examples/basic.vue +9 -0
  44. package/src/components/checkbox/examples/index.stories.ts +21 -0
  45. package/src/components/checkbox/examples/index.ts +3 -0
  46. package/src/components/checkbox/examples/slot.vue +11 -0
  47. package/src/components/dropdown/Dropdown.css +68 -0
  48. package/src/components/dropdown/Dropdown.vue +237 -0
  49. package/src/components/dropdown/DropdownItem.vue +118 -0
  50. package/src/components/dropdown/DropdownList.vue +20 -0
  51. package/src/components/dropdown/DropdownWrapper.vue +17 -0
  52. package/src/components/dropdown/examples/basic.vue +73 -0
  53. package/src/components/dropdown/examples/context.vue +71 -0
  54. package/src/components/dropdown/examples/index.stories.ts +23 -0
  55. package/src/components/dropdown/examples/index.ts +2 -0
  56. package/src/components/dropdown/hooks.ts +30 -0
  57. package/src/components/form/Form.vue +141 -0
  58. package/src/components/form/examples/basic.vue +73 -0
  59. package/src/components/form/examples/index.stories.ts +23 -0
  60. package/src/components/form/examples/index.ts +1 -0
  61. package/src/components/image/Image.vue +71 -0
  62. package/src/components/image/examples/basic.vue +5 -0
  63. package/src/components/image/examples/index.stories.ts +12 -0
  64. package/src/components/image/examples/index.ts +1 -0
  65. package/src/components/index.ts +27 -0
  66. package/src/components/input/Input.css +173 -0
  67. package/src/components/input/Input.vue +176 -0
  68. package/src/components/input/examples/basic.vue +7 -0
  69. package/src/components/input/examples/index.stories.ts +24 -0
  70. package/src/components/input/examples/index.ts +4 -0
  71. package/src/components/input/examples/prefix.vue +7 -0
  72. package/src/components/input/examples/sizes.vue +9 -0
  73. package/src/components/input/examples/suffix.vue +7 -0
  74. package/src/components/link/Link.css +147 -0
  75. package/src/components/link/Link.vue +54 -0
  76. package/src/components/link/examples/basic.vue +3 -0
  77. package/src/components/link/examples/index.stories.ts +20 -0
  78. package/src/components/link/examples/index.ts +1 -0
  79. package/src/components/messageprovider/Message.vue +76 -0
  80. package/src/components/messageprovider/MessageProvider.css +36 -0
  81. package/src/components/messageprovider/MessageProvider.vue +142 -0
  82. package/src/components/messageprovider/examples/basic.vue +19 -0
  83. package/src/components/messageprovider/examples/index.stories.ts +30 -0
  84. package/src/components/messageprovider/examples/index.ts +1 -0
  85. package/src/components/messageprovider/examples/wrapper.vue +9 -0
  86. package/src/components/messageprovider/types.ts +39 -0
  87. package/src/components/messageprovider/useMessage.ts +12 -0
  88. package/src/components/select/Select.css +226 -0
  89. package/src/components/select/Select.vue +474 -0
  90. package/src/components/select/examples/basic.vue +20 -0
  91. package/src/components/select/examples/index.stories.ts +22 -0
  92. package/src/components/select/examples/index.ts +3 -0
  93. package/src/components/select/examples/multiple.vue +22 -0
  94. package/src/components/select/examples/searchable.vue +20 -0
  95. package/src/components/skeleton/Skeleton.css +73 -0
  96. package/src/components/skeleton/Skeleton.vue +47 -0
  97. package/src/components/skeleton/examples/basic.vue +3 -0
  98. package/src/components/skeleton/examples/index.stories.ts +24 -0
  99. package/src/components/skeleton/examples/index.ts +2 -0
  100. package/src/components/skeleton/examples/post.vue +8 -0
  101. package/src/components/tab/Tab.css +113 -0
  102. package/src/components/tab/Tab.vue +98 -0
  103. package/src/components/tab/TabGroup.vue +90 -0
  104. package/src/components/tab/examples/basic.vue +19 -0
  105. package/src/components/tab/examples/index.stories.ts +25 -0
  106. package/src/components/tab/examples/index.ts +2 -0
  107. package/src/components/tab/examples/vertical.vue +19 -0
  108. package/src/components/table/Table.css +45 -0
  109. package/src/components/table/Table.vue +108 -0
  110. package/src/components/table/TableBody.vue +13 -0
  111. package/src/components/table/TableCell.vue +13 -0
  112. package/src/components/table/TableHead.vue +22 -0
  113. package/src/components/table/TableHeadRow.vue +15 -0
  114. package/src/components/table/TableRow.vue +13 -0
  115. package/src/components/table/examples/basic.vue +46 -0
  116. package/src/components/table/examples/index.stories.ts +19 -0
  117. package/src/components/table/examples/index.ts +1 -0
  118. package/src/components/table/types/index.ts +5 -0
  119. package/src/components/tooltip/Tooltip.css +49 -0
  120. package/src/components/tooltip/Tooltip.vue +85 -0
  121. package/src/components/tooltip/examples/basic.vue +8 -0
  122. package/src/components/tooltip/examples/index.stories.ts +21 -0
  123. package/src/components/tooltip/examples/index.ts +1 -0
  124. package/src/components/virtualscroll/VirtualScroll.css +13 -0
  125. package/src/components/virtualscroll/VirtualScroll.vue +61 -0
  126. package/src/components/virtualscroll/examples/basic.vue +37 -0
  127. package/src/components/virtualscroll/examples/index.stories.ts +31 -0
  128. package/src/components/virtualscroll/examples/index.ts +1 -0
  129. package/src/composables/element.ts +13 -0
  130. package/src/composables/useCachedUid.ts +48 -0
  131. package/src/composables/useEventListener.ts +14 -0
  132. package/src/composables/useInput.ts +134 -0
  133. package/src/composables/useIsServer.ts +7 -0
  134. package/src/composables/useMount.ts +43 -0
  135. package/src/composables/usePopper.ts +194 -0
  136. package/src/create.ts +34 -0
  137. package/src/css/components.css +14 -0
  138. package/src/css/index.css +4 -0
  139. package/src/css/normalize.css +379 -0
  140. package/src/css/transitions.css +40 -0
  141. package/src/css/variables.css +80 -0
  142. package/src/exports/nuxt.js +33 -0
  143. package/src/exports/nuxt.plugin.js +8 -0
  144. package/src/index.ts +5 -0
  145. package/src/install.ts +8 -0
  146. package/src/shims-vue.d.ts +6 -0
  147. package/src/types/props.ts +3 -0
@@ -0,0 +1,474 @@
1
+ <template>
2
+ <div :class="containerComputedClasses" ref="selectRef" tabindex="0">
3
+ <label v-if="label" :for="id" class="m-select__label" v-on="listeners" :id="`${id}-label`">
4
+ {{ label }}
5
+ </label>
6
+
7
+ <div :class="selectComputedClasses" :id="id" ref="container" @click="openSelect" :aria-disabled="disabled ? 'true' : 'false'" :aria-labelledby="`${id}-label`">
8
+ <div class="m-select__wrapper">
9
+ <div class="m-select__input">
10
+ <div class="m-select__input-wrapper">
11
+ <template v-if="multiple && Array.isArray(selected) && selected.length > 0">
12
+ <span
13
+ v-for="value in selected"
14
+ :key="value"
15
+ class="m-select__tag">
16
+ {{ getSelectedLabel(value) }}
17
+ </span>
18
+ </template>
19
+ <template v-else-if="!multiple && selected">
20
+ {{ getSelectedLabel(selected) }}
21
+ </template>
22
+ <template v-else>
23
+ <span class="m-select__placeholder-text">{{ placeholder }}</span>
24
+ </template>
25
+ </div>
26
+ </div>
27
+ <Teleport :to="'body'">
28
+ <Transition :name="transitionName">
29
+ <div class="m-select__dropdown" v-if="open" ref="popper" :style="computedConsistentWidth" :aria-hidden="open ? 'false' : 'true'">
30
+ <div v-if="searchable" class="m-select__search">
31
+ <MInput v-model="search" placeholder="Search" size="sm" ref="searchRef" />
32
+ </div>
33
+ <div class="m-select__options">
34
+ <div v-for="option, index in computedOptions" :key="index" ref="optionsRef"
35
+ class="m-select__option" :class="{
36
+ 'm-select__option--selected': option.active,
37
+ 'm-select__option--focused': selectedIndex === index
38
+ }"
39
+ @click="option.onClick"
40
+ @mouseover="selectedIndex = index">
41
+ {{ option.label }}
42
+ </div>
43
+ </div>
44
+ </div>
45
+ </Transition>
46
+ </Teleport>
47
+ </div>
48
+ </div>
49
+ <p v-if="inputError" class="m-select__error">
50
+ {{ inputError }}
51
+ </p>
52
+ </div>
53
+ </template>
54
+
55
+ <script lang="ts">
56
+ import { useInput } from "../../composables/useInput";
57
+ import { usePopper, type Placement } from "../../composables/usePopper";
58
+ import type { Size } from "../../types/props";
59
+ import { onClickOutside, useEventListener } from '@vueuse/core';
60
+ import {
61
+ computed,
62
+ defineComponent,
63
+ nextTick,
64
+ ref,
65
+ watch,
66
+ watchEffect,
67
+ type PropType
68
+ } from "vue";
69
+ import { MInput } from "..";
70
+
71
+ export default defineComponent({
72
+ name: 'MSelect',
73
+ emits: useInput.emits(),
74
+ props: {
75
+ ...useInput.props(),
76
+ label: {
77
+ type: String
78
+ },
79
+ placeholder: {
80
+ type: String,
81
+ default: 'Select an option'
82
+ },
83
+ options: {
84
+ type: Array as PropType<Array<any>>,
85
+ required: true
86
+ },
87
+ searchable: {
88
+ type: Boolean,
89
+ default: false
90
+ },
91
+ multiple: {
92
+ type: Boolean,
93
+ default: false
94
+ },
95
+ size: {
96
+ type: String as PropType<Size>,
97
+ default: 'md',
98
+ },
99
+ help: {
100
+ type: String,
101
+ default: ''
102
+ },
103
+ optionValue: {
104
+ type: String,
105
+ default: 'value'
106
+ },
107
+ optionLabel: {
108
+ type: String,
109
+ default: 'label'
110
+ },
111
+ transitionName: {
112
+ type: String,
113
+ default: 'fade'
114
+ },
115
+ consistentWidth: {
116
+ type: Boolean,
117
+ default: true
118
+ },
119
+ disabled: {
120
+ type: Boolean,
121
+ default: false
122
+ },
123
+ loading: {
124
+ type: Boolean,
125
+ default: false
126
+ },
127
+ },
128
+ setup(props, { emit }) {
129
+ const container = ref<HTMLDivElement>();
130
+ const wrapper = ref<HTMLDivElement>();
131
+ const optionsRef = ref<HTMLDivElement[]>();
132
+ const searchRef = ref<HTMLDivElement>();
133
+ const selectRef = ref<HTMLDivElement>();
134
+ const selectedIndex = ref<number | undefined>(0);
135
+ const open = ref<boolean>(false);
136
+ const search = ref<string>('');
137
+ const placement = ref<Placement>("bottom-start");
138
+ const transfer = ref(false);
139
+ const width = ref<number>(0);
140
+ const keyboardNavigation = ref<boolean>(false);
141
+
142
+ const focus = () => { };
143
+
144
+ // TODO: Add debounce on keyboard movement (hover flicks it back up)
145
+ const {
146
+ id,
147
+ insideForm,
148
+ listeners,
149
+ formError,
150
+ formDisabled,
151
+ formLoading
152
+ } = useInput(props, { focus, emit, withListeners: false });
153
+
154
+ const { popper } = usePopper({
155
+ placement,
156
+ transfer,
157
+ reference: container,
158
+ wrapper
159
+ });
160
+
161
+ const disabled = computed(() => formDisabled || props.disabled);
162
+ const loading = computed(() => formLoading || props.loading);
163
+
164
+ const selected = computed<any | any[]>({
165
+ get() {
166
+ if (props.multiple) {
167
+ if (!props.modelValue) {
168
+ return [];
169
+ }
170
+
171
+ if (Array.isArray(props.modelValue)) {
172
+ return props.modelValue;
173
+ }
174
+
175
+ return [props.modelValue];
176
+ }
177
+
178
+ return props.modelValue || null;
179
+ },
180
+ set(value: string | number | []) {
181
+ emit('update:modelValue', value);
182
+ }
183
+ });
184
+
185
+ const computedConsistentWidth = computed(() => {
186
+ if (props.consistentWidth) {
187
+ return {
188
+ width: width.value + 'px'
189
+ }
190
+ }
191
+
192
+ return {};
193
+ });
194
+
195
+ const computedOptions = computed(() => {
196
+ if (!props.options || props.options.length == 0) {
197
+ return [];
198
+ }
199
+
200
+ // Filter options by search
201
+ if (props.searchable && search.value) {
202
+ return props.options.filter((option) => {
203
+ return option[props.optionLabel].toLowerCase().includes(search.value.toLowerCase());
204
+ }).map((option) => {
205
+ if (option[props.optionValue] === undefined) {
206
+ throw new Error('Option value is undefined');
207
+ }
208
+
209
+ if (option[props.optionLabel] === undefined) {
210
+ throw new Error('Option label is undefined');
211
+ }
212
+
213
+ let value = option[props.optionValue];
214
+ let active = false;
215
+ if (props.multiple && Array.isArray(selected.value)) {
216
+ active = selected.value.includes(value);
217
+ } else {
218
+ active = selected.value === value;
219
+ }
220
+
221
+ return {
222
+ value: value,
223
+ label: option[props.optionLabel],
224
+ active: active,
225
+ onClick: () => handleOptionClick(value)
226
+ }
227
+ })
228
+ }
229
+
230
+ return props.options.map((option) => {
231
+ if (option[props.optionValue] === undefined) {
232
+ throw new Error('Option value is undefined');
233
+ }
234
+
235
+ if (option[props.optionLabel] === undefined) {
236
+ throw new Error('Option label is undefined');
237
+ }
238
+
239
+ let value = option[props.optionValue];
240
+ let active = false;
241
+ if (props.multiple && Array.isArray(selected.value)) {
242
+ active = selected.value.includes(value);
243
+ } else {
244
+ active = selected.value === value;
245
+ }
246
+
247
+ return {
248
+ value: value,
249
+ label: option[props.optionLabel],
250
+ active: active,
251
+ onClick: () => handleOptionClick(value)
252
+ }
253
+ })
254
+ });
255
+
256
+ function openSelect() {
257
+ if (disabled.value || loading.value) {
258
+ return;
259
+ }
260
+
261
+ open.value = !open.value;
262
+ }
263
+
264
+ function getSelectedIndex(start: number | undefined, direction = 'down') {
265
+ if (typeof start === 'undefined') {
266
+ start = direction === 'down' ? -1 : 1;
267
+ }
268
+
269
+ if (direction === 'down') {
270
+ let next = start + 1;
271
+
272
+ if (next > computedOptions.value.length - 1) {
273
+ next = 0;
274
+ }
275
+
276
+ selectedIndex.value = next;
277
+ } else {
278
+ let next = start - 1;
279
+
280
+ if (next < 0) {
281
+ next = computedOptions.value.length - 1;
282
+ }
283
+
284
+ selectedIndex.value = next;
285
+ }
286
+ }
287
+
288
+ function handleOptionClick(value: string | number) {
289
+ const option = computedOptions.value.find((item) => item.value === value);
290
+ if (!option) {
291
+ return;
292
+ }
293
+
294
+ if (props.multiple) {
295
+ if (Array.isArray(selected.value)) {
296
+ const index = selected.value.indexOf(value);
297
+
298
+ if (index !== -1) {
299
+ selected.value.splice(index, 1);
300
+ } else {
301
+ selected.value.push(value);
302
+ emit('update:modelValue', selected.value);
303
+ }
304
+ } else {
305
+ selected.value = [value];
306
+ }
307
+ } else {
308
+ selected.value = value;
309
+ open.value = false;
310
+ }
311
+ }
312
+
313
+ function handleKeyDown(e: Event) {
314
+ if (computedOptions.value.length === 0) {
315
+ return;
316
+ }
317
+
318
+ if (props.disabled) {
319
+ return;
320
+ }
321
+
322
+ keyboardNavigation.value = true;
323
+
324
+ const event: KeyboardEvent = e as KeyboardEvent;
325
+
326
+ switch (event.code) {
327
+ case 'ArrowDown':
328
+ e.preventDefault();
329
+ if (!open.value) {
330
+ open.value = true;
331
+ return;
332
+ }
333
+
334
+ getSelectedIndex(selectedIndex.value, 'down');
335
+ break;
336
+ case 'ArrowUp':
337
+ e.preventDefault();
338
+ if (!open.value) {
339
+ return;
340
+ }
341
+
342
+ getSelectedIndex(selectedIndex.value, 'up');
343
+ break;
344
+ case 'Enter':
345
+ e.preventDefault();
346
+ e.stopPropagation();
347
+
348
+ if (!open.value) {
349
+ open.value = true;
350
+ return;
351
+ }
352
+
353
+ if (typeof selectedIndex.value !== 'undefined') {
354
+ handleOptionClick(computedOptions.value[selectedIndex.value].value);
355
+ if (!props.multiple) {
356
+ open.value = false;
357
+ }
358
+ }
359
+ break;
360
+ case 'Escape':
361
+ e.preventDefault();
362
+ e.stopPropagation();
363
+
364
+ if (!open.value) {
365
+ return;
366
+ }
367
+
368
+ open.value = false;
369
+ break;
370
+ }
371
+ }
372
+
373
+ const transitionName = computed(() => {
374
+ return `m-transition__${props.transitionName}`;
375
+ });
376
+
377
+ const containerComputedClasses = computed(() => {
378
+ return [
379
+ 'm-select__container',
380
+ {
381
+ 'm-form__input': insideForm
382
+ }
383
+ ]
384
+ });
385
+
386
+ const selectComputedClasses = computed(() => {
387
+ return [
388
+ 'm-select',
389
+ {
390
+ [`m-select--${props.size}`]: props.size,
391
+ 'm-select--disabled': disabled.value || loading.value,
392
+ 'm-select--focused': open.value,
393
+ 'm-select--multiple': props.multiple,
394
+ 'm-select--error': formError.value
395
+ }
396
+ ]
397
+ });
398
+
399
+ function getSelectedLabel(value: string | number | []) {
400
+ const option = props.options.find((i) => i[props.optionValue] === value)
401
+ if (!option) {
402
+ return null;
403
+ }
404
+
405
+ return option[props.optionLabel];
406
+ }
407
+
408
+ useEventListener(selectRef, 'keydown', handleKeyDown)
409
+ useEventListener(searchRef, 'keydown', handleKeyDown)
410
+
411
+ onClickOutside(container, () => {
412
+ open.value = false;
413
+ }, { ignore: [searchRef, popper] });
414
+
415
+ watchEffect(() => {
416
+ if (container.value && open.value) {
417
+ width.value = container.value.offsetWidth;
418
+ }
419
+ });
420
+
421
+ watch(selectedIndex, (index: number | undefined) => {
422
+ if (!keyboardNavigation.value) {
423
+ return;
424
+ }
425
+ if (typeof index !== 'undefined' && optionsRef.value) {
426
+ optionsRef.value[index].scrollIntoView({ block: 'nearest', inline: 'nearest' });
427
+ }
428
+ })
429
+
430
+ watch(open, (value: boolean) => {
431
+ nextTick(() => {
432
+ if (value && optionsRef.value) {
433
+ const index = computedOptions.value.findIndex((i) => i.value === selected.value);
434
+ if (index !== -1) {
435
+ optionsRef.value[index].scrollIntoView({ block: 'center', inline: 'nearest' });
436
+ selectedIndex.value = index;
437
+ }
438
+ }
439
+
440
+ keyboardNavigation.value = false;
441
+ });
442
+ });
443
+
444
+ const inputError = computed(() => {
445
+ return formError.value || props.error;
446
+ });
447
+
448
+ return {
449
+ container,
450
+ optionsRef,
451
+ searchRef,
452
+ selectRef,
453
+ popper,
454
+
455
+ id,
456
+ open,
457
+ selected,
458
+ computedOptions,
459
+ selectedIndex,
460
+ search,
461
+
462
+ containerComputedClasses,
463
+ selectComputedClasses,
464
+ computedConsistentWidth,
465
+
466
+ getSelectedLabel,
467
+ openSelect,
468
+ inputError,
469
+ transitionName,
470
+ listeners
471
+ }
472
+ }
473
+ });
474
+ </script>
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <MSelect
3
+ v-model="value"
4
+ :options="options"
5
+ :option-value="'id'"
6
+ :option-label="'name'"
7
+ />
8
+ </template>
9
+
10
+ <script lang="ts" setup>
11
+ import { ref } from "vue";
12
+
13
+ // make list of options with 1000 items
14
+ const options = Array.from({ length: 50 }, (_, i) => ({
15
+ id: i,
16
+ name: `Option ${i+1}`
17
+ }));
18
+
19
+ const value = ref();
20
+ </script>
@@ -0,0 +1,22 @@
1
+ import MSelect from "../Select.vue";
2
+ import {
3
+ MSelectBasic,
4
+ MSelectSearchable,
5
+ MSelectMultiple,
6
+ } from "./index";
7
+
8
+ import {
9
+ createStory, sizeArgType
10
+ } from "@/__storybook__";
11
+
12
+ export default {
13
+ component: MSelect,
14
+ title: 'Components/Select',
15
+ argTypes: {
16
+ ...sizeArgType()
17
+ }
18
+ }
19
+
20
+ export const Basic = createStory(MSelectBasic);
21
+ export const Searchable = createStory(MSelectSearchable);
22
+ export const Multiple = createStory(MSelectMultiple);
@@ -0,0 +1,3 @@
1
+ export { default as MSelectBasic } from './basic.vue';
2
+ export { default as MSelectSearchable } from './searchable.vue';
3
+ export { default as MSelectMultiple } from './multiple.vue';
@@ -0,0 +1,22 @@
1
+ <template>
2
+ <MSelect
3
+ v-model="value"
4
+ :options="options"
5
+ :option-value="'id'"
6
+ :option-label="'name'"
7
+ multiple
8
+ searchable
9
+ />
10
+ {{ value }}
11
+ </template>
12
+
13
+ <script lang="ts" setup>
14
+ import { ref } from "vue";
15
+
16
+ const options = Array.from({ length: 50 }, (_, i) => ({
17
+ id: i,
18
+ name: `Option ${i+1}`
19
+ }));
20
+
21
+ const value = ref([]);
22
+ </script>
@@ -0,0 +1,20 @@
1
+ <template>
2
+ <MSelect
3
+ v-model="value"
4
+ :options="options"
5
+ :option-value="'id'"
6
+ :option-label="'name'"
7
+ searchable
8
+ />
9
+ </template>
10
+
11
+ <script lang="ts" setup>
12
+ import { ref } from "vue";
13
+
14
+ const options = Array.from({ length: 50 }, (_, i) => ({
15
+ id: i,
16
+ name: `Option ${i+1}`
17
+ }));
18
+
19
+ const value = ref();
20
+ </script>
@@ -0,0 +1,73 @@
1
+ .m-skeleton {
2
+ --skeleton-border-radius: 4px;
3
+ --skeleton-height: 100%;
4
+ --skeleton-width: 100%;
5
+ position: relative;
6
+ display: flex;
7
+ background: linear-gradient(-90deg, var(--gray-100) 0%, var(--gray-200) 50%, var(--gray-100) 100%);
8
+ background-size: 400% 400%;
9
+ overflow: hidden;
10
+ white-space: nowrap;
11
+ vertical-align: middle;
12
+ justify-content: center;
13
+ align-items: center;
14
+ box-sizing: content-box;
15
+ border-radius: var(--skeleton-border-radius);
16
+ font-weight: 600;
17
+ user-select: none;
18
+ width: var(--skeleton-width);
19
+ height: var(--skeleton-height);
20
+ }
21
+
22
+ .m-skeleton--thumb {
23
+ --skeleton-border-radius: 4px;
24
+ --skeleton-height: 128px;
25
+ --skeleton-width: 500px;
26
+ }
27
+
28
+ .m-skeleton--heading {
29
+ --skeleton-border-radius: 4px;
30
+ --skeleton-height: 32px;
31
+ --skeleton-width: 500px;
32
+ }
33
+
34
+ .m-skeleton--line {
35
+ --skeleton-border-radius: 4px;
36
+ --skeleton-height: 20px;
37
+ --skeleton-width: 500px;
38
+ }
39
+
40
+ .m-skeleton--circle {
41
+ --skeleton-border-radius: 50%;
42
+ --skeleton-height: 40px;
43
+ --skeleton-width: 40px;
44
+ }
45
+
46
+ .m-skeleton--block {
47
+ --skeleton-border-radius: 4px;
48
+ --skeleton-height: 100%;
49
+ --skeleton-width: 100%;
50
+ }
51
+
52
+ .m-skeleton--pulse {
53
+ animation: skeleton-pulse 1.5s ease-in-out infinite;
54
+ -webkit-animation: skeleton-pulse 1.5s ease-in-out infinite;
55
+ }
56
+
57
+ @keyframes skeleton-pulse {
58
+ 0% {
59
+ background-position: 0 0;
60
+ }
61
+ 100% {
62
+ background-position: -135% 0;
63
+ }
64
+ }
65
+
66
+ @-webkit-keyframes skeleton-pulse {
67
+ 0% {
68
+ background-position: 0 0;
69
+ }
70
+ 100% {
71
+ background-position: -135% 0;
72
+ }
73
+ }
@@ -0,0 +1,47 @@
1
+ <template>
2
+ <component
3
+ :is="tag"
4
+ :class="computedClasses"
5
+ >
6
+ &nbsp;
7
+ </component>
8
+ </template>
9
+
10
+ <script lang="ts">
11
+ import { computed, defineComponent } from 'vue';
12
+
13
+ export default defineComponent({
14
+ name: "MSkeleton",
15
+ props: {
16
+ tag: {
17
+ type: String,
18
+ default: "div"
19
+ },
20
+ variant: {
21
+ type: String,
22
+ default: "line",
23
+ validator: (value: string) => {
24
+ return ["block", "thumb", "heading", "line", "circle"].includes(value);
25
+ }
26
+ },
27
+ animation: {
28
+ type: String,
29
+ default: "pulse"
30
+ }
31
+ },
32
+ setup(props) {
33
+
34
+ const computedClasses = computed(() => {
35
+ return {
36
+ "m-skeleton": true,
37
+ [`m-skeleton--${props.variant}`]: true,
38
+ [`m-skeleton--${props.animation}`]: true
39
+ }
40
+ });
41
+
42
+ return {
43
+ computedClasses
44
+ }
45
+ }
46
+ })
47
+ </script>
@@ -0,0 +1,3 @@
1
+ <template>
2
+ <MSkeleton />
3
+ </template>