sprintify-ui 0.0.0

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 (176) hide show
  1. package/README.md +188 -0
  2. package/dist/types/src/components/BaseAlert.vue.d.ts +51 -0
  3. package/dist/types/src/components/BaseAutocomplete.vue.d.ts +268 -0
  4. package/dist/types/src/components/BaseAutocompleteFetch.vue.d.ts +273 -0
  5. package/dist/types/src/components/BaseAvatar.vue.d.ts +126 -0
  6. package/dist/types/src/components/BaseBadge.vue.d.ts +94 -0
  7. package/dist/types/src/components/BaseBelongsTo.vue.d.ts +268 -0
  8. package/dist/types/src/components/BaseBoolean.vue.d.ts +64 -0
  9. package/dist/types/src/components/BaseBreadcrumbs.vue.d.ts +66 -0
  10. package/dist/types/src/components/BaseButton.vue.d.ts +23 -0
  11. package/dist/types/src/components/BaseCard.vue.d.ts +74 -0
  12. package/dist/types/src/components/BaseCardRow.vue.d.ts +16 -0
  13. package/dist/types/src/components/BaseClipboard.vue.d.ts +74 -0
  14. package/dist/types/src/components/BaseContainer.vue.d.ts +34 -0
  15. package/dist/types/src/components/BaseCounter.vue.d.ts +125 -0
  16. package/dist/types/src/components/BaseDataIterator.vue.d.ts +345 -0
  17. package/dist/types/src/components/BaseDataTable.vue.d.ts +657 -0
  18. package/dist/types/src/components/BaseDataTableToggleColumns.vue.d.ts +1281 -0
  19. package/dist/types/src/components/BaseDatePicker.vue.d.ts +190 -0
  20. package/dist/types/src/components/BaseDateSelect.vue.d.ts +171 -0
  21. package/dist/types/src/components/BaseDescriptionList.vue.d.ts +48 -0
  22. package/dist/types/src/components/BaseDescriptionListItem.vue.d.ts +49 -0
  23. package/dist/types/src/components/BaseDialog.vue.d.ts +160 -0
  24. package/dist/types/src/components/BaseFilePicker.vue.d.ts +44 -0
  25. package/dist/types/src/components/BaseFileUploader.vue.d.ts +220 -0
  26. package/dist/types/src/components/BaseInput.vue.d.ts +209 -0
  27. package/dist/types/src/components/BaseInputLabel.vue.d.ts +31 -0
  28. package/dist/types/src/components/BaseLoadingCover.vue.d.ts +166 -0
  29. package/dist/types/src/components/BaseLoadingPage.vue.d.ts +2 -0
  30. package/dist/types/src/components/BaseMediaLibrary.vue.d.ts +269 -0
  31. package/dist/types/src/components/BaseMediaLibraryItem.vue.d.ts +75 -0
  32. package/dist/types/src/components/BaseMenu.vue.d.ts +117 -0
  33. package/dist/types/src/components/BaseMenuItem.vue.d.ts +147 -0
  34. package/dist/types/src/components/BaseModalCenter.vue.d.ts +141 -0
  35. package/dist/types/src/components/BaseModalSide.vue.d.ts +141 -0
  36. package/dist/types/src/components/BaseNavbar.vue.d.ts +79 -0
  37. package/dist/types/src/components/BaseNavbarItem.vue.d.ts +80 -0
  38. package/dist/types/src/components/BaseNavbarItemContent.vue.d.ts +127 -0
  39. package/dist/types/src/components/BasePagination.vue.d.ts +25 -0
  40. package/dist/types/src/components/BasePaginationSimple.vue.d.ts +25 -0
  41. package/dist/types/src/components/BasePanel.vue.d.ts +31 -0
  42. package/dist/types/src/components/BasePassword.vue.d.ts +66 -0
  43. package/dist/types/src/components/BaseProcessRing.vue.d.ts +36 -0
  44. package/dist/types/src/components/BaseReadMore.vue.d.ts +74 -0
  45. package/dist/types/src/components/BaseSelect.vue.d.ts +55 -0
  46. package/dist/types/src/components/BaseSideNavigation.vue.d.ts +48 -0
  47. package/dist/types/src/components/BaseSideNavigationItem.vue.d.ts +92 -0
  48. package/dist/types/src/components/BaseSkeleton.vue.d.ts +93 -0
  49. package/dist/types/src/components/BaseSpinner.vue.d.ts +2 -0
  50. package/dist/types/src/components/BaseSwitch.vue.d.ts +39 -0
  51. package/dist/types/src/components/BaseSystemAlert.vue.d.ts +141 -0
  52. package/dist/types/src/components/BaseTabItem.vue.d.ts +70 -0
  53. package/dist/types/src/components/BaseTable.vue.d.ts +467 -0
  54. package/dist/types/src/components/BaseTableColumn.vue.d.ts +164 -0
  55. package/dist/types/src/components/BaseTabs.vue.d.ts +48 -0
  56. package/dist/types/src/components/BaseTagAutocomplete.vue.d.ts +274 -0
  57. package/dist/types/src/components/BaseTagAutocompleteFetch.vue.d.ts +251 -0
  58. package/dist/types/src/components/BaseTextarea.vue.d.ts +228 -0
  59. package/dist/types/src/components/BaseTextareaAutoresize.vue.d.ts +44 -0
  60. package/dist/types/src/components/BaseTitle.vue.d.ts +45 -0
  61. package/dist/types/src/components/BaseWordCount.vue.d.ts +31 -0
  62. package/dist/types/src/components/SlotComponent.d.ts +43 -0
  63. package/dist/types/src/components/index.d.ts +2 -0
  64. package/dist/types/src/composables/breakpoints.d.ts +12 -0
  65. package/dist/types/src/composables/modal.d.ts +6 -0
  66. package/dist/types/src/constants/MyConstants.d.ts +1 -0
  67. package/dist/types/src/constants/index.d.ts +2 -0
  68. package/dist/types/src/index.d.ts +253 -0
  69. package/dist/types/src/types/Media.d.ts +8 -0
  70. package/dist/types/src/types/UploadedFile.d.ts +9 -0
  71. package/dist/types/src/types/User.d.ts +6 -0
  72. package/dist/types/src/types/types.d.ts +88 -0
  73. package/dist/types/src/utils/fileSizeFormat.d.ts +1 -0
  74. package/dist/types/src/utils/index.d.ts +4 -0
  75. package/dist/types/src/utils/scrollPreventer.d.ts +4 -0
  76. package/dist/types/src/utils/toHumanList.d.ts +1 -0
  77. package/package.json +99 -0
  78. package/src/assets/button.css +80 -0
  79. package/src/assets/form.css +15 -0
  80. package/src/assets/main.css +3 -0
  81. package/src/assets/pikaday.css +134 -0
  82. package/src/assets/tailwind.css +5 -0
  83. package/src/components/BaseAlert.stories.js +52 -0
  84. package/src/components/BaseAlert.vue +152 -0
  85. package/src/components/BaseAutocomplete.stories.js +127 -0
  86. package/src/components/BaseAutocomplete.vue +376 -0
  87. package/src/components/BaseAutocompleteFetch.stories.js +121 -0
  88. package/src/components/BaseAutocompleteFetch.vue +185 -0
  89. package/src/components/BaseAvatar.stories.js +39 -0
  90. package/src/components/BaseAvatar.vue +92 -0
  91. package/src/components/BaseBadge.stories.js +61 -0
  92. package/src/components/BaseBadge.vue +70 -0
  93. package/src/components/BaseBelongsTo.stories.js +130 -0
  94. package/src/components/BaseBelongsTo.vue +122 -0
  95. package/src/components/BaseBoolean.stories.js +35 -0
  96. package/src/components/BaseBoolean.vue +29 -0
  97. package/src/components/BaseBreadcrumbs.stories.js +45 -0
  98. package/src/components/BaseBreadcrumbs.vue +78 -0
  99. package/src/components/BaseButton.stories.js +80 -0
  100. package/src/components/BaseButton.vue +39 -0
  101. package/src/components/BaseCard.stories.js +61 -0
  102. package/src/components/BaseCard.vue +49 -0
  103. package/src/components/BaseCardRow.vue +34 -0
  104. package/src/components/BaseClipboard.stories.js +31 -0
  105. package/src/components/BaseClipboard.vue +96 -0
  106. package/src/components/BaseContainer.stories.js +34 -0
  107. package/src/components/BaseContainer.vue +50 -0
  108. package/src/components/BaseCounter.stories.js +32 -0
  109. package/src/components/BaseCounter.vue +72 -0
  110. package/src/components/BaseDataIterator.stories.js +90 -0
  111. package/src/components/BaseDataIterator.vue +658 -0
  112. package/src/components/BaseDataTable.stories.js +95 -0
  113. package/src/components/BaseDataTable.vue +489 -0
  114. package/src/components/BaseDataTableToggleColumns.vue +69 -0
  115. package/src/components/BaseDatePicker.stories.js +53 -0
  116. package/src/components/BaseDatePicker.vue +166 -0
  117. package/src/components/BaseDateSelect.vue +192 -0
  118. package/src/components/BaseDescriptionList.vue +11 -0
  119. package/src/components/BaseDescriptionListItem.vue +12 -0
  120. package/src/components/BaseDialog.vue +104 -0
  121. package/src/components/BaseFilePicker.vue +101 -0
  122. package/src/components/BaseFileUploader.vue +166 -0
  123. package/src/components/BaseInput.vue +82 -0
  124. package/src/components/BaseInputLabel.vue +26 -0
  125. package/src/components/BaseLoadingCover.vue +84 -0
  126. package/src/components/BaseLoadingPage.vue +19 -0
  127. package/src/components/BaseMediaLibrary.vue +281 -0
  128. package/src/components/BaseMediaLibraryItem.vue +92 -0
  129. package/src/components/BaseMenu.vue +114 -0
  130. package/src/components/BaseMenuItem.vue +93 -0
  131. package/src/components/BaseModalCenter.vue +107 -0
  132. package/src/components/BaseModalSide.vue +112 -0
  133. package/src/components/BaseNavbar.vue +72 -0
  134. package/src/components/BaseNavbarItem.vue +72 -0
  135. package/src/components/BaseNavbarItemContent.vue +57 -0
  136. package/src/components/BasePagination.vue +82 -0
  137. package/src/components/BasePaginationSimple.vue +60 -0
  138. package/src/components/BasePanel.vue +39 -0
  139. package/src/components/BasePassword.vue +73 -0
  140. package/src/components/BaseProcessRing.vue +56 -0
  141. package/src/components/BaseReadMore.vue +72 -0
  142. package/src/components/BaseSelect.vue +59 -0
  143. package/src/components/BaseSideNavigation.vue +7 -0
  144. package/src/components/BaseSideNavigationItem.vue +42 -0
  145. package/src/components/BaseSkeleton.vue +24 -0
  146. package/src/components/BaseSpinner.vue +47 -0
  147. package/src/components/BaseSwitch.vue +87 -0
  148. package/src/components/BaseSystemAlert.vue +86 -0
  149. package/src/components/BaseTabItem.vue +30 -0
  150. package/src/components/BaseTable.vue +781 -0
  151. package/src/components/BaseTableColumn.vue +109 -0
  152. package/src/components/BaseTabs.vue +12 -0
  153. package/src/components/BaseTagAutocomplete.vue +385 -0
  154. package/src/components/BaseTagAutocompleteFetch.vue +154 -0
  155. package/src/components/BaseTextarea.vue +73 -0
  156. package/src/components/BaseTextareaAutoresize.vue +117 -0
  157. package/src/components/BaseTitle.vue +80 -0
  158. package/src/components/BaseWordCount.vue +36 -0
  159. package/src/components/SlotComponent.ts +37 -0
  160. package/src/components/index.ts +5 -0
  161. package/src/composables/breakpoints.ts +6 -0
  162. package/src/composables/modal.ts +77 -0
  163. package/src/constants/MyConstants.ts +1 -0
  164. package/src/constants/index.ts +5 -0
  165. package/src/env.d.ts +15 -0
  166. package/src/index.ts +70 -0
  167. package/src/lang/en.json +56 -0
  168. package/src/lang/fr.json +56 -0
  169. package/src/types/Media.ts +9 -0
  170. package/src/types/UploadedFile.ts +10 -0
  171. package/src/types/User.ts +7 -0
  172. package/src/types/types.ts +112 -0
  173. package/src/utils/fileSizeFormat.ts +15 -0
  174. package/src/utils/index.ts +5 -0
  175. package/src/utils/scrollPreventer.ts +21 -0
  176. package/src/utils/toHumanList.ts +20 -0
