@caipira/tamandua 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.
Files changed (295) hide show
  1. package/.editorconfig +12 -0
  2. package/.prettierrc +5 -0
  3. package/.storybook/main.ts +25 -0
  4. package/.storybook/preview-body.html +3 -0
  5. package/.storybook/preview.ts +24 -0
  6. package/App.vue +1 -0
  7. package/Dockerfile +21 -0
  8. package/LICENSE +674 -0
  9. package/README.md +11 -0
  10. package/assets/icons/account.svg +1 -0
  11. package/assets/icons/alert-octagon-outline.svg +1 -0
  12. package/assets/icons/alert-octagon.svg +1 -0
  13. package/assets/icons/archive-outline.svg +1 -0
  14. package/assets/icons/archive.svg +1 -0
  15. package/assets/icons/arrow-left.svg +1 -0
  16. package/assets/icons/arrow-right.svg +1 -0
  17. package/assets/icons/bank-outline.svg +1 -0
  18. package/assets/icons/bank.svg +1 -0
  19. package/assets/icons/camera.svg +1 -0
  20. package/assets/icons/cards-outline.svg +1 -0
  21. package/assets/icons/cards-variant.svg +1 -0
  22. package/assets/icons/cart-outline.svg +1 -0
  23. package/assets/icons/chart-box-outline.svg +1 -0
  24. package/assets/icons/chart-box.svg +1 -0
  25. package/assets/icons/check-circle-outline.svg +1 -0
  26. package/assets/icons/check-circle.svg +1 -0
  27. package/assets/icons/check.svg +1 -0
  28. package/assets/icons/checkbox-dark.svg +1 -0
  29. package/assets/icons/checkbox-indeterminate-dark.svg +1 -0
  30. package/assets/icons/checkbox-indeterminate.svg +1 -0
  31. package/assets/icons/checkbox.svg +1 -0
  32. package/assets/icons/chevron-down.svg +1 -0
  33. package/assets/icons/chevron-left.svg +1 -0
  34. package/assets/icons/chevron-right.svg +1 -0
  35. package/assets/icons/chevron-up.svg +1 -0
  36. package/assets/icons/circle.svg +1 -0
  37. package/assets/icons/clock.svg +1 -0
  38. package/assets/icons/close-circle-outline.svg +1 -0
  39. package/assets/icons/close-circle.svg +1 -0
  40. package/assets/icons/close.svg +1 -0
  41. package/assets/icons/cog.svg +1 -0
  42. package/assets/icons/color-fill.svg +1 -0
  43. package/assets/icons/copy.svg +1 -0
  44. package/assets/icons/credit-card-plus.svg +1 -0
  45. package/assets/icons/credit-card.svg +1 -0
  46. package/assets/icons/currency.svg +1 -0
  47. package/assets/icons/database.svg +1 -0
  48. package/assets/icons/dots-grid.svg +1 -0
  49. package/assets/icons/dots-vertical.svg +1 -0
  50. package/assets/icons/email-open-outline.svg +1 -0
  51. package/assets/icons/email-outline.svg +1 -0
  52. package/assets/icons/eye-off.svg +1 -0
  53. package/assets/icons/eye.svg +1 -0
  54. package/assets/icons/file-document-plus-outline.svg +1 -0
  55. package/assets/icons/filmstrip.svg +1 -0
  56. package/assets/icons/filter.svg +1 -0
  57. package/assets/icons/fullscreen-exit.svg +1 -0
  58. package/assets/icons/fullscreen.svg +1 -0
  59. package/assets/icons/group.svg +1 -0
  60. package/assets/icons/image-album-outline.svg +1 -0
  61. package/assets/icons/image-album.svg +1 -0
  62. package/assets/icons/image-outline.svg +1 -0
  63. package/assets/icons/image.svg +1 -0
  64. package/assets/icons/info-outline.svg +1 -0
  65. package/assets/icons/key-chain.svg +1 -0
  66. package/assets/icons/key-variant.svg +1 -0
  67. package/assets/icons/key.svg +1 -0
  68. package/assets/icons/listbox-outline.svg +1 -0
  69. package/assets/icons/loading.svg +1 -0
  70. package/assets/icons/lock-outline.svg +1 -0
  71. package/assets/icons/lock.svg +1 -0
  72. package/assets/icons/logout.svg +1 -0
  73. package/assets/icons/menu-down.svg +1 -0
  74. package/assets/icons/menu-left.svg +1 -0
  75. package/assets/icons/menu-right.svg +1 -0
  76. package/assets/icons/menu.svg +1 -0
  77. package/assets/icons/minus-circle-outline.svg +1 -0
  78. package/assets/icons/minus-circle.svg +1 -0
  79. package/assets/icons/minus.svg +1 -0
  80. package/assets/icons/moon.svg +1 -0
  81. package/assets/icons/open-in-new.svg +1 -0
  82. package/assets/icons/pencil.svg +1 -0
  83. package/assets/icons/people.svg +1 -0
  84. package/assets/icons/piggy-bank-outline.svg +1 -0
  85. package/assets/icons/plus-circle-outline.svg +1 -0
  86. package/assets/icons/plus-circle.svg +1 -0
  87. package/assets/icons/plus.svg +1 -0
  88. package/assets/icons/qrcode-scan.svg +1 -0
  89. package/assets/icons/radio-dark.svg +1 -0
  90. package/assets/icons/radio.svg +1 -0
  91. package/assets/icons/refresh.svg +1 -0
  92. package/assets/icons/save.svg +1 -0
  93. package/assets/icons/search.svg +1 -0
  94. package/assets/icons/spotlight.svg +1 -0
  95. package/assets/icons/store-outline.svg +1 -0
  96. package/assets/icons/sun.svg +1 -0
  97. package/assets/icons/swap-horizontal.svg +1 -0
  98. package/assets/icons/swap-left.svg +1 -0
  99. package/assets/icons/swap-right.svg +1 -0
  100. package/assets/icons/swap.svg +1 -0
  101. package/assets/icons/system-theme.svg +1 -0
  102. package/assets/icons/tag-outline.svg +1 -0
  103. package/assets/icons/trash-can-outline.svg +1 -0
  104. package/assets/icons/trash-can.svg +1 -0
  105. package/assets/icons/upload.svg +1 -0
  106. package/assets/icons/user-circle.svg +1 -0
  107. package/assets/icons/zip-box.svg +1 -0
  108. package/assets/images/fs/apk.svg +11 -0
  109. package/assets/images/fs/bmp.svg +7 -0
  110. package/assets/images/fs/css.svg +8 -0
  111. package/assets/images/fs/doc.svg +9 -0
  112. package/assets/images/fs/docx.svg +9 -0
  113. package/assets/images/fs/folder-adwaita.svg +8 -0
  114. package/assets/images/fs/folder-black.svg +8 -0
  115. package/assets/images/fs/folder-brown.svg +8 -0
  116. package/assets/images/fs/folder-grey.svg +8 -0
  117. package/assets/images/fs/folder-nordic.svg +8 -0
  118. package/assets/images/fs/folder-orange.svg +8 -0
  119. package/assets/images/fs/folder-palebrown.svg +8 -0
  120. package/assets/images/fs/folder-paleorange.svg +8 -0
  121. package/assets/images/fs/folder-teal.svg +8 -0
  122. package/assets/images/fs/folder-white.svg +8 -0
  123. package/assets/images/fs/folder-yellow.svg +8 -0
  124. package/assets/images/fs/gif.svg +7 -0
  125. package/assets/images/fs/go.svg +9 -0
  126. package/assets/images/fs/ics.svg +24 -0
  127. package/assets/images/fs/iso.svg +10 -0
  128. package/assets/images/fs/jpeg.svg +7 -0
  129. package/assets/images/fs/jpg.svg +7 -0
  130. package/assets/images/fs/js.svg +9 -0
  131. package/assets/images/fs/json.svg +9 -0
  132. package/assets/images/fs/lua.svg +9 -0
  133. package/assets/images/fs/m4v.svg +7 -0
  134. package/assets/images/fs/md.svg +10 -0
  135. package/assets/images/fs/mov.svg +7 -0
  136. package/assets/images/fs/mp3.svg +9 -0
  137. package/assets/images/fs/mp4.svg +7 -0
  138. package/assets/images/fs/pdf.svg +9 -0
  139. package/assets/images/fs/pgp.svg +8 -0
  140. package/assets/images/fs/php.svg +9 -0
  141. package/assets/images/fs/png.svg +7 -0
  142. package/assets/images/fs/ppt.svg +9 -0
  143. package/assets/images/fs/py.svg +9 -0
  144. package/assets/images/fs/rar.svg +20 -0
  145. package/assets/images/fs/rpm.svg +7 -0
  146. package/assets/images/fs/rs.svg +9 -0
  147. package/assets/images/fs/sh.svg +9 -0
  148. package/assets/images/fs/tar.svg +20 -0
  149. package/assets/images/fs/txt.svg +8 -0
  150. package/assets/images/fs/unknown.svg +8 -0
  151. package/assets/images/fs/xls.svg +9 -0
  152. package/assets/images/fs/xlsx.svg +9 -0
  153. package/assets/images/fs/xml.svg +8 -0
  154. package/assets/images/fs/yaml.svg +9 -0
  155. package/assets/images/fs/zip.svg +20 -0
  156. package/components/Avatar/Avatar.story.ts +55 -0
  157. package/components/Avatar/Avatar.vue +82 -0
  158. package/components/Avatar/index.ts +12 -0
  159. package/components/Backdrop/Backdrop.vue +27 -0
  160. package/components/Backdrop/index.ts +12 -0
  161. package/components/Button/Button.story.ts +74 -0
  162. package/components/Button/Button.vue +230 -0
  163. package/components/Button/index.ts +12 -0
  164. package/components/ButtonCopy/ButtonCopy.vue +61 -0
  165. package/components/ButtonCopy/index.ts +12 -0
  166. package/components/Drawer/Drawer.vue +102 -0
  167. package/components/Drawer/index.ts +12 -0
  168. package/components/Dropdown/Dropdown.vue +258 -0
  169. package/components/Dropdown/index.ts +12 -0
  170. package/components/EventListener/EventListener.vue +12 -0
  171. package/components/FileDrop/FileDrop.vue +116 -0
  172. package/components/FileDrop/index.ts +12 -0
  173. package/components/Form/Form.spec.ts +72 -0
  174. package/components/Form/Form.vue +134 -0
  175. package/components/Form/index.ts +12 -0
  176. package/components/FormItem/FormItem.vue +85 -0
  177. package/components/FormItem/index.ts +12 -0
  178. package/components/GraphyEmpty/GraphyEmpty.vue +16 -0
  179. package/components/GraphyEmpty/index.ts +12 -0
  180. package/components/GraphyLabel/GraphyLabel.vue +34 -0
  181. package/components/GraphyLabel/index.ts +12 -0
  182. package/components/GraphyPrice/GraphyPrice.story.ts +37 -0
  183. package/components/GraphyPrice/GraphyPrice.vue +65 -0
  184. package/components/GraphyPrice/index.ts +12 -0
  185. package/components/GraphySubtitle/GraphySubtitle.vue +22 -0
  186. package/components/GraphySubtitle/index.ts +12 -0
  187. package/components/GraphyTitle/GraphyTitle.vue +13 -0
  188. package/components/GraphyTitle/index.ts +12 -0
  189. package/components/Icon/Icon.vue +84 -0
  190. package/components/Icon/index.ts +12 -0
  191. package/components/IconButton/IconButton.vue +168 -0
  192. package/components/IconButton/index.ts +12 -0
  193. package/components/InputAvatar/InputAvatar.vue +63 -0
  194. package/components/InputAvatar/index.ts +12 -0
  195. package/components/InputCheckbox/InputCheckbox.vue +77 -0
  196. package/components/InputCheckbox/index.ts +12 -0
  197. package/components/InputColor/InputColor.vue +54 -0
  198. package/components/InputColor/index.ts +12 -0
  199. package/components/InputDate/InputDate.story.ts +15 -0
  200. package/components/InputDate/InputDate.vue +368 -0
  201. package/components/InputDate/index.ts +12 -0
  202. package/components/InputMultiplier/InputMultiplier.vue +144 -0
  203. package/components/InputMultiplier/index.ts +12 -0
  204. package/components/InputPassword/InputPassword.vue +168 -0
  205. package/components/InputPassword/index.ts +12 -0
  206. package/components/InputPhone/InputPhone.vue +125 -0
  207. package/components/InputPhone/index.ts +12 -0
  208. package/components/InputPrice/InputPrice.vue +96 -0
  209. package/components/InputPrice/index.ts +12 -0
  210. package/components/InputRadio/InputRadio.vue +41 -0
  211. package/components/InputRadio/InputRadioGroup.story.ts +24 -0
  212. package/components/InputRadio/InputRadioGroup.vue +48 -0
  213. package/components/InputRadio/index.ts +14 -0
  214. package/components/InputSelect/InputSelect.story.ts +87 -0
  215. package/components/InputSelect/InputSelect.vue +507 -0
  216. package/components/InputSelect/index.ts +12 -0
  217. package/components/InputSwitch/InputSwitch.story.ts +34 -0
  218. package/components/InputSwitch/InputSwitch.vue +82 -0
  219. package/components/InputSwitch/index.ts +12 -0
  220. package/components/InputText/InputText.vue +62 -0
  221. package/components/InputText/index.ts +12 -0
  222. package/components/InputTextarea/InputTextarea.vue +64 -0
  223. package/components/InputTextarea/index.ts +12 -0
  224. package/components/LineChart/LineChart.vue +14 -0
  225. package/components/LineChart/index.ts +12 -0
  226. package/components/Modal/Modal.vue +106 -0
  227. package/components/Modal/index.ts +12 -0
  228. package/components/ModalForm/ModalForm.vue +141 -0
  229. package/components/ModalForm/index.ts +12 -0
  230. package/components/Pagination/Pagination.story.ts +15 -0
  231. package/components/Pagination/Pagination.vue +138 -0
  232. package/components/Pagination/index.ts +12 -0
  233. package/components/PieChart/PieChart.vue +14 -0
  234. package/components/PieChart/index.ts +12 -0
  235. package/components/Popconfirm/Popconfirm.vue +80 -0
  236. package/components/Popconfirm/index.ts +12 -0
  237. package/components/Popover/Popover.vue +133 -0
  238. package/components/Popover/index.ts +12 -0
  239. package/components/ProgressCircle/ProgressCircle.story.ts +31 -0
  240. package/components/ProgressCircle/ProgressCircle.vue +82 -0
  241. package/components/ProgressCircle/index.ts +12 -0
  242. package/components/ProgressLine/ProgressLine.story.ts +27 -0
  243. package/components/ProgressLine/ProgressLine.vue +104 -0
  244. package/components/ProgressLine/index.ts +12 -0
  245. package/components/SensitiveInfo/SensitiveInfo.vue +18 -0
  246. package/components/SensitiveInfo/index.ts +12 -0
  247. package/components/Tab/Tab.vue +58 -0
  248. package/components/Tab/index.ts +12 -0
  249. package/components/Table/Table.story.ts +32 -0
  250. package/components/Table/Table.vue +318 -0
  251. package/components/Table/index.ts +12 -0
  252. package/components/Tag/Tag.vue +73 -0
  253. package/components/Tag/index.ts +12 -0
  254. package/components/Toast/Toast.vue +75 -0
  255. package/components/Toast/index.ts +12 -0
  256. package/components/index.ts +43 -0
  257. package/components/plugins.ts +89 -0
  258. package/composables/index.ts +2 -0
  259. package/composables/useBreakpoints.ts +30 -0
  260. package/composables/useRender.ts +29 -0
  261. package/entrypoint.sh +19 -0
  262. package/enums/app.ts +5 -0
  263. package/enums/form.ts +25 -0
  264. package/enums/ui.ts +160 -0
  265. package/env.d.ts +8 -0
  266. package/i18n.ts +20 -0
  267. package/index.css +383 -0
  268. package/index.html +22 -0
  269. package/index.ts +14 -0
  270. package/main.ts +31 -0
  271. package/package.json +70 -0
  272. package/plugins/register-component.ts +5 -0
  273. package/postcss.config.js +6 -0
  274. package/services/clipboard.ts +5 -0
  275. package/services/date.ts +27 -0
  276. package/services/form/crud.ts +109 -0
  277. package/services/form/form-data-transformers.ts +148 -0
  278. package/services/form/form-json-transformers.ts +91 -0
  279. package/services/form/form-transformer.ts +54 -0
  280. package/services/form/form-value-transformers.ts +35 -0
  281. package/services/form/form.test.ts +98 -0
  282. package/services/form/form.ts +80 -0
  283. package/services/password.ts +309 -0
  284. package/services/ui.ts +43 -0
  285. package/tailwind.config.js +16 -0
  286. package/tsconfig.json +23 -0
  287. package/types/address.ts +44 -0
  288. package/types/api.ts +28 -0
  289. package/types/common.ts +5 -0
  290. package/types/form.ts +144 -0
  291. package/types/index.ts +5 -0
  292. package/types/ui.ts +55 -0
  293. package/types/website.ts +16 -0
  294. package/vite.config.mts +38 -0
  295. package/vitest.setup.ts +21 -0
