pukaad-ui-lib 1.72.0 → 1.74.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.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
3
  "configKey": "pukaadUI",
4
- "version": "1.72.0",
4
+ "version": "1.74.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -0,0 +1,26 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="180" height="123" viewBox="0 0 180 123" fill="none">
2
+ <g clip-path="url(#clip0_5616_301)">
3
+ <path d="M167.793 23.2002C162.111 19.4053 155.211 16.7871 149.617 14.0565C139.302 9.02603 128.278 3.54088 116.957 1.23824C102.668 -1.66881 87.2018 0.802313 73.6832 5.9291C62.0371 10.3472 51.0108 17.905 45.7471 29.5385C45.4815 30.1269 45.232 30.7206 44.9959 31.3223C34.6537 57.8414 8.52055 63.6769 3.50371 75.3506C-1.51314 87.0216 31.1017 121.2 65.5972 122.869C100.093 124.535 99.8244 110.032 131.245 110.781C158.886 111.439 174.986 86.0588 178.865 61.5588C180.308 52.4339 180.982 42.084 177.124 33.4404C175.206 29.14 171.801 25.88 167.796 23.2029L167.793 23.2002Z" fill="#E3F2FD"/>
4
+ <path opacity="0.7" d="M167.793 23.2002C162.111 19.4053 155.211 16.7871 149.617 14.0565C139.302 9.02603 128.278 3.54088 116.957 1.23824C102.668 -1.66881 87.2018 0.802313 73.6832 5.9291C62.0371 10.3472 51.0108 17.905 45.7471 29.5385C45.4815 30.1269 45.232 30.7206 44.9959 31.3223C34.6537 57.8414 8.52055 63.6769 3.50371 75.3506C-1.51314 87.0216 31.1017 121.2 65.5972 122.869C100.093 124.535 99.8244 110.032 131.245 110.781C158.886 111.439 174.986 86.0588 178.865 61.5588C180.308 52.4339 180.982 42.084 177.124 33.4404C175.206 29.14 171.801 25.88 167.796 23.2029L167.793 23.2002Z" fill="white"/>
5
+ <path d="M144.056 7.73697C143.581 8.54998 143.375 9.33357 143.265 9.82833C142.953 11.227 141.861 12.0026 139.527 10.9141C137.193 9.82566 135.01 10.4488 134.387 12.155C133.765 13.864 133.454 15.26 130.806 14.6395C128.158 14.0191 126.755 13.864 125.977 14.9498C125.199 16.0356 125.043 16.8138 125.043 16.8138H159.992" stroke="#22A7EF" stroke-miterlimit="10"/>
6
+ <path d="M161.703 16.8165H166.785C167.096 16.8165 166.291 14.4176 166.2 14.2705C164.424 11.3982 161.899 13.5885 159.466 12.7782C159.466 12.7782 156.196 4.39401 150.43 4.54912C147.723 4.62133 146.047 5.48248 144.998 6.52014" stroke="#22A7EF" stroke-miterlimit="10"/>
7
+ <path d="M16.907 32.6167H39.6411C39.8584 32.6167 39.2977 30.9479 39.2333 30.8436C37.9965 28.8432 36.2393 30.3676 34.5437 29.8059C34.5437 29.8059 33.256 26.5031 30.9085 24.9118" stroke="#22A7EF" stroke-miterlimit="10"/>
8
+ <path d="M29.6772 24.2914C29.2319 24.1416 28.757 24.0614 28.2554 24.0774C24.2419 24.1844 23.4826 26.7812 23.2653 27.752C23.048 28.7255 22.2888 29.2657 20.663 28.5089C19.0372 27.752 17.5188 28.1853 17.0842 29.3727C16.6496 30.5601 16.4322 31.5363 14.5892 31.103C12.7461 30.6698 11.7695 30.5628 11.2276 31.3196C10.6857 32.0765 10.5757 32.6167 10.5757 32.6167H15.0801" stroke="#22A7EF" stroke-miterlimit="10"/>
9
+ <path d="M102.335 55.3222C101.828 55.1243 101.284 55.0146 100.702 55.0307C96.6882 55.1377 95.9289 57.7345 95.7116 58.7053C95.4943 59.6761 94.7351 60.219 93.1093 59.4621C91.4835 58.7053 89.9651 59.1385 89.5305 60.326C89.0958 61.5161 88.8785 62.4895 87.0355 62.0563C85.1924 61.623 84.2158 61.5161 83.6739 62.2729C83.132 63.0298 83.022 63.57 83.022 63.57H112.087C112.305 63.57 111.744 61.9012 111.68 61.7969C110.443 59.7964 108.686 61.3208 106.99 60.7592C106.99 60.7592 105.888 57.9351 103.867 56.2502" stroke="#22A7EF" stroke-miterlimit="10"/>
10
+ <path d="M136.222 12.2166L130.247 34.585L125.166 53.5999L108.651 115.423H89.5789L106.094 53.5999L111.173 34.585L117.147 12.2166H136.222Z" fill="#22A7EF"/>
11
+ <path opacity="0.14" d="M136.222 12.2166L130.247 34.585L125.166 53.5999L108.651 115.423H89.5789L106.094 53.5999L111.173 34.585L117.147 12.2166H136.222Z" fill="white"/>
12
+ <path d="M130.248 34.5851L125.166 53.5999H106.094L111.173 34.5851H130.248Z" fill="#1976B8"/>
13
+ <path d="M118.529 78.6909L113.448 97.7057H94.3757L99.4543 78.6909H118.529Z" fill="#1976B8"/>
14
+ <path d="M91.8002 12.2166L85.8256 34.585L80.7444 53.5999L64.229 115.423H45.1543L61.6696 53.5999L66.7509 34.585L72.7255 12.2166H91.8002Z" fill="#22A7EF"/>
15
+ <path opacity="0.14" d="M91.8002 12.2166L85.8256 34.585L80.7444 53.5999L64.229 115.423H45.1543L61.6696 53.5999L66.7509 34.585L72.7255 12.2166H91.8002Z" fill="white"/>
16
+ <path d="M74.1071 78.6909L69.0259 97.7057H49.9512L55.0324 78.6909H74.1071Z" fill="#1976B8"/>
17
+ <path d="M85.8256 34.5851L80.7444 53.5999H61.6697L66.7509 34.5851H85.8256Z" fill="#1976B8"/>
18
+ <path d="M30.0608 95.5448L35.3084 76.53H138.843L133.596 95.5448H30.0608Z" fill="#1976B8"/>
19
+ <path d="M44.344 51.5861L49.5942 32.5713H153.126L147.879 51.5861H44.344Z" fill="#1976B8"/>
20
+ </g>
21
+ <defs>
22
+ <clipPath id="clip0_5616_301">
23
+ <rect width="180" height="123" fill="white"/>
24
+ </clipPath>
25
+ </defs>
26
+ </svg>
@@ -1,10 +1,29 @@
1
+ export interface DrawerPostBlogItem {
2
+ title: string;
3
+ content: object[];
4
+ tags: string[];
5
+ disableComment: boolean;
6
+ coverImage: File[];
7
+ }
8
+ export interface DrawerPostBlogProps {
9
+ item?: DrawerPostBlogItem;
10
+ }
11
+ type __VLS_Props = DrawerPostBlogProps;
1
12
  type __VLS_ModelProps = {
2
13
  modelValue?: boolean;
3
14
  };
