@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,63 @@
1
+ <script lang="ts" setup>
2
+ import { ref, watch } from "vue";
3
+
4
+ defineOptions({ name: "TInputAvatar" });
5
+
6
+ const props = defineProps<{
7
+ modelValue?: File | string;
8
+ }>();
9
+
10
+ const emit = defineEmits<{
11
+ (e: "update:modelValue", val: File): void;
12
+ }>();
13
+
14
+ const src = ref<string>();
15
+ const value = ref<FileList>();
16
+
17
+ const onChangeAvatar = async (event: Event): Promise<void> => {
18
+ const target = event.target as HTMLInputElement;
19
+ const files = target.files as FileList;
20
+
21
+ src.value = URL.createObjectURL(files[0]);
22
+
23
+ emit("update:modelValue", files[0]);
24
+ };
25
+
26
+ watch(
27
+ () => props.modelValue,
28
+ (newValue) => {
29
+ if (typeof newValue === "string") {
30
+ src.value = newValue;
31
+ }
32
+ },
33
+ { immediate: true },
34
+ );
35
+ </script>
36
+
37
+ <template>
38
+ <div class="relative h-40 w-40">
39
+ <t-avatar
40
+ size="xl"
41
+ :image="src"
42
+ icon="account"
43
+ class="text-white"
44
+ />
45
+ <label
46
+ for="file"
47
+ class="absolute transform bottom-1 right-2"
48
+ >
49
+ <t-icon
50
+ icon="camera"
51
+ size="lg"
52
+ class="rounded-full cursor-pointer p-2 color-caipira-primary-inverted bg-caipira-primary"
53
+ />
54
+ </label>
55
+ <input
56
+ id="file"
57
+ :value="value"
58
+ type="file"
59
+ class="hidden"
60
+ @change="onChangeAvatar"
61
+ />
62
+ </div>
63
+ </template>
@@ -0,0 +1,12 @@
1
+ import type { App, Plugin } from "vue";
2
+ import registerComponent from "@/plugins/register-component";
3
+
4
+ import InputAvatar from "./InputAvatar.vue";
5
+
6
+ export default {
7
+ install(app: App) {
8
+ registerComponent(app, InputAvatar);
9
+ },
10
+ } as Plugin;
11
+
12
+ export { InputAvatar };
@@ -0,0 +1,77 @@
1
+ <script lang="ts" setup>
2
+ import { ref, watch, inject } from "vue";
3
+
4
+ defineOptions({ name: "TInputCheckbox" });
5
+
6
+ const props = withDefaults(
7
+ defineProps<{
8
+ key?: string;
9
+ disabled?: boolean;
10
+ modelValue?: boolean;
11
+ isIndeterminate?: boolean;
12
+ initializeValue?: boolean;
13
+ }>(),
14
+ {
15
+ key: "checkbox",
16
+ disabled: false,
17
+ isIndeterminate: false,
18
+ initializeValue: false,
19
+ },
20
+ );
21
+
22
+ /**
23
+ * "update:model-value" is emitted whenever the model value changes, even if that change
24
+ * was triggered by the component itself, and the "input" event is emitted only when the
25
+ * user interacts with the component.
26
+ */
27
+ const emit = defineEmits<{
28
+ "update:model-value": [boolean];
29
+ input: [boolean];
30
+ }>();
31
+
32
+ const name = inject("id", "");
33
+ const checked = ref<boolean | undefined>(false);
34
+ const formStyle = inject("formStyle", { label: "", input: "" });
35
+
36
+ const onInput = () => {
37
+ const value = !checked.value;
38
+
39
+ /**
40
+ * Always emit "update:model-value" first, so that we can safely check the model value
41
+ * in the input event handler.
42
+ */
43
+ emit("update:model-value", value);
44
+ emit("input", value);
45
+ };
46
+
47
+ watch(
48
+ () => props.modelValue,
49
+ (newValue?: boolean | undefined) => {
50
+ if (props.initializeValue && typeof newValue === "undefined") {
51
+ emit("update:model-value", false);
52
+ }
53
+
54
+ checked.value = newValue;
55
+ },
56
+ { immediate: true },
57
+ );
58
+ </script>
59
+
60
+ <template>
61
+ <div
62
+ :class="{
63
+ [formStyle.label]: true,
64
+ }"
65
+ >
66
+ <input
67
+ v-model="checked"
68
+ v-bind="$attrs"
69
+ type="checkbox"
70
+ class="form-checkbox relative w-5 h-5 border input-border input-roundness color-caipira-primary-inverted bg-caipira-primary checked:bg-caipira-primary-inverted cursor-pointer input-outline"
71
+ :indeterminate="props.isIndeterminate"
72
+ :id="name"
73
+ :disabled="disabled"
74
+ @input="onInput"
75
+ />
76
+ </div>
77
+ </template>
@@ -0,0 +1,12 @@
1
+ import type { App, Plugin } from "vue";
2
+ import registerComponent from "@/plugins/register-component";
3
+
4
+ import InputCheckbox from "./InputCheckbox.vue";
5
+
6
+ export default {
7
+ install(app: App) {
8
+ registerComponent(app, InputCheckbox);
9
+ },
10
+ } as Plugin;
11
+
12
+ export { InputCheckbox };
@@ -0,0 +1,54 @@
1
+ <script lang="ts" setup>
2
+ import { inject, ref, watch } from "vue";
3
+
4
+ defineOptions({ name: "TInputColor" });
5
+
6
+ const props = withDefaults(
7
+ defineProps<{
8
+ disabled?: boolean;
9
+ modelValue?: string;
10
+ disableStyle?: boolean;
11
+ }>(),
12
+ {
13
+ disabled: false,
14
+ disableStyle: false,
15
+ },
16
+ );
17
+
18
+ const emit = defineEmits<{
19
+ (e: "update:modelValue", val: string): void;
20
+ }>();
21
+
22
+ const color = ref<string>("");
23
+ const formStyle = inject("formStyle", { label: "", input: "" });
24
+
25
+ const onChange = ({ target }: Event) => {
26
+ const value = (target as HTMLInputElement).value;
27
+
28
+ if (!value) {
29
+ return;
30
+ }
31
+
32
+ emit("update:modelValue", value);
33
+ };
34
+
35
+ watch(
36
+ () => props.modelValue,
37
+ (value?: string) => {
38
+ if (value && value !== color.value) {
39
+ color.value = value;
40
+ }
41
+ },
42
+ { immediate: true },
43
+ );
44
+ </script>
45
+
46
+ <template>
47
+ <input
48
+ type="color"
49
+ class="h-10 border-0 input-bg-color input-text-color input-outline input-roundnes input-roundness w-12"
50
+ :value="color"
51
+ :disabled="props.disabled"
52
+ @input="onChange"
53
+ />
54
+ </template>
@@ -0,0 +1,12 @@
1
+ import type { App, Plugin } from "vue";
2
+ import registerComponent from "@/plugins/register-component";
3
+
4
+ import InputColor from "./InputColor.vue";
5
+
6
+ export default {
7
+ install(app: App) {
8
+ registerComponent(app, InputColor);
9
+ },
10
+ } as Plugin;
11
+
12
+ export { InputColor };
@@ -0,0 +1,15 @@
1
+ export default {
2
+ title: "Form/Date",
3
+ };
4
+
5
+ const Template1 = (args: any) => ({
6
+ setup() {
7
+ return { args };
8
+ },
9
+ template: `<t-input-date v-model="args.value" />`,
10
+ });
11
+
12
+ const Default: any = Template1.bind({});
13
+ Default.args = { value: "1996-10-06" };
14
+
15
+ export { Default };
@@ -0,0 +1,368 @@
1
+ <script lang="ts" setup>
2
+ import uiService from "@/services/ui";
3
+ import dateService from "@/services/date";
4
+ import { watch, ref, computed, inject, onBeforeMount } from "vue";
5
+
6
+ type Day = {
7
+ key: string;
8
+ day: number;
9
+ isToday: boolean;
10
+ overflow: number;
11
+ selected: boolean;
12
+ };
13
+
14
+ defineOptions({ name: "TInputDate" });
15
+
16
+ const props = defineProps<{
17
+ value?: Date | string;
18
+ modelValue?: Date | string;
19
+ initialValue?: Date;
20
+ }>();
21
+
22
+ const emit = defineEmits<{
23
+ (e: "update:model-value", val: Date): void;
24
+ }>();
25
+
26
+ const today = new Date();
27
+ const days: any = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
28
+ const formStyle = inject("formStyle", { input: "w-full" });
29
+ const wrapperRef = ref<HTMLElement | null>(null);
30
+ const showDatepicker = ref<boolean>(false);
31
+ const canGoNextMonth = true;
32
+ const canGoPreviousMonth = true;
33
+
34
+ /**
35
+ * Date being displayed in the datepicker dropdown
36
+ */
37
+ const date = ref<Date>(new Date());
38
+ const displayYear = ref<number>(date.value.getFullYear());
39
+ const displayMonth = ref<number>(date.value.getMonth());
40
+
41
+ watch(
42
+ () => date.value,
43
+ (newDate: Date) => {
44
+ displayMonth.value = newDate.getMonth();
45
+ displayYear.value = newDate.getFullYear();
46
+ },
47
+ { immediate: true },
48
+ );
49
+
50
+ /**
51
+ * v-model
52
+ */
53
+ let selectedDay: number;
54
+ let selectedYear: number;
55
+ let selectedMonth: number;
56
+
57
+ watch(
58
+ () => props.modelValue,
59
+ () => {
60
+ if (props.modelValue) {
61
+ switch (typeof props.modelValue) {
62
+ case "string":
63
+ const newDate = new Date(props.modelValue);
64
+
65
+ if (newDate) {
66
+ emit("update:model-value", newDate);
67
+ }
68
+ break;
69
+ case "object":
70
+ date.value = props.modelValue;
71
+
72
+ selectedDay = date.value.getDate();
73
+ selectedYear = date.value.getFullYear();
74
+ selectedMonth = date.value.getMonth();
75
+
76
+ break;
77
+ }
78
+ }
79
+ },
80
+ { immediate: true },
81
+ );
82
+
83
+ /**
84
+ * Value shown in the input field
85
+ */
86
+ const inputValue = computed<string>(() => {
87
+ if (!props.modelValue) {
88
+ return "";
89
+ }
90
+
91
+ const localDate =
92
+ typeof props.modelValue === "string"
93
+ ? new Date(props.modelValue)
94
+ : props.modelValue;
95
+
96
+ return dateService.format(localDate, "yyyy-MM-dd");
97
+ });
98
+
99
+ /**
100
+ * Year select
101
+ */
102
+ const yearOptions = computed(() => {
103
+ const currentYear = today.getFullYear();
104
+
105
+ return Array.from({ length: 49 }, (_, i) => currentYear - 1 - i)
106
+ .reverse()
107
+ .concat(Array.from({ length: 51 }, (_, i) => currentYear + i));
108
+ });
109
+
110
+ const onSelectYear = (event: Event) =>
111
+ (date.value = dateService.setYear(
112
+ date.value,
113
+ (event?.target as HTMLInputElement)?.value as unknown as number,
114
+ ));
115
+
116
+ /**
117
+ * Reset datepicker display and close it
118
+ */
119
+ const closeDatepicker = () => {
120
+ showDatepicker.value = false;
121
+ date.value = selectedDay ? (props.modelValue as Date) : new Date();
122
+ };
123
+
124
+ const setDay = (localDay: Day) => {
125
+ if (localDay.overflow) {
126
+ date.value =
127
+ localDay.overflow > 0
128
+ ? dateService.addMonths(date.value, localDay.overflow)
129
+ : dateService.subMonths(date.value, ~localDay.overflow + 1);
130
+ }
131
+
132
+ date.value = dateService.setDate(date.value, localDay.day);
133
+
134
+ emit("update:model-value", date.value);
135
+ closeDatepicker();
136
+ };
137
+
138
+ const goPreviousMonth = () => {
139
+ date.value = dateService.subMonths(date.value, 1);
140
+ };
141
+
142
+ const goNextMonth = () => {
143
+ date.value = dateService.addMonths(date.value, 1);
144
+ };
145
+
146
+ const items = computed<Day[]>(() => {
147
+ const total = 42;
148
+ const days: Day[] = [];
149
+
150
+ const dayOfWeek = new Date(displayYear.value, displayMonth.value).getDay();
151
+ const daysLastMonth = dateService.getDaysInMonth(
152
+ dateService.subMonths(date.value, 1),
153
+ );
154
+ const daysCurrentMonth = dateService.getDaysInMonth(date.value);
155
+
156
+ const todayDay = today.getDate();
157
+ const todayYear = today.getFullYear();
158
+ const todayMonth = today.getMonth();
159
+
160
+ let index = 1;
161
+ let hoistedMonth = displayMonth.value - 1;
162
+
163
+ for (let i = daysLastMonth - (dayOfWeek - 1); i <= daysLastMonth; i++) {
164
+ days.push({
165
+ key: `${i}-${todayMonth - 1}`,
166
+ day: i,
167
+ isToday:
168
+ todayDay === i &&
169
+ todayMonth === hoistedMonth &&
170
+ todayYear === displayYear.value,
171
+ selected:
172
+ selectedDay === i &&
173
+ selectedMonth === hoistedMonth &&
174
+ selectedYear === displayYear.value,
175
+ overflow: -1,
176
+ });
177
+
178
+ index++;
179
+ }
180
+
181
+ hoistedMonth = displayMonth.value;
182
+
183
+ for (let i = 1; i <= daysCurrentMonth; i++) {
184
+ days.push({
185
+ key: `${i}-${todayMonth}`,
186
+ day: i,
187
+ isToday:
188
+ todayDay === i &&
189
+ todayMonth === hoistedMonth &&
190
+ todayYear === displayYear.value,
191
+ selected:
192
+ selectedDay === i &&
193
+ selectedMonth === hoistedMonth &&
194
+ selectedYear === displayYear.value,
195
+ overflow: 0,
196
+ });
197
+
198
+ index++;
199
+ }
200
+
201
+ hoistedMonth = displayMonth.value + 1;
202
+
203
+ for (let i = 1; index <= total; i++) {
204
+ days.push({
205
+ key: `${i}-${todayMonth + 1}`,
206
+ day: i,
207
+ isToday:
208
+ todayDay === i &&
209
+ todayMonth === hoistedMonth &&
210
+ todayYear === displayYear.value,
211
+ selected:
212
+ selectedDay === i &&
213
+ selectedMonth === hoistedMonth &&
214
+ selectedYear === displayYear.value,
215
+ overflow: 1,
216
+ });
217
+
218
+ index++;
219
+ }
220
+
221
+ return days;
222
+ });
223
+
224
+ const onInputClick = () => {
225
+ if (showDatepicker.value) {
226
+ closeDatepicker();
227
+ } else {
228
+ showDatepicker.value = true;
229
+ }
230
+ };
231
+
232
+ const off = (event: MouseEvent) => {
233
+ if (
234
+ wrapperRef.value &&
235
+ uiService.wasClickOutsideOfContainer(event, wrapperRef.value)
236
+ ) {
237
+ closeDatepicker();
238
+ }
239
+ };
240
+
241
+ onBeforeMount(() => {
242
+ document.addEventListener("click", off);
243
+
244
+ if (!props.modelValue && props.initialValue) {
245
+ emit("update:model-value", props.initialValue);
246
+ }
247
+ });
248
+ </script>
249
+
250
+ <template>
251
+ <div
252
+ class="inline-block relative"
253
+ ref="wrapperRef"
254
+ :class="{ [formStyle.input]: true }"
255
+ >
256
+ <input
257
+ type="text"
258
+ readonly
259
+ :value="inputValue"
260
+ @click="onInputClick"
261
+ @keydown.escape="closeDatepicker"
262
+ class="w-full pl-2 pr-10 py-2 leading-none input-bg-color input-text-color border input-border input-roundness input-outline"
263
+ placeholder="Select date"
264
+ />
265
+ <div
266
+ class="mt-9 shadow-md p-4 absolute top-0 left-0 select-none z-10 bg-caipira-primary border input-border input-roundness"
267
+ style="width: 17rem"
268
+ v-show="showDatepicker"
269
+ >
270
+ <!-- Header -->
271
+ <div class="flex justify-between items-center mb-2">
272
+ <div>
273
+ <!-- Month -->
274
+ <span class="text-lg font-bold">
275
+ {{ dateService.format(date, "MMMM") }}
276
+ </span>
277
+
278
+ <select
279
+ class="ml-2 text-lg input-bg-color border input-border input-outline"
280
+ @change="onSelectYear"
281
+ >
282
+ <option
283
+ v-for="year in yearOptions"
284
+ :key="year"
285
+ :value="year"
286
+ :selected="year === displayYear"
287
+ >
288
+ {{ year }}
289
+ </option>
290
+ </select>
291
+ </div>
292
+
293
+ <!-- Previous/Next month -->
294
+ <div>
295
+ <button
296
+ type="button"
297
+ class="transition ease-in-out duration-100 inline-flex cursor-pointer hover:bg-hover p-1 rounded-full"
298
+ :class="{
299
+ 'cursor-not-allowed opacity-25':
300
+ !canGoPreviousMonth,
301
+ }"
302
+ :disabled="!canGoPreviousMonth"
303
+ @click="goPreviousMonth"
304
+ >
305
+ <t-icon icon="chevron-left" />
306
+ </button>
307
+ <button
308
+ type="button"
309
+ class="transition ease-in-out duration-100 inline-flex cursor-pointer p-1 rounded-full hover:bg-hover"
310
+ :class="{
311
+ 'cursor-not-allowed opacity-25':
312
+ !canGoPreviousMonth,
313
+ }"
314
+ :disabled="!canGoPreviousMonth"
315
+ @click="goNextMonth"
316
+ >
317
+ <t-icon icon="chevron-right" />
318
+ </button>
319
+ </div>
320
+ </div>
321
+
322
+ <!-- Week days -->
323
+ <div class="flex flex-wrap mb-3 -mx-1">
324
+ <template
325
+ v-for="localDay in days"
326
+ :key="localDay"
327
+ >
328
+ <div
329
+ style="width: 14.26%"
330
+ class="px-1"
331
+ >
332
+ <div class="font-medium text-center text-xs">
333
+ {{ localDay }}
334
+ </div>
335
+ </div>
336
+ </template>
337
+ </div>
338
+
339
+ <!-- Month days -->
340
+ <div class="flex flex-wrap -mx-1">
341
+ <template
342
+ v-for="localDay in items"
343
+ :key="localDay.key"
344
+ >
345
+ <div
346
+ style="width: 14.28%"
347
+ class="px-1 mb-1"
348
+ >
349
+ <div
350
+ @click="setDay(localDay)"
351
+ class="cursor-pointer text-center text-sm rounded leading-loose transition ease-in-out duration-100"
352
+ :class="{
353
+ 'hover:bg-hover': !localDay.selected,
354
+ 'color-unfocused-text': localDay.overflow !== 0,
355
+ 'border border-caipira-primary-inverted':
356
+ localDay.isToday,
357
+ 'color-caipira-primary bg-caipira-primary-inverted':
358
+ localDay.selected,
359
+ }"
360
+ >
361
+ {{ localDay.day }}
362
+ </div>
363
+ </div>
364
+ </template>
365
+ </div>
366
+ </div>
367
+ </div>
368
+ </template>
@@ -0,0 +1,12 @@
1
+ import type { App, Plugin } from "vue";
2
+ import registerComponent from "@/plugins/register-component";
3
+
4
+ import InputDate from "./InputDate.vue";
5
+
6
+ export default {
7
+ install(app: App) {
8
+ registerComponent(app, InputDate);
9
+ },
10
+ } as Plugin;
11
+
12
+ export { InputDate };