@@ -0,0 +1,14 @@
1
+ import type { App, Plugin } from "vue";
2
+ import registerComponent from "@/plugins/register-component";
3
+
4
+ import InputRadio from "./InputRadio.vue";
5
+ import InputRadioGroup from "./InputRadioGroup.vue";
6
+
7
+ export default {
8
+ install(app: App) {
9
+ registerComponent(app, InputRadio);
10
+ registerComponent(app, InputRadioGroup);
11
+ },
12
+ } as Plugin;
13
+
14
+ export { InputRadio, InputRadioGroup };
@@ -0,0 +1,87 @@
1
+ import { faker } from "@faker-js/faker";
2
+
3
+ const story = {
4
+ title: "Form/Select",
5
+ };
6
+
7
+ const Template1 = (args: any) => ({
8
+ setup() {
9
+ return { args };
10
+ },
11
+ template: `
12
+ <t-input-select
13
+ v-model="args.value"
14
+ :options="args.items"
15
+ :placeholder="args.placeholder"
16
+ @change="args.change"
17
+ />
18
+ `,
19
+ });
20
+
21
+ const Template2 = (args: any) => ({
22
+ setup() {
23
+ return { args };
24
+ },
25
+ template: `
26
+ <t-input-select
27
+ v-model="args.value"
28
+ :options="args.items"
29
+ :placeholder="args.placeholder"
30
+ >
31
+ <template v-slot:default="option">
32
+ <img
33
+ v-if="option.thumbnail" :src="option.thumbnail"
34
+ class="favicon"
35
+ />
36
+ {{ option.name }}
37
+ </template>
38
+ </t-input-select>
39
+ `,
40
+ });
41
+
42
+ const defaultArgs: any = {
43
+ value: "",
44
+ placeholder: "Select an option",
45
+ };
46
+
47
+ const Default: any = Template1.bind({});
48
+ Default.args = {
49
+ ...defaultArgs,
50
+ items: Array.from({ length: 10 }).map(() => ({
51
+ label: faker.person.fullName(),
52
+ value: faker.string.uuid(),
53
+ })),
54
+ };
55
+
56
+ const With_Images: any = Template2.bind({});
57
+ With_Images.args = {
58
+ ...defaultArgs,
59
+ items: [
60
+ {
61
+ id: 1,
62
+ name: "DuckDuckGo",
63
+ thumbnail:
64
+ "https://upload.wikimedia.org/wikipedia/en/9/90/The_DuckDuckGo_Duck.png",
65
+ },
66
+ {
67
+ id: 2,
68
+ name: "Google",
69
+ thumbnail:
70
+ "https://banner2.cleanpng.com/20180324/sww/kisspng-google-logo-g-suite-chrome-5ab6e618b3b2c3.5810634915219358967361.jpg",
71
+ },
72
+ {
73
+ id: 3,
74
+ name: "Bing",
75
+ thumbnail:
76
+ "https://img.favpng.com/16/14/8/bing-news-logo-microsoft-msn-png-favpng-mwugypPENzFKWhVQQZnuGbvfD.jpg",
77
+ },
78
+ {
79
+ id: 4,
80
+ name: "Wikipedia",
81
+ thumbnail:
82
+ "https://upload.wikimedia.org/wikipedia/commons/d/de/Wikipedia_Logo_1.0.png",
83
+ },
84
+ ].map((seller) => ({ label: seller, value: seller.id })),
85
+ };
86
+
87
+ export { story as default, Default, With_Images };
@@ -0,0 +1,507 @@
1
+ <script lang="ts">
2
+ let id = 0;
3
+ </script>
4
+
5
+ <script lang="ts" setup>
6
+ import type Dropdown from "@/components/Dropdown/Dropdown.vue";
7
+ import { Pagination } from "@/types/ui";
8
+ import { ElementSize } from "@/enums/ui";
9
+ import { ElementPosition } from "@/enums/ui";
10
+ import { APISearchFilters } from "@/types/api";
11
+ import { SelectOption, SelectValue, SelectValuePrimitives } from "@/types/form";
12
+
13
+ import uiService from "@/services/ui";
14
+ import {
15
+ ref,
16
+ watch,
17
+ inject,
18
+ computed,
19
+ nextTick,
20
+ onBeforeMount,
21
+ onBeforeUnmount,
22
+ } from "vue";
23
+
24
+ defineOptions({ name: "TInputSelect" });
25
+
26
+ const props = withDefaults(
27
+ defineProps<{
28
+ size?: ElementSize;
29
+ options?: SelectOption[];
30
+
31
+ /** Mapper to convert search results to SelectOption */
32
+ mapper?: (i: any) => SelectOption;
33
+
34
+ /** Filter function */
35
+ filter?: (input: string, option: SelectOption) => boolean;
36
+
37
+ tooltip?: string;
38
+ disabled?: boolean;
39
+ resource?: (
40
+ filters?: APISearchFilters | undefined,
41
+ ) => Promise<{ items: unknown[]; pagination: Pagination }>;
42
+ multiple?: boolean;
43
+ identifier?: string;
44
+ modelValue?: SelectValue;
45
+ placeholder?: string;
46
+ initialValue?: any;
47
+ disableStyle?: boolean;
48
+
49
+ /** Indicates whether the user can search the options */
50
+ isSearchable?: boolean;
51
+
52
+ dropdownPlacement?: `${ElementPosition}`;
53
+ }>(),
54
+ {
55
+ size: ElementSize.DEFAULT,
56
+ options: () => [],
57
+ mapper: (i: any): SelectOption => ({ value: i.id ?? i.uuid, label: i }),
58
+ filter: (input: string, option: SelectOption): boolean =>
59
+ option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0,
60
+ tooltip: "",
61
+ multiple: false,
62
+ identifier: "id",
63
+ placeholder: "",
64
+ disableStyle: false,
65
+ isSearchable: false,
66
+ dropdownPlacement: ElementPosition.BOTTOM,
67
+ },
68
+ );
69
+
70
+ let bubble: boolean = false;
71
+ let timeout!: any;
72
+ let lastSearch: string = "";
73
+
74
+ const name = `checkbox_${++id}`;
75
+ const search = ref<string>("");
76
+ const items = ref<SelectOption[]>([]);
77
+ const inputRef = ref<HTMLInputElement | null>(null);
78
+ const formStyle = inject("formStyle", { label: "", input: "" });
79
+ const wrapperRef = ref<InstanceType<typeof HTMLDivElement> | null>(null);
80
+ const listVisible = ref<boolean>(false);
81
+ const dropdownRef = ref<InstanceType<typeof Dropdown> | null>(null);
82
+ const selectedIndexes = ref<number[]>([]);
83
+
84
+ /**
85
+ * If the options come from the API and we know the backend response includes the pagination,
86
+ * we can load either more or all the options once the user hits the end of the list,
87
+ * so store that pagination for tracking here
88
+ *
89
+ */
90
+ const backendPagination = ref<Pagination | null>(null);
91
+
92
+ const emit = defineEmits<{
93
+ (e: "update:modelValue", val: any): void;
94
+ }>();
95
+
96
+ const hasValue = computed((): boolean => {
97
+ return selectedIndexes.value.length > 0;
98
+ });
99
+
100
+ const showSearchbox = computed((): boolean => {
101
+ return props.isSearchable && listVisible.value;
102
+ });
103
+
104
+ const isRemote = computed((): boolean => {
105
+ return typeof props.resource === "function";
106
+ });
107
+
108
+ const isOptionSelected = (item: any): boolean => {
109
+ if (
110
+ !props.multiple ||
111
+ !props.modelValue ||
112
+ !Array.isArray(props.modelValue)
113
+ ) {
114
+ return false;
115
+ }
116
+
117
+ return props.modelValue.includes(item.option[props.identifier]);
118
+ };
119
+
120
+ const isValidValue = (
121
+ value: SelectValue | undefined = props.modelValue,
122
+ ): boolean => {
123
+ return !(
124
+ value === undefined ||
125
+ value === "" ||
126
+ (Array.isArray(value) && value.length === 0)
127
+ );
128
+ };
129
+
130
+ const emitValue = (value: any) => {
131
+ emit("update:modelValue", value);
132
+ };
133
+
134
+ const setValue = (value: any = props.modelValue) => {
135
+ if (!isValidValue(value)) {
136
+ return;
137
+ }
138
+
139
+ if (props.multiple) {
140
+ if (Array.isArray(value)) {
141
+ selectedIndexes.value = value
142
+ .map((item: SelectValuePrimitives) =>
143
+ findOptionIndexByValue(item),
144
+ )
145
+ .filter((item: number) => item > -1);
146
+ }
147
+ } else {
148
+ const index = findOptionIndexByValue(value);
149
+
150
+ if (~index) {
151
+ selectedIndexes.value = [index];
152
+ }
153
+ }
154
+ };
155
+
156
+ const toggleDropdown = (e: Event) => {
157
+ listVisible.value = !listVisible.value;
158
+
159
+ if (listVisible.value && showSearchbox.value) {
160
+ nextTick(() => {
161
+ inputRef.value?.focus();
162
+ });
163
+ }
164
+
165
+ e.stopPropagation();
166
+ };
167
+
168
+ const findOptionIndexByValue = (value: SelectValue): number => {
169
+ return items.value.findIndex(
170
+ (option: SelectOption) => option.value === value,
171
+ );
172
+ };
173
+
174
+ const off = (event: any) => {
175
+ if (
176
+ wrapperRef.value &&
177
+ uiService.wasClickOutsideOfContainer(event, wrapperRef.value)
178
+ ) {
179
+ listVisible.value = false;
180
+ }
181
+ };
182
+
183
+ const fetch = async (loadNextPage = false) => {
184
+ if (!props.resource) {
185
+ return;
186
+ }
187
+
188
+ const filters: APISearchFilters = {
189
+ t: search.value,
190
+ r: "option",
191
+ };
192
+
193
+ if (
194
+ loadNextPage &&
195
+ backendPagination.value &&
196
+ backendPagination.value.pages > backendPagination.value.page
197
+ ) {
198
+ filters.page = backendPagination.value.page + 1;
199
+ } else {
200
+ loadNextPage = false;
201
+ }
202
+
203
+ // @todo: if multiple, send array of values
204
+ if (!search.value && props.modelValue) {
205
+ filters.i = props.modelValue as string;
206
+ }
207
+
208
+ const data = await props.resource(filters);
209
+
210
+ if (loadNextPage) {
211
+ items.value.push(...data.items.map(props.mapper));
212
+ } else {
213
+ items.value = data.items.map(props.mapper);
214
+ }
215
+
216
+ backendPagination.value = data.pagination;
217
+
218
+ if (!isRemote.value && search.value) {
219
+ setValue(search.value);
220
+ }
221
+ };
222
+
223
+ const throttle = () => {
224
+ if (lastSearch === search.value) {
225
+ return;
226
+ }
227
+
228
+ if (timeout) {
229
+ clearTimeout(timeout);
230
+
231
+ timeout = null;
232
+ }
233
+
234
+ timeout = setTimeout(() => {
235
+ lastSearch = search.value;
236
+
237
+ if (isRemote.value) {
238
+ if (!listVisible.value) {
239
+ listVisible.value = true;
240
+ }
241
+
242
+ fetch();
243
+ } else if (props.isSearchable) {
244
+ items.value = props.options.filter((option: any) =>
245
+ props.filter(search.value, option),
246
+ );
247
+ }
248
+ }, 500);
249
+ };
250
+
251
+ const onBlur = () => {
252
+ listVisible.value = false;
253
+ };
254
+
255
+ const onEsc = (event: KeyboardEvent) => {
256
+ listVisible.value = false;
257
+
258
+ event.stopPropagation();
259
+ };
260
+
261
+ const onSelect = ({
262
+ index,
263
+ hold,
264
+ }: {
265
+ index: number;
266
+ option: SelectOption;
267
+ hold: boolean;
268
+ }) => {
269
+ bubble = true;
270
+
271
+ if (props.multiple) {
272
+ const existingIndex = selectedIndexes.value.findIndex(
273
+ (index2: number) => index2 === index,
274
+ );
275
+
276
+ if (~existingIndex) {
277
+ selectedIndexes.value.splice(existingIndex, 1);
278
+ } else {
279
+ selectedIndexes.value.push(index);
280
+ }
281
+ } else {
282
+ selectedIndexes.value = [index];
283
+ }
284
+
285
+ if (!props.multiple || !hold) {
286
+ listVisible.value = false;
287
+ }
288
+ };
289
+
290
+ const onListEnd = () => {
291
+ if (
292
+ isRemote.value &&
293
+ backendPagination.value &&
294
+ backendPagination.value.pages > backendPagination.value.page
295
+ ) {
296
+ fetch(true);
297
+ }
298
+ };
299
+
300
+ const onKeyDown = async (e: KeyboardEvent) => {
301
+ const key = e.key;
302
+
303
+ switch (key) {
304
+ case "ArrowDown":
305
+ dropdownRef.value?.move(e);
306
+ break;
307
+ }
308
+ };
309
+
310
+ onBeforeMount(async () => {
311
+ if (isRemote.value) {
312
+ await fetch();
313
+
314
+ /**
315
+ * Instead of using { immediate: true } in the modelValue watcher, if the options come
316
+ * from the network, we need to wait for them to be loaded, so we wait for that and then call
317
+ * the setValue to update to ti's current value
318
+ *
319
+ */
320
+ setValue();
321
+ } else if (props.options.length > 0) {
322
+ items.value = props.options;
323
+ }
324
+
325
+ if (!props.modelValue && props.initialValue) {
326
+ setValue(props.initialValue);
327
+ } else {
328
+ setValue(props.modelValue);
329
+ }
330
+
331
+ document.addEventListener("click", off);
332
+ });
333
+
334
+ onBeforeUnmount(() => {
335
+ document.removeEventListener("click", off);
336
+ });
337
+
338
+ watch(
339
+ () => props.modelValue,
340
+ async (newValue) => {
341
+ if (!newValue) {
342
+ return;
343
+ }
344
+
345
+ /**
346
+ * If the value is not in the options and the options come from the netwoek, fetch the option for the value
347
+ * and use it to hydrate the select
348
+ *
349
+ */
350
+ if (findOptionIndexByValue(newValue) < 0 && isRemote.value) {
351
+ await fetch();
352
+ }
353
+
354
+ setValue(newValue);
355
+ },
356
+ );
357
+
358
+ watch(
359
+ selectedIndexes,
360
+ (newValue) => {
361
+ const value = props.multiple
362
+ ? items.value
363
+ .filter((option: SelectOption, index) =>
364
+ selectedIndexes.value.includes(index),
365
+ )
366
+ .map((option: SelectOption) => option.value)
367
+ : items.value[selectedIndexes.value[0]].value;
368
+
369
+ if (bubble) {
370
+ bubble = false;
371
+ emitValue(value);
372
+ }
373
+ },
374
+ { deep: true },
375
+ );
376
+
377
+ watch(
378
+ () => props.options,
379
+ (newOptions: SelectOption[]) => {
380
+ if (newOptions.length > 0) {
381
+ items.value = newOptions;
382
+
383
+ if (props.modelValue && selectedIndexes.value.length === 0) {
384
+ setValue(props.modelValue);
385
+ }
386
+ }
387
+ },
388
+ );
389
+ </script>
390
+
391
+ <template>
392
+ <div :class="{ [formStyle.input]: !props.disableStyle }">
393
+ <div
394
+ ref="wrapperRef"
395
+ :class="{
396
+ 'h-9': !props.disableStyle && !props.multiple,
397
+ 'min-h-9': !props.disableStyle && props.multiple,
398
+ 'text-left select-none input-bg-color input-text-color border input-border input-roundness':
399
+ !props.disableStyle,
400
+ [formStyle.input]: !props.disableStyle,
401
+ }"
402
+ class="inline-flex justify-between items-center w-full text-left"
403
+ role="button"
404
+ @click="toggleDropdown"
405
+ @blur="onBlur"
406
+ @keyup.esc="onEsc"
407
+ >
408
+ <div
409
+ v-if="!showSearchbox"
410
+ class="flex items-center ml-2"
411
+ :class="{
412
+ 'overflow-hidden whitespace-nowrap': !props.multiple,
413
+ 'flex-wrap': props.multiple,
414
+ }"
415
+ >
416
+ <!-- Render selection with 'selection' slot, highest priority -->
417
+ <template v-if="$slots.selection && hasValue">
418
+ <slot
419
+ name="selection"
420
+ v-bind="items[selectedIndexes[0]].label"
421
+ />
422
+ </template>
423
+
424
+ <!-- Render selection with default slot passed -->
425
+ <template v-else-if="$slots.default && hasValue">
426
+ <template v-if="props.multiple">
427
+ <template v-for="selection in selectedIndexes">
428
+ <slot v-bind="items[selection].label" />
429
+ </template>
430
+ </template>
431
+ <slot
432
+ v-else
433
+ v-bind="items[selectedIndexes[0]].label"
434
+ />
435
+ </template>
436
+
437
+ <!-- No slot passed, render selection ourselves -->
438
+ <template v-else-if="hasValue">
439
+ <template v-if="props.multiple">
440
+ <t-tag
441
+ v-for="selection in selectedIndexes"
442
+ :key="selection"
443
+ :text="items[selection].label"
444
+ color="#6e6e6e"
445
+ />
446
+ </template>
447
+ <span v-else>{{ items[selectedIndexes[0]]?.label }}</span>
448
+ </template>
449
+ </div>
450
+ <input
451
+ v-else
452
+ v-model="search"
453
+ type="text"
454
+ ref="inputRef"
455
+ :placeholder="props.placeholder"
456
+ :disabled="props.disabled"
457
+ :readonly="!props.isSearchable"
458
+ :class="{ 'cursor-pointer': !props.isSearchable }"
459
+ class="w-full h-9 bg-transparent input-padding input-roundness input-outline"
460
+ @click="(e) => e.stopPropagation()"
461
+ @input="throttle"
462
+ @keydown="onKeyDown"
463
+ />
464
+ <t-icon-button
465
+ :disable-style="true"
466
+ class="h-9"
467
+ :icon="listVisible === true ? 'chevron-up' : 'chevron-down'"
468
+ @click="toggleDropdown"
469
+ />
470
+ </div>
471
+ <t-popover
472
+ :placement="props.dropdownPlacement"
473
+ :is-visible="listVisible"
474
+ >
475
+ <template v-slot:content>
476
+ <t-dropdown
477
+ ref="dropdownRef"
478
+ :owner="name"
479
+ :items="items"
480
+ :is-visible="listVisible"
481
+ :multiple="props.multiple"
482
+ class="fixed"
483
+ aria-describedby="tooltip"
484
+ @change="onSelect"
485
+ @end-reached="onListEnd"
486
+ >
487
+ <template
488
+ v-if="$slots.default"
489
+ #default="data"
490
+ >
491
+ <div class="flex w-full justify-between items-center">
492
+ <div class="flex items-center">
493
+ <slot v-bind="data.label" />
494
+ </div>
495
+ <t-icon
496
+ v-if="isOptionSelected(data)"
497
+ icon="check"
498
+ size="sm"
499
+ class="float-right ml-2"
500
+ />
501
+ </div>
502
+ </template>
503
+ </t-dropdown>
504
+ </template>
505
+ </t-popover>
506
+ </div>
507
+ </template>
@@ -0,0 +1,12 @@
1
+ import type { App, Plugin } from "vue";
2
+ import registerComponent from "@/plugins/register-component";
3
+
4
+ import InputSelect from "./InputSelect.vue";
5
+
6
+ export default {
7
+ install(app: App) {
8
+ registerComponent(app, InputSelect);
9
+ },
10
+ } as Plugin;
11
+
12
+ export { InputSelect };
@@ -0,0 +1,34 @@
1
+ export default {
2
+ title: "Form/Switch",
3
+ args: {},
4
+ argTypes: {
5
+ disabled: {
6
+ options: [true, false],
7
+ control: "boolean",
8
+ description: "Disable input",
9
+ table: {
10
+ type: { summary: "bool" },
11
+ defaultValue: { summary: false },
12
+ },
13
+ },
14
+ label: {
15
+ table: { type: { summary: "string" } },
16
+ },
17
+ },
18
+ };
19
+
20
+ const defaultArgs = {
21
+ label: "Encrypt files",
22
+ };
23
+
24
+ const Template = (args: typeof defaultArgs) => ({
25
+ setup() {
26
+ return { args };
27
+ },
28
+ template: `<t-input-switch v-bind="args" />`,
29
+ });
30
+
31
+ const Default: any = Template.bind({});
32
+ Default.args = defaultArgs;
33
+
34
+ export { Default };