4
- declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
16
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
5
17
  "update:modelValue": (value: boolean) => any;
6
- }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
18
+ } & {
19
+ submit: (data: DrawerPostBlogItem) => any;
20
+ saveDraft: (data: DrawerPostBlogItem) => any;
21
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
22
+ onSubmit?: ((data: DrawerPostBlogItem) => any) | undefined;
7
23
  "onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
8
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
+ onSaveDraft?: ((data: DrawerPostBlogItem) => any) | undefined;
25
+ }>, {
26
+ item: DrawerPostBlogItem;
27
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
28
  declare const _default: typeof __VLS_export;
10
29
  export default _default;
@@ -1,89 +1,122 @@
1
1
  <template>
2
- <Drawer class="w-[748px]" title="เพิ่มบล็อก" v-model="isOpen">
3
- <div class="flex flex-col gap-4">
2
+ <Drawer
3
+ class="w-[748px]"
4
+ :title="drawerTitle"
5
+ @close="onClose"
6
+ disabled-auto-close
7
+ @submit="onSubmit"
8
+ v-model="isOpen"
9
+ >
10
+ <div class="flex flex-col gap-[16px]">
4
11
  <InputTextField
12
+ name="title"
5
13
  label="หัวข้อ"
6
14
  placeholder="ตั้งชื่อบทความให้น่าสนใจ"
7
15
  showCounter
8
16
  :limit="180"
17
+ v-model="form.title"
18
+ disabled-border
9
19
  required
10
20
  />
11
- <InputContent placeholder="อยากเล่าอะไร" />
12
21
 
13
- <InputCheckbox label="ไม่อนุญาตให้แสดงความคิดเห็น" />
14
- <div class="h-[500px] w-full bg-red-500"></div>
15
- <div class="h-[500px] w-full bg-red-500"></div>
16
- <!-- <div class="flex flex-col gap-[8px]">
22
+ <InputContent
23
+ placeholder="อยากเล่าอะไร"
24
+ :height="288"
25
+ v-model="form.content"
26
+ />
27
+
28
+ <InputTag
29
+ v-model="form.tags"
30
+ name="tags"
31
+ label="แท็ก"
32
+ placeholder="เพิ่มแท็ก"
33
+ :limit="5"
34
+ />
35
+
36
+ <InputCheckbox
37
+ name="disableComment"
38
+ label="ไม่อนุญาตให้แสดงความคิดเห็น"
39
+ v-model="form.disableComment"
40
+ />
41
+
42
+ <div class="flex flex-col gap-[8px]">
17
43
  <div class="text-gray font-body-large">ภาพหน้าปก</div>
18
44
  <InputFile
19
- accept="image/jpeg,image/png,image/webp,image/bmp,image/gif"
20
45
  :limit="1"
46
+ name="coverImage"
47
+ accept="image/jpeg,image/png,image/webp,image/bmp,image/gif"
48
+ v-model="form.coverImage"
21
49
  />
22
50
  <div class="flex flex-col text-gray font-body-small">
23
51
  <div>รองรับไฟล์ *.jpg *.jpeg *.png *.webp *.bmp *.gif</div>
24
52
  <div>ขนาดไม่เกิน 30 mb</div>
25
53
  </div>
26
- </div> -->
27
- </div>
28
- </Drawer>
29
- <!-- <Drawer
30
- :width="788"
31
- title="เล่าเรื่องราวของคุณ"
32
- v-model="isOpen"
33
- @submit="onSubmit"
34
- >
35
- <Card full-width>
36
- <div class="flex flex-col gap-[16px]">
37
- <InputTextField
38
- placeholder="ตั้งชื่อบทความให้น่าสนใจ"
39
- label="หัวข้อ"
40
- fullWidth
41
- showCounter
42
- disabled-border
43
- :limit="180"
44
- required
45
- v-model="state.title"
46
- />
47
- <InputContent
48
- placeholder="อยากเล่าอะไร"
49
- class="h-[140px]"
50
- v-model="state.content"
51
- />
52
- <InputTag v-model="state.tag" />
53
- <InputCheckbox
54
- label="ไม่อนุญาตให้แสดงความคิดเห็น"
55
- :label-gap="10"
56
- v-model="state.disabled_comment"
57
- />
58
- <div class="flex flex-col gap-[8px]">
59
- <div class="text-gray font-body-large">ภาพหน้าปก</div>
60
- <InputFile
61
- accept="image/jpeg,image/png,image/webp,image/bmp,image/gif"
62
- :limit="1"
63
- v-model="state.file"
64
- />
65
- <div class="flex flex-col text-gray font-body-small">
66
- <div>รองรับไฟล์ *.jpg *.jpeg *.png *.webp *.bmp *.gif</div>
67
- <div>ขนาดไม่เกิน 30 mb</div>
68
- </div>
69
- </div>
70
54
  </div>
71
- </Card>
55
+ </div>
72
56
 
73
57
  <template #footer="{ meta }">
74
- <div class="flex justify-end items-center gap-[16px]">
75
- <Button variant="text" color="black" @click="onClose"> ยกเลิก </Button>
76
- <Button variant="solid" @click="onSave"> บันทึกแบบร่าง </Button>
58
+ <div class="flex justify-end gap-[16px] items-center">
59
+ <Button variant="outline" @click="onClose">ยกเลิก</Button>
60
+ <Button @click="onSaveDraft"> บันทึกแบบร่าง </Button>
77
61
  <Button type="submit" color="primary" :disabled="!meta.valid">
78
- ยืนยัน
62
+ เผยแพร่
79
63
  </Button>
80
64
  </div>
81
65
  </template>
82
- </Drawer> -->
66
+ </Drawer>
83
67
  </template>
84
68
 
85
69
  <script setup>
70
+ import { ref, watch, computed } from "vue";
71
+ import { useNuxtApp } from "nuxt/app";
72
+ const { $alert } = useNuxtApp();
73
+ const emit = defineEmits(["submit", "saveDraft"]);
74
+ const props = defineProps({
75
+ item: { type: Object, required: false, default: () => ({
76
+ title: "",
77
+ content: [],
78
+ tags: [],
79
+ disableComment: false,
80
+ coverImage: []
81
+ }) }
82
+ });
83
+ const form = ref(JSON.parse(JSON.stringify(props.item)));
86
84
  const isOpen = defineModel({ type: Boolean, ...{
87
85
  default: false
88
86
  } });
87
+ const drawerTitle = computed(() => {
88
+ const hasTitle = props.item?.title?.trim();
89
+ return hasTitle ? "\u0E41\u0E01\u0E49\u0E44\u0E02\u0E1A\u0E25\u0E47\u0E2D\u0E01" : "\u0E40\u0E1E\u0E34\u0E48\u0E21\u0E1A\u0E25\u0E47\u0E2D\u0E01";
90
+ });
91
+ watch(isOpen, (newVal) => {
92
+ if (newVal) {
93
+ form.value = JSON.parse(JSON.stringify(props.item));
94
+ }
95
+ });
96
+ const onClose = async () => {
97
+ const isModified = JSON.stringify(form.value) !== JSON.stringify(props.item);
98
+ if (isModified) {
99
+ const { isConfirmed } = await $alert.show({
100
+ type: "warning",
101
+ title: "\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23\u0E2D\u0E2D\u0E01\u0E08\u0E32\u0E01\u0E2B\u0E19\u0E49\u0E32\u0E19\u0E35\u0E49\u0E2B\u0E23\u0E37\u0E2D\u0E44\u0E21\u0E48 ?",
102
+ description: "\u0E01\u0E32\u0E23\u0E40\u0E1B\u0E25\u0E35\u0E48\u0E22\u0E19\u0E41\u0E1B\u0E25\u0E07\u0E02\u0E2D\u0E07\u0E04\u0E38\u0E13\u0E22\u0E31\u0E07\u0E44\u0E21\u0E48\u0E44\u0E14\u0E49\u0E23\u0E31\u0E1A\u0E01\u0E32\u0E23\u0E1A\u0E31\u0E19\u0E17\u0E36\u0E01 \u0E04\u0E38\u0E13\u0E15\u0E49\u0E2D\u0E07\u0E01\u0E32\u0E23\u0E25\u0E30\u0E17\u0E34\u0E49\u0E07\u0E01\u0E32\u0E23\u0E40\u0E1B\u0E25\u0E35\u0E48\u0E22\u0E19\u0E41\u0E1B\u0E25\u0E07\u0E2B\u0E23\u0E37\u0E2D\u0E44\u0E21\u0E48 ?",
103
+ confirmText: "\u0E25\u0E30\u0E17\u0E34\u0E49\u0E07",
104
+ cancelText: "\u0E41\u0E01\u0E49\u0E44\u0E02\u0E15\u0E48\u0E2D",
105
+ showCancelBtn: true
106
+ });
107
+ if (isConfirmed) {
108
+ isOpen.value = false;
109
+ }
110
+ } else {
111
+ isOpen.value = false;
112
+ }
113
+ };
114
+ const onSaveDraft = () => {
115
+ emit("saveDraft", form.value);
116
+ console.log("onSaveDraft", form.value);
117
+ };
118
+ const onSubmit = () => {
119
+ emit("submit", form.value);
120
+ isOpen.value = false;
121
+ };
89
122
  </script>
@@ -1,10 +1,29 @@
1
+ export interface DrawerPostBlogItem {
2
+ title: string;
3
+ content: object[];
4
+ tags: string[];
5
+ disableComment: boolean;
6
+ coverImage: File[];
7
+ }
8
+ export interface DrawerPostBlogProps {
9
+ item?: DrawerPostBlogItem;
10
+ }
11
+ type __VLS_Props = DrawerPostBlogProps;
1
12
  type __VLS_ModelProps = {
2
13
  modelValue?: boolean;
3
14
  };
4
- declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
15
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
16
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
5
17
  "update:modelValue": (value: boolean) => any;
6
- }, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
18
+ } & {
19
+ submit: (data: DrawerPostBlogItem) => any;
20
+ saveDraft: (data: DrawerPostBlogItem) => any;
21
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
22
+ onSubmit?: ((data: DrawerPostBlogItem) => any) | undefined;
7
23
  "onUpdate:modelValue"?: ((value: boolean) => any) | undefined;
8
- }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
24
+ onSaveDraft?: ((data: DrawerPostBlogItem) => any) | undefined;
25
+ }>, {
26
+ item: DrawerPostBlogItem;
27
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
28
  declare const _default: typeof __VLS_export;
10
29
  export default _default;
@@ -1,3 +1,25 @@
1
- declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
1
+ interface InputTagProps {
2
+ name?: string;
3
+ rules?: object | string | Function;
4
+ label?: string;
5
+ required?: boolean;
6
+ showCounter?: boolean;
7
+ limit?: number;
8
+ placeholder?: string;
9
+ }
10
+ type __VLS_Props = InputTagProps;
11
+ type __VLS_ModelProps = {
12
+ modelValue?: string[];
13
+ };
14
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
15
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
16
+ "update:modelValue": (value: string[]) => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
18
+ "onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
19
+ }>, {
20
+ name: string;
21
+ placeholder: string;
22
+ limit: number;
23
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
2
24
  declare const _default: typeof __VLS_export;
3
25
  export default _default;
@@ -1,43 +1,269 @@
1
1
  <template>
2
- <!-- <div class="flex flex-col gap-[4px]">
3
- <div class="flex flex-wrap gap-[4px]">
4
- <Chip
5
- v-for="(item, i_item) in items"
6
- :key="i_item"
7
- color="primary"
8
- size="small"
9
- closable
10
- auto-height
11
- @closable="onClosable(i_item)"
12
- >
13
- {{ item }}
14
- </Chip>
15
- <Chip v-if="isInputShow" color="primary" size="small">
16
- <input
17
- ref="inputRef"
18
- class="text-center w-full"
19
- @keyup.enter="onAddTag"
20
- :style="{ width: inputWidth + 'px' }"
21
- @input="onInput"
22
- v-model="inputTag"
23
- />
24
- </Chip>
25
- <Button
26
- v-else
27
- prepend-icon="fa6-solid:plus"
28
- full-rounded
29
- size="small"
30
- @click="showInputTag"
31
- >
32
- เพิ่มแท็ก
33
- </Button>
34
- </div>
35
- <Card v-if="isInputShow" type="outline" full-width disabled-padding-content>
36
- <Tab :items="tabItems" type="text" v-model="tab" />
37
- </Card>
38
- </div> -->
2
+ <ShadPopover v-model:open="popoverOpen">
3
+ <ShadPopoverAnchor as-child>
4
+ <ShadFormField :name="props.name" v-model="modelValue">
5
+ <ShadFormItem>
6
+ <ShadFormLabel v-if="props.label" class="w-full">
7
+ <div class="font-body-large-prominent text-gray-500">
8
+ {{ props.label }}
9
+ <span v-if="props.required" class="text-destructive">*</span>
10
+ </div>
11
+ </ShadFormLabel>
12
+ <div
13
+ class="flex flex-wrap items-center gap-2 min-h-10 w-full rounded-md bg-background px-3 py-2 cursor-text"
14
+ @mousedown="handleContainerClick"
15
+ >
16
+ <!-- Selected Tags as Chips -->
17
+ <Chip
18
+ v-for="tag in selectedTags"
19
+ :key="tag.value"
20
+ closable
21
+ @closable="onRemoveTag(tag.value)"
22
+ class="bg-bright"
23
+ >
24
+ {{ tag.label }}
25
+ </Chip>
26
+ <!-- Input for search -->
27
+ <InputTextField
28
+ v-if="!isLimitReached"
29
+ ref="inputRef"
30
+ id="input-tag-search"
31
+ name="input-tag-search"
32
+ v-model="searchQuery"
33
+ disabled-border
34
+ :placeholder="props.placeholder"
35
+ class="flex-1 min-w-[100px]"
36
+ @keydown="onKeydown"
37
+ />
38
+ </div>
39
+ </ShadFormItem>
40
+ </ShadFormField>
41
+ </ShadPopoverAnchor>
42
+ <ShadPopoverContent
43
+ @openAutoFocus.prevent
44
+ @interactOutside="handleInteractOutside"
45
+ >
46
+ <ShadCommand class="p-2">
47
+ <ShadTabs v-model="currentTab" class="w-full">
48
+ <ShadTabsList class="w-full grid grid-cols-2">
49
+ <ShadTabsTrigger value="popular">ยอดนิยม</ShadTabsTrigger>
50
+ <ShadTabsTrigger value="latest">ล่าสุด</ShadTabsTrigger>
51
+ </ShadTabsList>
52
+
53
+ <!-- Popular Tab -->
54
+ <ShadTabsContent value="popular" class="m-0">
55
+ <ShadCommandList class="max-h-64">
56
+ <ShadCommandGroup>
57
+ <ShadCommandItem
58
+ v-for="tag in filteredPopularTags"
59
+ :key="tag.value"
60
+ :value="tag.value"
61
+ :disabled="isOptionDisabled(tag.value)"
62
+ :class="[
63
+ 'flex w-full justify-between',
64
+ isOptionDisabled(tag.value) && 'cursor-not-allowed opacity-50'
65
+ ]"
66
+ @select="onSelectTag(tag)"
67
+ >
68
+ <span class="font-body-medium">{{ tag.label }}</span>
69
+ <div class="flex items-center gap-2">
70
+ <div class="font-body-small text-gray">
71
+ {{ tag.postCount }}
72
+ </div>
73
+ <Icon
74
+ v-if="isSelected(tag.value)"
75
+ name="lucide:check"
76
+ class="h-4 w-4"
77
+ />
78
+ </div>
79
+ </ShadCommandItem>
80
+ </ShadCommandGroup>
81
+ <ShadCommandEmpty :force-show="filteredPopularTags.length === 0">
82
+ <div
83
+ class="flex flex-col items-center justify-center h-full gap-2"
84
+ >
85
+ <Icon name="pukaad:tag-empty" class="h-[123px] w-[180px]" />
86
+ <div class="text-gray font-body-medium">ไม่พบข้อมูล</div>
87
+ </div>
88
+ </ShadCommandEmpty>
89
+ </ShadCommandList>
90
+ </ShadTabsContent>
91
+
92
+ <!-- Latest Tab -->
93
+ <ShadTabsContent value="latest" class="m-0">
94
+ <ShadCommandList class="max-h-64">
95
+ <ShadCommandGroup>
96
+ <ShadCommandItem
97
+ v-for="tag in filteredLatestTags"
98
+ :key="tag.value"
99
+ :value="tag.value"
100
+ :disabled="isOptionDisabled(tag.value)"
101
+ :class="[
102
+ 'flex w-full justify-between',
103
+ isOptionDisabled(tag.value) && 'cursor-not-allowed opacity-50'
104
+ ]"
105
+ @select="onSelectTag(tag)"
106
+ >
107
+ <span class="font-body-medium">{{ tag.label }}</span>
108
+ <div class="flex items-center gap-2">
109
+ <div class="font-body-small text-gray">
110
+ {{ tag.postCount }}
111
+ </div>
112
+ <Icon
113
+ v-if="isSelected(tag.value)"
114
+ name="lucide:check"
115
+ class="h-4 w-4"
116
+ />
117
+ </div>
118
+ </ShadCommandItem>
119
+ </ShadCommandGroup>
120
+ <ShadCommandEmpty :force-show="filteredLatestTags.length === 0">
121
+ <div
122
+ class="flex flex-col items-center justify-center h-full gap-2"
123
+ >
124
+ <Icon name="pukaad:tag-empty" class="h-[123px] w-[180px]" />
125
+ <div class="text-gray font-body-medium">ไม่พบข้อมูล</div>
126
+ </div>
127
+ </ShadCommandEmpty>
128
+ </ShadCommandList>
129
+ </ShadTabsContent>
130
+ </ShadTabs>
131
+ </ShadCommand>
132
+ </ShadPopoverContent>
133
+ </ShadPopover>
39
134
  </template>