@@ -0,0 +1,376 @@
1
+ <template>
2
+ <div>
3
+ <div class="relative">
4
+ <div class="relative">
5
+ <input
6
+ ref="inputElement"
7
+ :value="keywords"
8
+ type="text"
9
+ :class="inputClass + ' pl-9'"
10
+ :placeholder="
11
+ placeholder ? placeholder : $t('sui.autocomplete_placeholder')
12
+ "
13
+ class="rounded disabled:cursor-not-allowed disabled:text-slate-300"
14
+ autocomplete="off"
15
+ :disabled="disabled"
16
+ @focus="onTextFocus"
17
+ @blur="onTextBlur"
18
+ @input="onTextInput"
19
+ @keydown="onTextKeydown"
20
+ />
21
+ <div
22
+ class="pointer-events-none absolute top-0 left-0 flex h-full items-center justify-center pl-2.5"
23
+ >
24
+ <Icon
25
+ class="h-5 w-5 text-slate-400"
26
+ icon="heroicons:magnifying-glass-solid"
27
+ />
28
+ </div>
29
+ </div>
30
+
31
+ <div
32
+ v-if="normalizedModelValue && !disabled"
33
+ class="absolute top-0 right-0 flex h-full items-center p-1"
34
+ >
35
+ <button
36
+ type="button"
37
+ class="group flex h-full items-center rounded p-1.5 enabled:hover:bg-slate-100"
38
+ @click="clear()"
39
+ >
40
+ <Icon
41
+ icon="heroicons:x-mark"
42
+ class="h-5 w-5 text-slate-500 group-hover:text-slate-700"
43
+ />
44
+ </button>
45
+ </div>
46
+ </div>
47
+
48
+ <div class="relative">
49
+ <div
50
+ v-show="showDropdown"
51
+ class="absolute top-1 z-[1] min-h-[110px] w-full overflow-hidden rounded border border-slate-300 bg-white shadow-md"
52
+ >
53
+ <div ref="dropdown" class="max-h-[214px] w-full overflow-y-auto p-1">
54
+ <slot v-if="filteredNormalizedOptions.length == 0" name="empty">
55
+ <div
56
+ class="flex items-center justify-center px-5 py-10 text-center text-slate-600"
57
+ >
58
+ {{ $t('sui.nothing_found') }}
59
+ </div>
60
+ </slot>
61
+
62
+ <ul v-else>
63
+ <li
64
+ v-for="option in filteredNormalizedOptions"
65
+ :key="option.value"
66
+ class="block"
67
+ >
68
+ <button
69
+ class="block w-full cursor-pointer appearance-none border-none text-left focus:outline-none"
70
+ type="button"
71
+ tabindex="-1"
72
+ @click="onSelect(option)"
73
+ @mousedown="dontLooseFocus"
74
+ >
75
+ <slot
76
+ name="option"
77
+ :option="option.option"
78
+ :selected="
79
+ normalizedModelValue &&
80
+ normalizedModelValue.value == option.value
81
+ "
82
+ :active="optionActive && optionActive.value == option.value"
83
+ >
84
+ <div
85
+ class="rounded px-2 py-1 text-sm"
86
+ :class="optionClass(option)"
87
+ >
88
+ {{ option.label }}
89
+ </div>
90
+ </slot>
91
+ </button>
92
+ </li>
93
+ </ul>
94
+
95
+ <slot
96
+ :options="filteredNormalizedOptions"
97
+ :dont-loose-focus="dontLooseFocus"
98
+ name="footer"
99
+ />
100
+ </div>
101
+
102
+ <Transition>
103
+ <div
104
+ v-if="loading"
105
+ class="absolute inset-0 h-full w-full space-y-1 bg-white p-2"
106
+ >
107
+ <div class="space-y-1">
108
+ <BaseSkeleton class="h-7 w-full" delay="0ms"></BaseSkeleton>
109
+ <BaseSkeleton
110
+ class="h-7 w-full opacity-70"
111
+ delay="50ms"
112
+ ></BaseSkeleton>
113
+ <BaseSkeleton
114
+ class="h-7 w-full opacity-30"
115
+ delay="100ms"
116
+ ></BaseSkeleton>
117
+ </div>
118
+ </div>
119
+ </Transition>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ </template>
124
+
125
+ <script lang="ts" setup>
126
+ import { get } from 'lodash';
127
+ import { PropType, Ref } from 'vue';
128
+ import {
129
+ NormalizedOption,
130
+ Option,
131
+ Selection,
132
+ NormalizedSelection,
133
+ } from '@/types/types';
134
+ import { useInfiniteScroll } from '@vueuse/core';
135
+ import BaseSkeleton from './BaseSkeleton.vue';
136
+
137
+ const props = defineProps({
138
+ modelValue: {
139
+ required: true,
140
+ type: [Object, null, undefined] as PropType<Selection>,
141
+ },
142
+ options: {
143
+ required: true,
144
+ type: Array as PropType<Option[]>,
145
+ },
146
+ labelKey: {
147
+ required: true,
148
+ type: String,
149
+ },
150
+ valueKey: {
151
+ required: true,
152
+ type: String,
153
+ },
154
+ inputClass: {
155
+ default: undefined,
156
+ type: String,
157
+ },
158
+ placeholder: {
159
+ default: undefined,
160
+ type: String,
161
+ },
162
+ loading: {
163
+ default: false,
164
+ type: Boolean,
165
+ },
166
+ required: {
167
+ default: false,
168
+ type: Boolean,
169
+ },
170
+ disabled: {
171
+ default: false,
172
+ type: Boolean,
173
+ },
174
+ filter: {
175
+ default: undefined,
176
+ type: Function as PropType<(option: NormalizedOption) => boolean>,
177
+ },
178
+ });
179
+
180
+ const emit = defineEmits([
181
+ 'update:modelValue',
182
+ 'typing',
183
+ 'focus',
184
+ 'scrollBottom',
185
+ 'clear',
186
+ ]);
187
+
188
+ const timerId = ref(0);
189
+ const keywords = ref('');
190
+ const showDropdown = ref(false);
191
+ const selectionIndex = ref(0);
192
+ const inputElement = ref(null) as Ref<HTMLInputElement | null>;
193
+ const dropdown = ref(null) as Ref<HTMLElement | null>;
194
+
195
+ onMounted(() => {
196
+ useInfiniteScroll(
197
+ dropdown.value,
198
+ () => {
199
+ emit('scrollBottom');
200
+ },
201
+ { distance: 10 }
202
+ );
203
+ });
204
+
205
+ const optionActive = computed(() => {
206
+ return (
207
+ filteredNormalizedOptions.value[
208
+ Math.min(selectionIndex.value, filteredNormalizedOptions.value.length - 1)
209
+ ] ?? null
210
+ );
211
+ });
212
+
213
+ const normalizedModelValue = computed(() => {
214
+ if (!props.modelValue) {
215
+ return null;
216
+ }
217
+ return {
218
+ label: props.modelValue[props.labelKey] as string,
219
+ value: props.modelValue[props.valueKey] as string | number,
220
+ option: props.modelValue,
221
+ };
222
+ });
223
+
224
+ watch(
225
+ () => normalizedModelValue.value,
226
+ () => {
227
+ if (normalizedModelValue.value) {
228
+ keywords.value = normalizedModelValue.value?.label;
229
+ } else {
230
+ keywords.value = '';
231
+ }
232
+ },
233
+ { immediate: true }
234
+ );
235
+
236
+ const normalizedOptions = computed(() => {
237
+ return props.options.map((option) => {
238
+ return {
239
+ label: option[props.labelKey] as string,
240
+ value: option[props.valueKey] as string | number,
241
+ option: option,
242
+ } as NormalizedOption;
243
+ });
244
+ });
245
+
246
+ const filteredNormalizedOptions = computed((): NormalizedOption[] => {
247
+ return normalizedOptions.value.filter((option) => {
248
+ if (props.filter !== undefined) {
249
+ return props.filter(option);
250
+ }
251
+ if (!option.label) {
252
+ return false;
253
+ }
254
+ return option.label.toLowerCase().includes(keywords.value.toLowerCase());
255
+ });
256
+ });
257
+
258
+ const dontLooseFocus = (event: Event, next: null | (() => void) = null) => {
259
+ event.preventDefault();
260
+ inputElement.value?.focus();
261
+ if (next) {
262
+ next();
263
+ }
264
+ };
265
+
266
+ const onTextFocus = () => {
267
+ clearTimeout(timerId.value);
268
+ showDropdown.value = true;
269
+ emit('focus');
270
+ };
271
+
272
+ const onTextBlur = () => {
273
+ timerId.value = setTimeout(() => {
274
+ showDropdown.value = false;
275
+ if (normalizedModelValue.value) {
276
+ setKeywordsWithoutEvent(normalizedModelValue.value.label);
277
+ }
278
+ }, 10);
279
+ };
280
+
281
+ const onTextInput = (event: Event) => {
282
+ selectionIndex.value = 0;
283
+ setKeywords(get(event, 'target.value') + '');
284
+ dropdown.value?.scrollTo({
285
+ top: 0,
286
+ });
287
+
288
+ if (keywords.value == '') {
289
+ update(null);
290
+ }
291
+ };
292
+
293
+ const onTextKeydown = (event: KeyboardEvent) => {
294
+ const key = event.key;
295
+
296
+ if (props.loading) {
297
+ return;
298
+ }
299
+
300
+ if (key === 'ArrowDown') {
301
+ if (selectionIndex.value < props.options.length - 1) {
302
+ selectionIndex.value++;
303
+ } else {
304
+ selectionIndex.value = 0;
305
+ }
306
+ return;
307
+ }
308
+
309
+ if (key === 'ArrowUp') {
310
+ if (selectionIndex.value > 0) {
311
+ selectionIndex.value--;
312
+ } else {
313
+ selectionIndex.value = Math.max(0, props.options.length - 1);
314
+ }
315
+ return;
316
+ }
317
+
318
+ if (key === 'Enter') {
319
+ event.preventDefault();
320
+ if (optionActive.value) {
321
+ onSelect(optionActive.value);
322
+ }
323
+ return;
324
+ }
325
+ };
326
+
327
+ const optionClass = (option: NormalizedOption) => {
328
+ const active = optionActive.value && optionActive.value.value == option.value;
329
+ const selected =
330
+ normalizedModelValue.value &&
331
+ normalizedModelValue.value.value == option.value;
332
+
333
+ if (selected) {
334
+ if (active) {
335
+ return 'bg-blue-600 hover:bg-blue-700 text-white';
336
+ }
337
+
338
+ return 'bg-blue-500 hover:bg-blue-600 text-white';
339
+ }
340
+
341
+ if (active) {
342
+ return 'bg-slate-200 hover:bg-slate-300';
343
+ }
344
+
345
+ return 'bg-white hover:bg-slate-100';
346
+ };
347
+
348
+ const clear = () => {
349
+ emit('clear');
350
+ setKeywordsWithoutEvent('');
351
+ update(null);
352
+ inputElement.value?.focus();
353
+ };
354
+
355
+ const onSelect = (normalizedModelValue: NormalizedSelection) => {
356
+ update(normalizedModelValue);
357
+ inputElement.value?.blur();
358
+ };
359
+
360
+ const update = (normalizedSelection: NormalizedSelection) => {
361
+ const selection = normalizedSelection ? normalizedSelection.option : null;
362
+ if (normalizedSelection) {
363
+ setKeywordsWithoutEvent(normalizedSelection.label);
364
+ }
365
+ emit('update:modelValue', selection);
366
+ };
367
+
368
+ const setKeywordsWithoutEvent = (input: string) => {
369
+ keywords.value = input;
370
+ };
371
+
372
+ const setKeywords = (input: string) => {
373
+ keywords.value = input;
374
+ emit('typing', input);
375
+ };
376
+ </script>
@@ -0,0 +1,121 @@
1
+ import BaseAutocompleteFetch from './BaseAutocompleteFetch.vue';
2
+
3
+ export default {
4
+ title: 'Form/BaseAutocompleteFetch',
5
+ component: BaseAutocompleteFetch,
6
+ argTypes: {},
7
+ args: {
8
+ url: 'https://effettandem.com/api/content/articles',
9
+ labelKey: 'title',
10
+ valueKey: 'id',
11
+ inputClass: 'w-full border-slate-300',
12
+ disabled: false,
13
+ },
14
+ decorators: [() => ({ template: '<div class="mb-36"><story/></div>' })],
15
+ };
16
+
17
+ const Template = (args) => {
18
+ return {
19
+ components: { BaseAutocompleteFetch },
20
+ setup() {
21
+ const value = ref(null);
22
+ return { args, value };
23
+ },
24
+ template: `
25
+ <BaseAutocompleteFetch
26
+ v-model="value"
27
+ v-bind="args"
28
+ ></BaseAutocompleteFetch>
29
+ <p class="mt-5 text-sm">Value: <span class="bg-slate-200 font-mono px-1 py-px rounded">{{ value ?? 'NULL' }}</span></p>
30
+ `,
31
+ };
32
+ };
33
+
34
+ export const Demo = Template.bind({});
35
+ Demo.args = {};
36
+
37
+ export const Disabled = Template.bind({});
38
+ Disabled.args = {
39
+ modelValue: { label: 'Dark Maul', value: '1' },
40
+ labelKey: 'label',
41
+ valueKey: 'value',
42
+ disabled: true,
43
+ };
44
+
45
+ export const SlotOption = (args) => {
46
+ return {
47
+ components: { BaseAutocompleteFetch },
48
+ setup() {
49
+ const value = ref(null);
50
+ return { args, value };
51
+ },
52
+ template: `
53
+ <div class="mb-20">
54
+ <BaseAutocompleteFetch
55
+ v-model="value"
56
+ v-bind="args"
57
+ >
58
+ <template #option="{ option, active, selected }">
59
+ <div
60
+ class="rounded px-2 py-1"
61
+ :class="{
62
+ 'hover:bg-slate-100': !active && !selected,
63
+ 'bg-slate-200 hover:bg-slate-300': active && !selected,
64
+ 'bg-blue-500 text-white hover:bg-blue-600': !active && selected,
65
+ 'bg-blue-600 text-white hover:bg-blue-700': active && selected,
66
+ }"
67
+ >
68
+ <p class="text-sm font-medium">{{ option.title }}</p>
69
+ <p class="opacity-60 text-xs">{{ option.owner?.name }}</p>
70
+ </div>
71
+ </template>
72
+ </BaseAutocompleteFetch>
73
+ </div>
74
+ `,
75
+ };
76
+ };
77
+
78
+ export const SlotFooter = (args) => {
79
+ return {
80
+ components: { BaseAutocompleteFetch },
81
+ setup() {
82
+ const value = ref(null);
83
+ return { args, value };
84
+ },
85
+ template: `
86
+ <BaseAutocompleteFetch
87
+ v-model="value"
88
+ v-bind="args"
89
+ >
90
+ <template #footer>
91
+ <div class="text-center p-1 pt-2 mt-2 border-t">
92
+ <button class="btn btn-sm w-full btn-slate-200-outline">This is the footer 💯</button>
93
+ </div>
94
+ </template>
95
+ </BaseAutocompleteFetch>
96
+ `,
97
+ };
98
+ };
99
+
100
+ export const SlotEmpty = (args) => {
101
+ return {
102
+ components: { BaseAutocompleteFetch },
103
+ setup() {
104
+ const value = ref(null);
105
+ return { args, value };
106
+ },
107
+ template: `
108
+ <BaseAutocompleteFetch
109
+ v-model="value"
110
+ v-bind="args"
111
+ >
112
+ <template #empty="props">
113
+ <div>
114
+ <div v-if="props.firstSearch" class="text-center py-10 p-6">🤓🤓🤓</div>
115
+ <div v-else class="text-center p-6">Start your search... 🔎</div>
116
+ </div>
117
+ </template>
118
+ </BaseAutocompleteFetch>
119
+ `,
120
+ };
121
+ };
@@ -0,0 +1,185 @@
1
+ <template>
2
+ <BaseAutocomplete
3
+ ref="input"
4
+ :loading="showLoading && page == 1"
5
+ :model-value="modelValue"
6
+ :disabled="disabled"
7
+ :placeholder="placeholder"
8
+ :options="options"
9
+ :value-key="valueKey"
10
+ :label-key="labelKey"
11
+ :input-class="inputClass"
12
+ :filter="() => true"
13
+ @clear="onClear"
14
+ @focus="onFocus"
15
+ @typing="onTyping"
16
+ @scroll-bottom="scrollBottom"
17
+ @update:model-value="$emit('update:modelValue', $event)"
18
+ >
19
+ <template #option="optionProps">
20
+ <slot name="option" v-bind="optionProps" />
21
+ </template>
22
+
23
+ <template #footer="footerProps">
24
+ <slot name="footer" v-bind="footerProps" :keywords="keywords">
25
+ <div v-if="createNewUrl" class="p-2">
26
+ <router-link
27
+ :to="createNewUrl"
28
+ target="_blank"
29
+ class="btn flex items-center justify-center font-normal shadow"
30
+ @mousedown="footerProps.dontLooseFocus"
31
+ >
32
+ <Icon icon="heroicons-solid:plus" class="mr-1.5 block h-5 w-5" />
33
+ <span>{{ $t('sui.create_new') }}</span>
34
+ </router-link>
35
+ </div>
36
+ </slot>
37
+ </template>
38
+
39
+ <template #empty="emptyProps">
40
+ <slot name="empty" v-bind="emptyProps" :first-search="firstSearch">
41
+ <div
42
+ v-if="firstSearch"
43
+ class="flex h-[80px] items-center justify-center px-3 text-center text-base leading-tight text-slate-600"
44
+ >
45
+ {{ $t('sui.nothing_found') }}
46
+ </div>
47
+ </slot>
48
+ </template>
49
+ </BaseAutocomplete>
50
+ </template>
51
+
52
+ <script lang="ts" setup>
53
+ import { config } from '../';
54
+ import { debounce } from 'lodash';
55
+ import { PropType, Ref } from 'vue';
56
+ import { Option, Selection } from '@/types/types';
57
+ import { RouteLocationRaw } from 'vue-router';
58
+ import BaseAutocomplete from './BaseAutocomplete.vue';
59
+
60
+ const props = defineProps({
61
+ modelValue: {
62
+ required: true,
63
+ type: [Object, null, undefined] as PropType<Selection>,
64
+ },
65
+ url: {
66
+ required: true,
67
+ type: String,
68
+ },
69
+ labelKey: {
70
+ required: true,
71
+ type: String,
72
+ },
73
+ valueKey: {
74
+ required: true,
75
+ type: String,
76
+ },
77
+ inputClass: {
78
+ default: undefined,
79
+ type: String,
80
+ },
81
+ placeholder: {
82
+ default: undefined,
83
+ type: String,
84
+ },
85
+ required: {
86
+ default: false,
87
+ type: Boolean,
88
+ },
89
+ disabled: {
90
+ default: false,
91
+ type: Boolean,
92
+ },
93
+ createNewUrl: {
94
+ default: '',
95
+ type: [String, Object] as PropType<RouteLocationRaw>,
96
+ },
97
+ queryKey: {
98
+ default: 'search',
99
+ type: String,
100
+ },
101
+ });
102
+
103
+ const emit = defineEmits([
104
+ 'update:modelValue',
105
+ 'typing',
106
+ 'focus',
107
+ 'scrollBottom',
108
+ 'clear',
109
+ ]);
110
+
111
+ const showLoading = ref(false);
112
+ const fetching = ref(false);
113
+ const firstSearch = ref(false);
114
+ const reachedEnd = ref(false);
115
+ const keywords = ref('');
116
+ const page = ref(1);
117
+ const options = ref([]) as Ref<Option[]>;
118
+
119
+ const onTyping = (query: string) => {
120
+ page.value = 1;
121
+ reachedEnd.value = false;
122
+ keywords.value = query;
123
+ showLoading.value = true;
124
+ debouncedSearch();
125
+ };
126
+
127
+ const onFocus = () => {
128
+ if (!firstSearch.value) {
129
+ search();
130
+ }
131
+ };
132
+
133
+ const onClear = () => {
134
+ keywords.value = '';
135
+ search();
136
+ emit('clear');
137
+ };
138
+
139
+ const scrollBottom = () => {
140
+ if (!reachedEnd.value) {
141
+ page.value++;
142
+ search();
143
+ }
144
+ };
145
+
146
+ const search = () => {
147
+ if (fetching.value) {
148
+ return;
149
+ }
150
+
151
+ fetching.value = true;
152
+ showLoading.value = true;
153
+
154
+ config.http
155
+ .get(props.url, {
156
+ params: {
157
+ [props.queryKey]: keywords.value,
158
+ page: page.value,
159
+ },
160
+ })
161
+ .then((response: any) => {
162
+ const data = response.data.data as Option[];
163
+
164
+ if (data.length == 0) {
165
+ reachedEnd.value = true;
166
+ }
167
+
168
+ if (page.value == 1) {
169
+ options.value = data;
170
+ } else {
171
+ options.value.push(...data);
172
+ }
173
+
174
+ firstSearch.value = true;
175
+ })
176
+ .finally(() => {
177
+ fetching.value = false;
178
+ showLoading.value = false;
179
+ });
180
+ };
181
+
182
+ const debouncedSearch = debounce(() => {
183
+ search();
184
+ }, 300);
185
+ </script>
@@ -0,0 +1,39 @@
1
+ import BaseAvatar from './BaseAvatar.vue';
2
+
3
+ export default {
4
+ title: 'Components/BaseAvatar',
5
+ component: BaseAvatar,
6
+ argTypes: {
7
+ size: {
8
+ control: { type: 'select' },
9
+ options: ['xs', 'sm', 'base', 'lg', 'xl'],
10
+ },
11
+ detailsPosition: {
12
+ control: { type: 'select' },
13
+ options: ['left', 'right'],
14
+ },
15
+ },
16
+ };
17
+
18
+ const Template = (args) => ({
19
+ components: { BaseAvatar },
20
+ setup() {
21
+ return { args };
22
+ },
23
+ template: `
24
+ <BaseAvatar v-bind="args"></BaseAvatar>
25
+ `,
26
+ });
27
+
28
+ export const Demo = Template.bind({});
29
+ Demo.args = {
30
+ showDetails: true,
31
+ user: {
32
+ email: 'jane@witify.io',
33
+ first_name: 'Jane',
34
+ last_name: 'Doe',
35
+ full_name: 'Jane Doe',
36
+ avatar_thumbnail_url:
37
+ 'https://images.unsplash.com/photo-1494790108377-be9c29b29330??auto=format&fit=crop&w=200&h=200&q=80&g=face',
38
+ },
39
+ };