40
135
 
41
136
  <script setup>
42
-
137
+ import { ref, computed, nextTick } from "vue";
138
+ const props = defineProps({
139
+ name: { type: String, required: false, default: "input-tag" },
140
+ rules: { type: [Object, String, Function], required: false },
141
+ label: { type: String, required: false },
142
+ required: { type: Boolean, required: false },
143
+ showCounter: { type: Boolean, required: false },
144
+ limit: { type: Number, required: false, default: 0 },
145
+ placeholder: { type: String, required: false, default: "\u0E04\u0E49\u0E19\u0E2B\u0E32\u0E41\u0E17\u0E47\u0E01..." }
146
+ });
147
+ const modelValue = defineModel({ type: Array, ...{
148
+ default: () => []
149
+ } });
150
+ const popoverOpen = ref(false);
151
+ const currentTab = ref("popular");
152
+ const searchQuery = ref("");
153
+ const inputRef = ref();
154
+ const allTags = ref([
155
+ // Popular tags
156
+ { label: "\u0E1A\u0E38\u0E23\u0E35\u0E23\u0E31\u0E21\u0E22\u0E4C", value: "buriram", postCount: "46.5 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
157
+ { label: "\u0E01\u0E23\u0E38\u0E07\u0E40\u0E17\u0E1E", value: "bangkok", postCount: "120.3 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
158
+ { label: "\u0E40\u0E0A\u0E35\u0E22\u0E07\u0E43\u0E2B\u0E21\u0E48", value: "chiangmai", postCount: "85.2 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
159
+ { label: "\u0E20\u0E39\u0E40\u0E01\u0E47\u0E15", value: "phuket", postCount: "72.1 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
160
+ { label: "\u0E1E\u0E31\u0E17\u0E22\u0E32", value: "pattaya", postCount: "55.8 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
161
+ { label: "\u0E2B\u0E31\u0E27\u0E2B\u0E34\u0E19", value: "huahin", postCount: "34.6 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
162
+ { label: "\u0E02\u0E2D\u0E19\u0E41\u0E01\u0E48\u0E19", value: "khonkaen", postCount: "28.4 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
163
+ { label: "\u0E19\u0E04\u0E23\u0E23\u0E32\u0E0A\u0E2A\u0E35\u0E21\u0E32", value: "korat", postCount: "22.9 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
164
+ // Latest tags
165
+ { label: "\u0E2A\u0E38\u0E02\u0E38\u0E21\u0E27\u0E34\u0E17", value: "sukhumvit", postCount: "12.3 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
166
+ { label: "\u0E2A\u0E22\u0E32\u0E21", value: "siam", postCount: "8.7 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
167
+ { label: "\u0E2D\u0E32\u0E23\u0E35\u0E22\u0E4C", value: "ari", postCount: "6.2 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
168
+ { label: "\u0E17\u0E2D\u0E07\u0E2B\u0E25\u0E48\u0E2D", value: "thonglor", postCount: "15.4 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
169
+ { label: "\u0E40\u0E2D\u0E01\u0E21\u0E31\u0E22", value: "ekkamai", postCount: "9.8 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
170
+ { label: "\u0E2D\u0E42\u0E28\u0E01", value: "asoke", postCount: "11.2 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
171
+ { label: "\u0E2A\u0E35\u0E25\u0E21", value: "silom", postCount: "18.6 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" },
172
+ { label: "\u0E23\u0E31\u0E0A\u0E14\u0E32", value: "ratchada", postCount: "7.5 \u0E1E\u0E31\u0E19 \u0E42\u0E1E\u0E2A\u0E15\u0E4C" }
173
+ ]);
174
+ const popularTags = computed(() => allTags.value.slice(0, 8));
175
+ const latestTags = computed(() => allTags.value.slice(8));
176
+ const selectedTags = computed(() => {
177
+ return modelValue.value.map((value) => allTags.value.find((tag) => tag.value === value)).filter((tag) => tag !== void 0);
178
+ });
179
+ const isLimitReached = computed(() => {
180
+ if (props.limit <= 0) return false;
181
+ return modelValue.value.length >= props.limit;
182
+ });
183
+ const handleContainerClick = (event) => {
184
+ if (!isLimitReached.value) {
185
+ popoverOpen.value = true;
186
+ setTimeout(() => {
187
+ const input = document.getElementById(
188
+ "input-tag-search"
189
+ );
190
+ input?.focus();
191
+ }, 50);
192
+ }
193
+ };
194
+ const handleInteractOutside = (event) => {
195
+ const target = event.target;
196
+ const container = target.closest('[class*="flex flex-wrap items-center"]');
197
+ if (container) {
198
+ event.preventDefault();
199
+ }
200
+ };
201
+ const filteredPopularTags = computed(() => {
202
+ if (!searchQuery.value) return popularTags.value;
203
+ const query = searchQuery.value.toLowerCase();
204
+ return popularTags.value.filter(
205
+ (tag) => tag.label.toLowerCase().includes(query) || tag.value.toLowerCase().includes(query)
206
+ );
207
+ });
208
+ const filteredLatestTags = computed(() => {
209
+ if (!searchQuery.value) return latestTags.value;
210
+ const query = searchQuery.value.toLowerCase();
211
+ return latestTags.value.filter(
212
+ (tag) => tag.label.toLowerCase().includes(query) || tag.value.toLowerCase().includes(query)
213
+ );
214
+ });
215
+ const isSelected = (value) => {
216
+ return modelValue.value.includes(value);
217
+ };
218
+ const isOptionDisabled = (value) => {
219
+ if (isLimitReached.value && !isSelected(value)) {
220
+ return true;
221
+ }
222
+ return false;
223
+ };
224
+ const onSelectTag = (tag) => {
225
+ if (modelValue.value.includes(tag.value)) {
226
+ modelValue.value = modelValue.value.filter((v) => v !== tag.value);
227
+ } else {
228
+ if (props.limit > 0 && modelValue.value.length >= props.limit) {
229
+ return;
230
+ }
231
+ modelValue.value = [...modelValue.value, tag.value];
232
+ }
233
+ searchQuery.value = "";
234
+ };
235
+ const onRemoveTag = (value) => {
236
+ modelValue.value = modelValue.value.filter((v) => v !== value);
237
+ };
238
+ const onKeydown = (event) => {
239
+ if (event.key === "Enter") {
240
+ event.preventDefault();
241
+ onAddCustomTag();
242
+ }
243
+ };
244
+ const onAddCustomTag = () => {
245
+ const trimmedQuery = searchQuery.value.trim();
246
+ if (!trimmedQuery) return;
247
+ if (props.limit > 0 && modelValue.value.length >= props.limit) {
248
+ return;
249
+ }
250
+ const newValue = trimmedQuery.toLowerCase().replace(/\s+/g, "-");
251
+ const existingTag = allTags.value.find(
252
+ (tag) => tag.value === newValue || tag.label === trimmedQuery
253
+ );
254
+ if (existingTag) {
255
+ if (!modelValue.value.includes(existingTag.value)) {
256
+ modelValue.value = [...modelValue.value, existingTag.value];
257
+ }
258
+ } else {
259
+ const newTag = {
260
+ label: trimmedQuery,
261
+ value: newValue,
262
+ postCount: "\u0E43\u0E2B\u0E21\u0E48"
263
+ };
264
+ allTags.value = [...allTags.value, newTag];
265
+ modelValue.value = [...modelValue.value, newValue];
266
+ }
267
+ searchQuery.value = "";
268
+ };
43
269
  </script>
@@ -1,3 +1,25 @@
1
- declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
1
+ interface InputTagProps {
2
+ name?: string;
3
+ rules?: object | string | Function;
4
+ label?: string;
5
+ required?: boolean;
6
+ showCounter?: boolean;
7
+ limit?: number;
8
+ placeholder?: string;
9
+ }
10
+ type __VLS_Props = InputTagProps;
11
+ type __VLS_ModelProps = {
12
+ modelValue?: string[];
13
+ };
14
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
15
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
16
+ "update:modelValue": (value: string[]) => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
18
+ "onUpdate:modelValue"?: ((value: string[]) => any) | undefined;
19
+ }>, {
20
+ name: string;
21
+ placeholder: string;
22
+ limit: number;
23
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
2
24
  declare const _default: typeof __VLS_export;
3
25
  export default _default;
@@ -23,13 +23,13 @@ type __VLS_ModelProps = {
23
23
  modelValue?: string;
24
24
  };
25
25
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
26
- declare var __VLS_21: {}, __VLS_58: {}, __VLS_73: {};
26
+ declare var __VLS_21: {}, __VLS_59: {}, __VLS_74: {};
27
27
  type __VLS_Slots = {} & {
28
28
  label?: (props: typeof __VLS_21) => any;
29
29
  } & {
30
- prepend?: (props: typeof __VLS_58) => any;
30
+ prepend?: (props: typeof __VLS_59) => any;
31
31
  } & {
32
- append?: (props: typeof __VLS_73) => any;
32
+ append?: (props: typeof __VLS_74) => any;
33
33
  };
34
34
  declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
35
35
  setErrors: (errMsg: string[]) => void;
@@ -39,6 +39,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
39
39
  "update:modelValue": (value: string) => any;
40
40
  } & {
41
41
  click: (event: Event) => any;
42
+ keydown: (event: KeyboardEvent) => any;
42
43
  clickPrepend: (event: Event) => any;
43
44
  clickIconPrepend: (event: Event) => any;
44
45
  clickAppend: (event: Event) => any;
@@ -46,6 +47,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
46
47
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
47
48
  onClick?: ((event: Event) => any) | undefined;
48
49
  "onUpdate:modelValue"?: ((value: string) => any) | undefined;
50
+ onKeydown?: ((event: KeyboardEvent) => any) | undefined;
49
51
  onClickPrepend?: ((event: Event) => any) | undefined;
50
52
  onClickIconPrepend?: ((event: Event) => any) | undefined;
51
53
  onClickAppend?: ((event: Event) => any) | undefined;
@@ -48,6 +48,7 @@
48
48
  :placeholder="props.placeholder"
49
49
  :type="props.type"
50
50
  @click="emits('click', $event)"
51
+ @keydown="emits('keydown', $event)"
51
52
  >
52
53
  </ShadInputGroupInput>
53
54
  </ShadFormControl>
@@ -118,7 +119,7 @@ const props = defineProps({
118
119
  modelValue: { type: [String, Number], required: false },
119
120
  class: { type: null, required: false }
120
121
  });
121
- const emits = defineEmits(["click", "clickPrepend", "clickIconPrepend", "clickAppend", "clickIconAppend"]);
122
+ const emits = defineEmits(["click", "keydown", "clickPrepend", "clickIconPrepend", "clickAppend", "clickIconAppend"]);
122
123
  const modelValue = defineModel({ type: String, ...{
123
124
  default: ""
124
125
  } });
@@ -23,13 +23,13 @@ type __VLS_ModelProps = {
23
23
  modelValue?: string;
24
24
  };
25
25
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
26
- declare var __VLS_21: {}, __VLS_58: {}, __VLS_73: {};
26
+ declare var __VLS_21: {}, __VLS_59: {}, __VLS_74: {};
27
27
  type __VLS_Slots = {} & {
28
28
  label?: (props: typeof __VLS_21) => any;
29
29
  } & {
30
- prepend?: (props: typeof __VLS_58) => any;
30
+ prepend?: (props: typeof __VLS_59) => any;
31
31
  } & {
32
- append?: (props: typeof __VLS_73) => any;
32
+ append?: (props: typeof __VLS_74) => any;
33
33
  };
34
34
  declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
35
35
  setErrors: (errMsg: string[]) => void;
@@ -39,6 +39,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
39
39
  "update:modelValue": (value: string) => any;
40
40
  } & {
41
41
  click: (event: Event) => any;
42
+ keydown: (event: KeyboardEvent) => any;
42
43
  clickPrepend: (event: Event) => any;
43
44
  clickIconPrepend: (event: Event) => any;
44
45
  clickAppend: (event: Event) => any;
@@ -46,6 +47,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
46
47
  }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
47
48
  onClick?: ((event: Event) => any) | undefined;
48
49
  "onUpdate:modelValue"?: ((value: string) => any) | undefined;
50
+ onKeydown?: ((event: KeyboardEvent) => any) | undefined;
49
51
  onClickPrepend?: ((event: Event) => any) | undefined;
50
52
  onClickIconPrepend?: ((event: Event) => any) | undefined;
51
53
  onClickAppend?: ((event: Event) => any) | undefined;
@@ -31,7 +31,13 @@ const isRender = computed(() => {
31
31
  "
32
32
  >
33
33
  <slot />
34
- <Icon name="pukaad:page-not-found" class="h-[64px] w-[74px]" />
35
- <div class="text-gray font-body-medium">ไม่พบข้อมูล</div>
34
+ <Icon
35
+ v-if="!$slots.default"
36
+ name="pukaad:page-not-found"
37
+ class="h-[64px] w-[74px]"
38
+ />
39
+ <div v-if="!$slots.default" class="text-gray font-body-medium">
40
+ ไม่พบข้อมูล
41
+ </div>
36
42
  </Primitive>
37
43
  </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
- "version": "1.72.0",
3
+ "version": "1.74.0",
4
4
  "description": "pukaad-ui for MeMSG",
5
5
  "repository": {
6
6
  "type": "git",