pukaad-ui-lib 1.37.0 → 1.39.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/module.json +1 -1
  2. package/dist/runtime/assets/css/main.css +1 -1
  3. package/dist/runtime/components/drawer/drawer.d.vue.ts +7 -5
  4. package/dist/runtime/components/drawer/drawer.vue +3 -0
  5. package/dist/runtime/components/drawer/drawer.vue.d.ts +7 -5
  6. package/dist/runtime/components/image/image-cropper.d.vue.ts +2 -2
  7. package/dist/runtime/components/image/image-cropper.vue.d.ts +2 -2
  8. package/dist/runtime/components/input/input-address.d.vue.ts +74 -1
  9. package/dist/runtime/components/input/input-address.vue +480 -75
  10. package/dist/runtime/components/input/input-address.vue.d.ts +74 -1
  11. package/dist/runtime/components/input/input-autocomplete.vue +44 -13
  12. package/dist/runtime/components/input/input-combobox.vue +1 -1
  13. package/dist/runtime/components/input/input-text-field.d.vue.ts +1 -1
  14. package/dist/runtime/components/input/input-text-field.vue.d.ts +1 -1
  15. package/dist/runtime/components/input/input-textarea.d.vue.ts +1 -1
  16. package/dist/runtime/components/input/input-textarea.vue.d.ts +1 -1
  17. package/dist/runtime/components/loading.d.vue.ts +10 -0
  18. package/dist/runtime/components/loading.vue +24 -0
  19. package/dist/runtime/components/loading.vue.d.ts +10 -0
  20. package/dist/runtime/components/modal/modal.d.vue.ts +9 -7
  21. package/dist/runtime/components/modal/modal.vue +4 -1
  22. package/dist/runtime/components/modal/modal.vue.d.ts +9 -7
  23. package/dist/runtime/components/ui/input-group/InputGroupButton.d.vue.ts +1 -1
  24. package/dist/runtime/components/ui/input-group/InputGroupButton.vue.d.ts +1 -1
  25. package/dist/runtime/components/ui/input-group/index.d.ts +1 -1
  26. package/package.json +1 -1
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
3
  "configKey": "pukaadUI",
4
- "version": "1.37.0",
4
+ "version": "1.39.0",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "3.6.1"
@@ -1 +1 @@
1
- @import "tailwindcss";@import "tw-animate-css";@import "./system.css";@import "./fonts/Anuphan.css";@import "./fonts/Sarabun.css";@import "./scroll-bar.css";@import "quill/dist/quill.snow.css";@custom-variant dark (&:is(.dark *));@theme inline{--radius-sm:calc(var(--radius) - 4px);--radius-md:calc(var(--radius) - 2px);--radius-lg:var(--radius);--radius-xl:calc(var(--radius) + 4px);--color-background:var(--background);--color-foreground:var(--foreground);--color-card:var(--card);--color-card-foreground:var(--card-foreground);--color-popover:var(--popover);--color-popover-foreground:var(--popover-foreground);--color-primary-foreground:var(--primary-foreground);--color-secondary-foreground:var(--secondary-foreground);--color-muted:var(--muted);--color-muted-foreground:var(--muted-foreground);--color-accent:var(--accent);--color-accent-foreground:var(--accent-foreground);--color-destructive:var(--destructive);--color-destructive-foreground:var(--destructive-foreground);--color-border:var(--border);--color-input:var(--input);--color-ring:var(--ring);--color-chart-1:var(--chart-1);--color-chart-2:var(--chart-2);--color-chart-3:var(--chart-3);--color-chart-4:var(--chart-4);--color-chart-5:var(--chart-5);--color-sidebar:var(--sidebar);--color-sidebar-foreground:var(--sidebar-foreground);--color-sidebar-primary:var(--sidebar-primary);--color-sidebar-primary-foreground:var(--sidebar-primary-foreground);--color-sidebar-accent:var(--sidebar-accent);--color-sidebar-accent-foreground:var(--sidebar-accent-foreground);--color-sidebar-border:var(--sidebar-border);--color-sidebar-ring:var(--sidebar-ring);--color-red:var(--red);--color-error:var(--error);--color-success:var(--success);--color-green:var(--green);--color-warning:var(--warning);--color-yellow:var(--yellow);--color-primary:var(--primary);--color-secondary:var(--secondary);--color-info:var(--info);--color-black:var(--black);--color-dark:var(--dark);--color-gray:var(--gray);--color-cloud:var(--cloud);--color-mercury:var(--mercury);--color-green-light:var(--green-light);--color-red-light:var(--red-light);--color-yellow-light:var(--yellow-light);--color-bright:var(--bright);--color-silver:var(--silver);--color-smoke:var(--smoke);--z-index-announce-tool:10;--z-index-dropdown:11;--z-index-overlay:12;--z-index-loading:13;--z-index-drawer:14;--z-index-modal:15;--z-index-toast:99998;--z-index-loading-page:99999}:root{--radius:0.625rem;--red:#b71c1c;--success:#43a047;--green:#1b5e20;--warning:#f9a825;--yellow:#fbc02d;--info:#e3f2fd;--black:#212121;--dark:#424242;--gray:#616161;--cloud:#c4c4c4;--mercury:#e0e0e0;--green-light:#e3f5e3;--red-light:#fde3e3;--yellow-light:#fef3c7;--bright:#f5f5f5;--silver:#f7f7f7;--smoke:#fafafa;--white:#fff;--background:var(--white);--foreground:var(--black);--card:var(--background);--card-foreground:var(--foreground);--popover:var(--background);--popover-foreground:var(--foreground);--primary:#1976b8;--primary-foreground:var(--white);--secondary:#22a7ef;--secondary-foreground:var(--white);--muted:oklch(0.97 0 0);--muted-foreground:var(--gray);--accent:oklch(0.97 0 0);--accent-foreground:oklch(0.205 0 0);--destructive:#d32f2f;--destructive-foreground:var(--white);--border:var(--color-mercury);--input:var(--color-mercury);--ring:var(--mercury);--chart-1:oklch(0.646 0.222 41.116);--chart-2:oklch(0.6 0.118 184.704);--chart-3:oklch(0.398 0.07 227.392);--chart-4:oklch(0.828 0.189 84.429);--chart-5:oklch(0.769 0.188 70.08);--sidebar:oklch(0.985 0 0);--sidebar-foreground:var(--foreground);--sidebar-primary:oklch(0.205 0 0);--sidebar-primary-foreground:oklch(0.985 0 0);--sidebar-accent:oklch(0.97 0 0);--sidebar-accent-foreground:oklch(0.205 0 0);--sidebar-border:var(--color-mercury);--sidebar-ring:var(--color-mercury)}.dark{--background:oklch(0.145 0 0);--foreground:oklch(0.985 0 0);--card:oklch(0.145 0 0);--card-foreground:oklch(0.985 0 0);--popover:oklch(0.145 0 0);--popover-foreground:oklch(0.985 0 0);--primary:oklch(0.985 0 0);--primary-foreground:oklch(0.205 0 0);--secondary:oklch(0.269 0 0);--secondary-foreground:oklch(0.985 0 0);--muted:oklch(0.269 0 0);--muted-foreground:oklch(0.708 0 0);--accent:oklch(0.269 0 0);--accent-foreground:oklch(0.985 0 0);--destructive:oklch(0.396 0.141 25.723);--destructive-foreground:oklch(0.637 0.237 25.331);--border:oklch(0.269 0 0);--input:oklch(0.269 0 0);--ring:oklch(0.439 0 0);--chart-1:oklch(0.488 0.243 264.376);--chart-2:oklch(0.696 0.17 162.48);--chart-3:oklch(0.769 0.188 70.08);--chart-4:oklch(0.627 0.265 303.9);--chart-5:oklch(0.645 0.246 16.439);--sidebar:oklch(0.205 0 0);--sidebar-foreground:oklch(0.985 0 0);--sidebar-primary:oklch(0.488 0.243 264.376);--sidebar-primary-foreground:oklch(0.985 0 0);--sidebar-accent:oklch(0.269 0 0);--sidebar-accent-foreground:oklch(0.985 0 0);--sidebar-border:oklch(0.269 0 0);--sidebar-ring:oklch(0.439 0 0)}@layer base{*{@apply border-border outline-ring/50}body{@apply bg-background text-foreground}}
1
+ @import "tailwindcss";@import "tw-animate-css";@import "./system.css";@import "./fonts/Anuphan.css";@import "./fonts/Sarabun.css";@import "./scroll-bar.css";@import "quill/dist/quill.snow.css";@custom-variant dark (&:is(.dark *));@theme inline{--radius-sm:calc(var(--radius) - 4px);--radius-md:calc(var(--radius) - 2px);--radius-lg:var(--radius);--radius-xl:calc(var(--radius) + 4px);--color-background:var(--background);--color-foreground:var(--foreground);--color-card:var(--card);--color-card-foreground:var(--card-foreground);--color-popover:var(--popover);--color-popover-foreground:var(--popover-foreground);--color-primary-foreground:var(--primary-foreground);--color-secondary-foreground:var(--secondary-foreground);--color-muted:var(--muted);--color-muted-foreground:var(--muted-foreground);--color-accent:var(--accent);--color-accent-foreground:var(--accent-foreground);--color-destructive:var(--destructive);--color-destructive-foreground:var(--destructive-foreground);--color-border:var(--border);--color-input:var(--input);--color-ring:var(--ring);--color-chart-1:var(--chart-1);--color-chart-2:var(--chart-2);--color-chart-3:var(--chart-3);--color-chart-4:var(--chart-4);--color-chart-5:var(--chart-5);--color-sidebar:var(--sidebar);--color-sidebar-foreground:var(--sidebar-foreground);--color-sidebar-primary:var(--sidebar-primary);--color-sidebar-primary-foreground:var(--sidebar-primary-foreground);--color-sidebar-accent:var(--sidebar-accent);--color-sidebar-accent-foreground:var(--sidebar-accent-foreground);--color-sidebar-border:var(--sidebar-border);--color-sidebar-ring:var(--sidebar-ring);--color-red:var(--red);--color-error:var(--error);--color-success:var(--success);--color-green:var(--green);--color-warning:var(--warning);--color-yellow:var(--yellow);--color-primary:var(--primary);--color-secondary:var(--secondary);--color-info:var(--info);--color-black:var(--black);--color-dark:var(--dark);--color-gray:var(--gray);--color-cloud:var(--cloud);--color-mercury:var(--mercury);--color-green-light:var(--green-light);--color-red-light:var(--red-light);--color-yellow-light:var(--yellow-light);--color-bright:var(--bright);--color-silver:var(--silver);--color-smoke:var(--smoke);--z-index-announce-tool:10;--z-index-dropdown:11;--z-index-overlay:12;--z-index-loading:13;--z-index-drawer:14;--z-index-modal:15;--z-index-toast:99998;--z-index-loading-page:99999}:root{--radius:0.625rem;--red:#b71c1c;--success:#43a047;--green:#1b5e20;--warning:#f9a825;--yellow:#fbc02d;--info:#e3f2fd;--black:#212121;--dark:#424242;--gray:#616161;--cloud:#c4c4c4;--mercury:#e0e0e0;--green-light:#e3f5e3;--red-light:#fde3e3;--yellow-light:#fef3c7;--bright:#f5f5f5;--silver:#f7f7f7;--smoke:#fafafa;--white:#fff;--background:var(--white);--foreground:var(--black);--card:var(--background);--card-foreground:var(--foreground);--popover:var(--background);--popover-foreground:var(--foreground);--primary:#1976b8;--primary-foreground:var(--white);--secondary:#22a7ef;--secondary-foreground:var(--white);--muted:oklch(0.97 0 0);--muted-foreground:var(--cloud);--accent:oklch(0.97 0 0);--accent-foreground:oklch(0.205 0 0);--destructive:#d32f2f;--destructive-foreground:var(--white);--border:var(--color-mercury);--input:var(--color-mercury);--ring:var(--mercury);--chart-1:oklch(0.646 0.222 41.116);--chart-2:oklch(0.6 0.118 184.704);--chart-3:oklch(0.398 0.07 227.392);--chart-4:oklch(0.828 0.189 84.429);--chart-5:oklch(0.769 0.188 70.08);--sidebar:oklch(0.985 0 0);--sidebar-foreground:var(--foreground);--sidebar-primary:oklch(0.205 0 0);--sidebar-primary-foreground:oklch(0.985 0 0);--sidebar-accent:oklch(0.97 0 0);--sidebar-accent-foreground:oklch(0.205 0 0);--sidebar-border:var(--color-mercury);--sidebar-ring:var(--color-mercury)}.dark{--background:oklch(0.145 0 0);--foreground:oklch(0.985 0 0);--card:oklch(0.145 0 0);--card-foreground:oklch(0.985 0 0);--popover:oklch(0.145 0 0);--popover-foreground:oklch(0.985 0 0);--primary:oklch(0.985 0 0);--primary-foreground:oklch(0.205 0 0);--secondary:oklch(0.269 0 0);--secondary-foreground:oklch(0.985 0 0);--muted:oklch(0.269 0 0);--muted-foreground:oklch(0.708 0 0);--accent:oklch(0.269 0 0);--accent-foreground:oklch(0.985 0 0);--destructive:oklch(0.396 0.141 25.723);--destructive-foreground:oklch(0.637 0.237 25.331);--border:oklch(0.269 0 0);--input:oklch(0.269 0 0);--ring:oklch(0.439 0 0);--chart-1:oklch(0.488 0.243 264.376);--chart-2:oklch(0.696 0.17 162.48);--chart-3:oklch(0.769 0.188 70.08);--chart-4:oklch(0.627 0.265 303.9);--chart-5:oklch(0.645 0.246 16.439);--sidebar:oklch(0.205 0 0);--sidebar-foreground:oklch(0.985 0 0);--sidebar-primary:oklch(0.488 0.243 264.376);--sidebar-primary-foreground:oklch(0.985 0 0);--sidebar-accent:oklch(0.269 0 0);--sidebar-accent-foreground:oklch(0.985 0 0);--sidebar-border:oklch(0.269 0 0);--sidebar-ring:oklch(0.439 0 0)}@layer base{*{@apply border-border outline-ring/50}body{@apply bg-background text-foreground}}
@@ -2,23 +2,25 @@ import type { SheetContentProps } from "@/runtime/components/ui/sheet/SheetConte
2
2
  export interface DrawerProps extends SheetContentProps {
3
3
  title?: string;
4
4
  description?: string;
5
+ isLoading?: boolean;
6
+ loadingText?: string;
5
7
  }
6
8
  type __VLS_Props = DrawerProps;
7
9
  type __VLS_ModelProps = {
8
10
  modelValue?: boolean;
9
11
  };
10
12
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
11
- declare var __VLS_22: {}, __VLS_36: {
13
+ declare var __VLS_27: {}, __VLS_41: {
12
14
  meta: any;
13
- }, __VLS_44: {
15
+ }, __VLS_49: {
14
16
  meta: any;
15
17
  };
16
18
  type __VLS_Slots = {} & {
17
- header?: (props: typeof __VLS_22) => any;
19
+ header?: (props: typeof __VLS_27) => any;
18
20
  } & {
19
- default?: (props: typeof __VLS_36) => any;
21
+ default?: (props: typeof __VLS_41) => any;
20
22
  } & {
21
- footer?: (props: typeof __VLS_44) => any;
23
+ footer?: (props: typeof __VLS_49) => any;
22
24
  };
23
25
  declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
24
26
  "update:modelValue": (value: boolean) => any;
@@ -1,6 +1,7 @@
1
1
  <template>
2
2
  <ShadSheet v-model:open="isOpen">
3
3
  <ShadSheetContent v-bind="props" v-slot="{ meta }" @submit="onSubmit">
4
+ <Loading :is-loading="props.isLoading" :text="props.loadingText" />
4
5
  <ShadSheetHeader class="flex-shrink-0">
5
6
  <slot name="header">
6
7
  <ShadSheetTitle>{{ props.title }}</ShadSheetTitle>
@@ -23,6 +24,8 @@
23
24
  const props = defineProps({
24
25
  title: { type: String, required: false },
25
26
  description: { type: String, required: false },
27
+ isLoading: { type: Boolean, required: false },
28
+ loadingText: { type: String, required: false },
26
29
  class: { type: null, required: false },
27
30
  side: { type: String, required: false },
28
31
  forceMount: { type: Boolean, required: false },
@@ -2,23 +2,25 @@ import type { SheetContentProps } from "@/runtime/components/ui/sheet/SheetConte
2
2
  export interface DrawerProps extends SheetContentProps {
3
3
  title?: string;
4
4
  description?: string;
5
+ isLoading?: boolean;
6
+ loadingText?: string;
5
7
  }
6
8
  type __VLS_Props = DrawerProps;
7
9
  type __VLS_ModelProps = {
8
10
  modelValue?: boolean;
9
11
  };
10
12
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
11
- declare var __VLS_22: {}, __VLS_36: {
13
+ declare var __VLS_27: {}, __VLS_41: {
12
14
  meta: any;
13
- }, __VLS_44: {
15
+ }, __VLS_49: {
14
16
  meta: any;
15
17
  };
16
18
  type __VLS_Slots = {} & {
17
- header?: (props: typeof __VLS_22) => any;
19
+ header?: (props: typeof __VLS_27) => any;
18
20
  } & {
19
- default?: (props: typeof __VLS_36) => any;
21
+ default?: (props: typeof __VLS_41) => any;
20
22
  } & {
21
- footer?: (props: typeof __VLS_44) => any;
23
+ footer?: (props: typeof __VLS_49) => any;
22
24
  };
23
25
  declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
24
26
  "update:modelValue": (value: boolean) => any;
@@ -64,15 +64,15 @@ declare const __VLS_export: import("vue").DefineComponent<ImageCropperProps, {
64
64
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ImageCropperProps> & Readonly<{}>, {
65
65
  center: boolean;
66
66
  src: string;
67
- background: boolean;
68
- modal: boolean;
69
67
  responsive: boolean;
70
68
  restore: boolean;
71
69
  checkCrossOrigin: boolean;
72
70
  checkOrientation: boolean;
73
71
  crossorigin: "" | "anonymous" | "use-credentials";
72
+ modal: boolean;
74
73
  guides: boolean;
75
74
  highlight: boolean;
75
+ background: boolean;
76
76
  autoCrop: boolean;
77
77
  movable: boolean;
78
78
  rotatable: boolean;
@@ -64,15 +64,15 @@ declare const __VLS_export: import("vue").DefineComponent<ImageCropperProps, {
64
64
  }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ImageCropperProps> & Readonly<{}>, {
65
65
  center: boolean;
66
66
  src: string;
67
- background: boolean;
68
- modal: boolean;
69
67
  responsive: boolean;
70
68
  restore: boolean;
71
69
  checkCrossOrigin: boolean;
72
70
  checkOrientation: boolean;
73
71
  crossorigin: "" | "anonymous" | "use-credentials";
72
+ modal: boolean;
74
73
  guides: boolean;
75
74
  highlight: boolean;
75
+ background: boolean;
76
76
  autoCrop: boolean;
77
77
  movable: boolean;
78
78
  rotatable: boolean;
@@ -1,3 +1,76 @@
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 Province {
2
+ code: number;
3
+ name_th: string;
4
+ name_en: string;
5
+ }
6
+ interface District {
7
+ code: number;
8
+ name_th: string;
9
+ name_en: string;
10
+ province_code: number;
11
+ }
12
+ interface SubDistrict {
13
+ code: number;
14
+ name_th: string;
15
+ name_en: string;
16
+ district_code: number;
17
+ zip_code: number;
18
+ }
19
+ export interface InputAddressValue {
20
+ province?: Province;
21
+ district?: District;
22
+ subDistrict?: SubDistrict;
23
+ zipCode?: number;
24
+ detail?: string;
25
+ fullAddress?: string;
26
+ }
27
+ export interface InputAddressProps {
28
+ name?: string;
29
+ label?: string;
30
+ placeholder?: string;
31
+ labelDetail?: string;
32
+ placeholderDetail?: string;
33
+ required?: boolean;
34
+ requiredDetail?: boolean;
35
+ rules?: object | string | Function;
36
+ provinces?: Province[];
37
+ districts?: District[];
38
+ subDistricts?: SubDistrict[];
39
+ }
40
+ type __VLS_Props = InputAddressProps;
41
+ type __VLS_ModelProps = {
42
+ modelValue?: InputAddressValue;
43
+ };
44
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
45
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
46
+ validate: () => Promise<boolean>;
47
+ setErrors: (errMsg: string[]) => void;
48
+ reset: () => void;
49
+ addressFieldRef: import("vue").Ref<any, any>;
50
+ detailFieldRef: import("vue").Ref<any, any>;
51
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
52
+ "update:modelValue": (value: InputAddressValue) => any;
53
+ } & {
54
+ change: (value: InputAddressValue) => any;
55
+ selectProvince: (province: Province) => any;
56
+ selectDistrict: (district: District) => any;
57
+ selectSubDistrict: (subDistrict: SubDistrict) => any;
58
+ selectZipCode: (zipCode: number) => any;
59
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
60
+ onChange?: ((value: InputAddressValue) => any) | undefined;
61
+ "onUpdate:modelValue"?: ((value: InputAddressValue) => any) | undefined;
62
+ onSelectProvince?: ((province: Province) => any) | undefined;
63
+ onSelectDistrict?: ((district: District) => any) | undefined;
64
+ onSelectSubDistrict?: ((subDistrict: SubDistrict) => any) | undefined;
65
+ onSelectZipCode?: ((zipCode: number) => any) | undefined;
66
+ }>, {
67
+ label: string;
68
+ required: boolean;
69
+ name: string;
70
+ placeholder: string;
71
+ labelDetail: string;
72
+ placeholderDetail: string;
73
+ requiredDetail: boolean;
74
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
2
75
  declare const _default: typeof __VLS_export;
3
76
  export default _default;
@@ -1,87 +1,492 @@
1
1
  <template>
2
- <!-- <div class="space-y-[16px]">
3
- <Dropdown full-width v-model="isOpen">
4
- <InputTextField
5
- name="address"
6
- label="จังหวัด, อำเภอ/เขต, ตำบล/แขวง, รหัสไปรษณีย์"
7
- placeholder="จังหวัด, อำเภอ/เขต, ตำบล/แขวง, รหัสไปรษณีย์"
8
- input-class="cursor-pointer"
9
- readonly
10
- :full-width="props.fullWidth"
11
- append-icon="solar:alt-arrow-down-line-duotone"
12
- v-model="addressValue"
13
- />
14
- <template #content>
15
- <Tab
16
- :items="typeAddress"
17
- type="text"
18
- tab-content-gab="0"
19
- v-model="tabValue"
20
- >
21
- <template #tab-content-provinces>
22
- <ul class="h-96 overflow-y-auto">
23
- <li
24
- v-for="(pro, i_pro) in getProvince()"
25
- :key="i_pro"
26
- class="p-2 border-b-[1px] border-mercury cursor-pointer"
27
- @click="onSelectProvince(pro)"
2
+ <div class="space-y-4">
3
+ <!-- Address Selector (Province, District, Sub-district, Zip code) -->
4
+ <ShadFormField
5
+ ref="addressFieldRef"
6
+ :name="props.name"
7
+ :rules="props.rules || defaultRules"
8
+ v-slot="{ componentField }"
9
+ v-model="addressValue"
10
+ >
11
+ <ShadFormItem>
12
+ <ShadFormLabel v-if="props.label" class="w-full">
13
+ <div class="flex-1">
14
+ {{ props.label }}
15
+ <span v-if="props.required" class="text-destructive">*</span>
16
+ </div>
17
+ </ShadFormLabel>
18
+ <ShadPopover v-model:open="popoverOpen" v-bind="componentField">
19
+ <ShadPopoverTrigger as-child>
20
+ <ShadFormControl>
21
+ <ShadButton
22
+ variant="outline"
23
+ role="combobox"
24
+ class="w-full justify-between text-start"
28
25
  >
29
- {{ pro.name_th }}
30
- </li>
31
- </ul>
32
- </template>
26
+ <span :class="{ 'text-cloud': !addressValue }">
27
+ {{ addressValue || props.placeholder }}
28
+ </span>
29
+ <Icon name="lucide:chevron-down" class="h-4 w-4" />
30
+ </ShadButton>
31
+ </ShadFormControl>
32
+ </ShadPopoverTrigger>
33
+ <ShadPopoverContent class="w-[400px] p-0">
34
+ <ShadCommand>
35
+ <ShadCommandInput
36
+ v-model="searchQuery"
37
+ placeholder="ค้นหาที่อยู่"
38
+ />
39
+ <ShadTabs v-model="currentTab" class="w-full">
40
+ <ShadTabsList class="w-full grid grid-cols-4 rounded-none">
41
+ <ShadTabsTrigger value="province" class="rounded-none">
42
+ จังหวัด
43
+ </ShadTabsTrigger>
44
+ <ShadTabsTrigger
45
+ value="district"
46
+ :disabled="!selectedProvince"
47
+ class="rounded-none"
48
+ >
49
+ อำเภอ/เขต
50
+ </ShadTabsTrigger>
51
+ <ShadTabsTrigger
52
+ value="subDistrict"
53
+ :disabled="!selectedDistrict"
54
+ class="rounded-none"
55
+ >
56
+ ตำบล/แขวง
57
+ </ShadTabsTrigger>
58
+ <ShadTabsTrigger
59
+ value="zipCode"
60
+ :disabled="!selectedSubDistrict"
61
+ class="rounded-none"
62
+ >
63
+ รหัสไปรษณีย์
64
+ </ShadTabsTrigger>
65
+ </ShadTabsList>
33
66
 
34
- <template #tab-content-districts>
35
- <ul class="h-96 overflow-y-auto">
36
- <li
37
- v-for="(dist, i_dist) in getDistrict({ pro_code: provineCode })"
38
- :key="i_dist"
39
- class="p-2 border-b-[1px] border-mercury cursor-pointer"
40
- @click="onSelectDistrict(dist)"
41
- >
42
- {{ dist.name_th }}
43
- </li>
44
- </ul>
45
- </template>
67
+ <!-- Province Tab -->
68
+ <ShadTabsContent value="province" class="m-0">
69
+ <ShadCommandList class="max-h-64">
70
+ <ShadCommandGroup>
71
+ <ShadCommandItem
72
+ v-for="province in filteredProvinces"
73
+ :key="province.code"
74
+ :value="province.name_th"
75
+ @select="onSelectProvince(province)"
76
+ >
77
+ {{ province.name_th }}
78
+ <Icon
79
+ v-if="selectedProvince?.code === province.code"
80
+ name="lucide:check"
81
+ class="ml-auto h-4 w-4"
82
+ />
83
+ </ShadCommandItem>
84
+ </ShadCommandGroup>
85
+ <ShadCommandEmpty>ไม่พบจังหวัด</ShadCommandEmpty>
86
+ </ShadCommandList>
87
+ </ShadTabsContent>
46
88
 
47
- <template #tab-content-sub-districts>
48
- <ul class="h-96 overflow-y-auto">
49
- <li
50
- v-for="(sub, i_sub) in getSubDistrict({
51
- dist_code: districtCode,
52
- })"
53
- :key="i_sub"
54
- class="p-2 border-b-[1px] border-mercury cursor-pointer"
55
- @click="onSelectSubDistrict(sub)"
56
- >
57
- {{ sub.name_th }}
58
- </li>
59
- </ul>
60
- </template>
89
+ <!-- District Tab -->
90
+ <ShadTabsContent value="district" class="m-0">
91
+ <ShadCommandList class="max-h-64">
92
+ <ShadCommandGroup>
93
+ <ShadCommandItem
94
+ v-for="district in filteredDistricts"
95
+ :key="district.code"
96
+ :value="district.name_th"
97
+ @select="onSelectDistrict(district)"
98
+ >
99
+ {{ district.name_th }}
100
+ <Icon
101
+ v-if="selectedDistrict?.code === district.code"
102
+ name="lucide:check"
103
+ class="ml-auto h-4 w-4"
104
+ />
105
+ </ShadCommandItem>
106
+ </ShadCommandGroup>
107
+ <ShadCommandEmpty>ไม่พบอำเภอ/เขต</ShadCommandEmpty>
108
+ </ShadCommandList>
109
+ </ShadTabsContent>
61
110
 
62
- <template #tab-content-zip-code>
63
- <ul class="h-96 overflow-y-auto">
64
- <li
65
- class="p-2 border-b-[1px] border-mercury cursor-pointer"
66
- @click="onSelectZipCode"
67
- >
68
- {{ zipCode }}
69
- </li>
70
- </ul>
71
- </template>
72
- </Tab>
73
- </template>
74
- </Dropdown>
111
+ <!-- Sub-district Tab -->
112
+ <ShadTabsContent value="subDistrict" class="m-0">
113
+ <ShadCommandList class="max-h-64">
114
+ <ShadCommandGroup>
115
+ <ShadCommandItem
116
+ v-for="subDistrict in filteredSubDistricts"
117
+ :key="subDistrict.code"
118
+ :value="subDistrict.name_th"
119
+ @select="onSelectSubDistrict(subDistrict)"
120
+ >
121
+ {{ subDistrict.name_th }}
122
+ <Icon
123
+ v-if="selectedSubDistrict?.code === subDistrict.code"
124
+ name="lucide:check"
125
+ class="ml-auto h-4 w-4"
126
+ />
127
+ </ShadCommandItem>
128
+ </ShadCommandGroup>
129
+ <ShadCommandEmpty>ไม่พบตำบล/แขวง</ShadCommandEmpty>
130
+ </ShadCommandList>
131
+ </ShadTabsContent>
132
+
133
+ <!-- Zip Code Tab -->
134
+ <ShadTabsContent value="zipCode" class="m-0">
135
+ <ShadCommandList class="max-h-64">
136
+ <ShadCommandGroup>
137
+ <ShadCommandItem
138
+ v-if="selectedSubDistrict"
139
+ :value="selectedSubDistrict.zip_code.toString()"
140
+ @select="onSelectZipCode"
141
+ >
142
+ {{ selectedSubDistrict.zip_code }}
143
+ <Icon name="lucide:check" class="ml-auto h-4 w-4" />
144
+ </ShadCommandItem>
145
+ </ShadCommandGroup>
146
+ <ShadCommandEmpty>ไม่พบรหัสไปรษณีย์</ShadCommandEmpty>
147
+ </ShadCommandList>
148
+ </ShadTabsContent>
149
+ </ShadTabs>
150
+ </ShadCommand>
151
+ </ShadPopoverContent>
152
+ </ShadPopover>
153
+ <ShadFormMessage />
154
+ </ShadFormItem>
155
+ </ShadFormField>
156
+
157
+ <!-- Address Detail (House number, Soi, Moo, Road) -->
75
158
  <InputTextField
76
- label="บ้านเลขที่, ซอย, หมู่, ถนน"
77
- placeholder="บ้านเลขที่, ซอย, หมู่, ถนน"
78
- :full-width="props.fullWidth"
79
- :disabled="isDisabledAddressDetail"
159
+ ref="detailFieldRef"
160
+ :name="`${props.name}-detail`"
161
+ :label="props.labelDetail"
162
+ :placeholder="props.placeholderDetail"
163
+ :disabled="!addressValue"
164
+ :required="props.requiredDetail"
80
165
  v-model="addressDetailValue"
81
166
  />
82
- </div> -->
167
+ </div>
83
168
  </template>
84
169
 
85
170
  <script setup>
86
-
171
+ import { ref, computed, watch } from "vue";
172
+ import InputTextField from "./input-text-field.vue";
173
+ const props = defineProps({
174
+ name: { type: String, required: false, default: "address" },
175
+ label: { type: String, required: false, default: "\u0E08\u0E31\u0E07\u0E2B\u0E27\u0E31\u0E14, \u0E2D\u0E33\u0E40\u0E20\u0E2D/\u0E40\u0E02\u0E15, \u0E15\u0E33\u0E1A\u0E25/\u0E41\u0E02\u0E27\u0E07, \u0E23\u0E2B\u0E31\u0E2A\u0E44\u0E1B\u0E23\u0E29\u0E13\u0E35\u0E22\u0E4C" },
176
+ placeholder: { type: String, required: false, default: "\u0E08\u0E31\u0E07\u0E2B\u0E27\u0E31\u0E14, \u0E2D\u0E33\u0E40\u0E20\u0E2D/\u0E40\u0E02\u0E15, \u0E15\u0E33\u0E1A\u0E25/\u0E41\u0E02\u0E27\u0E07, \u0E23\u0E2B\u0E31\u0E2A\u0E44\u0E1B\u0E23\u0E29\u0E13\u0E35\u0E22\u0E4C" },
177
+ labelDetail: { type: String, required: false, default: "\u0E1A\u0E49\u0E32\u0E19\u0E40\u0E25\u0E02\u0E17\u0E35\u0E48, \u0E0B\u0E2D\u0E22, \u0E2B\u0E21\u0E39\u0E48, \u0E16\u0E19\u0E19" },
178
+ placeholderDetail: { type: String, required: false, default: "\u0E1A\u0E49\u0E32\u0E19\u0E40\u0E25\u0E02\u0E17\u0E35\u0E48, \u0E0B\u0E2D\u0E22, \u0E2B\u0E21\u0E39\u0E48, \u0E16\u0E19\u0E19" },
179
+ required: { type: Boolean, required: false, default: false },
180
+ requiredDetail: { type: Boolean, required: false, default: false },
181
+ rules: { type: [Object, String, Function], required: false },
182
+ provinces: { type: Array, required: false },
183
+ districts: { type: Array, required: false },
184
+ subDistricts: { type: Array, required: false }
185
+ });
186
+ const emits = defineEmits(["selectProvince", "selectDistrict", "selectSubDistrict", "selectZipCode", "change"]);
187
+ const modelValue = defineModel({ type: Object, ...{
188
+ default: () => ({})
189
+ } });
190
+ const popoverOpen = ref(false);
191
+ const currentTab = ref("province");
192
+ const searchQuery = ref("");
193
+ const addressFieldRef = ref();
194
+ const detailFieldRef = ref();
195
+ const selectedProvince = ref(null);
196
+ const selectedDistrict = ref(null);
197
+ const selectedSubDistrict = ref(null);
198
+ const addressValue = computed(() => {
199
+ const parts = [];
200
+ if (selectedProvince.value) parts.push(selectedProvince.value.name_th);
201
+ if (selectedDistrict.value) parts.push(selectedDistrict.value.name_th);
202
+ if (selectedSubDistrict.value) parts.push(selectedSubDistrict.value.name_th);
203
+ if (selectedSubDistrict.value)
204
+ parts.push(selectedSubDistrict.value.zip_code.toString());
205
+ return parts.join(", ");
206
+ });
207
+ const addressDetailValue = ref("");
208
+ const mockProvinces = props.provinces || [
209
+ { code: 10, name_th: "\u0E01\u0E23\u0E38\u0E07\u0E40\u0E17\u0E1E\u0E21\u0E2B\u0E32\u0E19\u0E04\u0E23", name_en: "Bangkok" },
210
+ { code: 11, name_th: "\u0E2A\u0E21\u0E38\u0E17\u0E23\u0E1B\u0E23\u0E32\u0E01\u0E32\u0E23", name_en: "Samut Prakan" },
211
+ { code: 12, name_th: "\u0E19\u0E19\u0E17\u0E1A\u0E38\u0E23\u0E35", name_en: "Nonthaburi" },
212
+ { code: 13, name_th: "\u0E1B\u0E17\u0E38\u0E21\u0E18\u0E32\u0E19\u0E35", name_en: "Pathum Thani" },
213
+ { code: 14, name_th: "\u0E1E\u0E23\u0E30\u0E19\u0E04\u0E23\u0E28\u0E23\u0E35\u0E2D\u0E22\u0E38\u0E18\u0E22\u0E32", name_en: "Phra Nakhon Si Ayutthaya" },
214
+ { code: 20, name_th: "\u0E0A\u0E25\u0E1A\u0E38\u0E23\u0E35", name_en: "Chon Buri" },
215
+ { code: 50, name_th: "\u0E40\u0E0A\u0E35\u0E22\u0E07\u0E43\u0E2B\u0E21\u0E48", name_en: "Chiang Mai" }
216
+ ];
217
+ const mockDistricts = props.districts || [
218
+ // กรุงเทพมหานคร
219
+ {
220
+ code: 1001,
221
+ name_th: "\u0E40\u0E02\u0E15\u0E1E\u0E23\u0E30\u0E19\u0E04\u0E23",
222
+ name_en: "Phra Nakhon",
223
+ province_code: 10
224
+ },
225
+ { code: 1002, name_th: "\u0E40\u0E02\u0E15\u0E14\u0E38\u0E2A\u0E34\u0E15", name_en: "Dusit", province_code: 10 },
226
+ {
227
+ code: 1003,
228
+ name_th: "\u0E40\u0E02\u0E15\u0E2B\u0E19\u0E2D\u0E07\u0E08\u0E2D\u0E01",
229
+ name_en: "Nong Chok",
230
+ province_code: 10
231
+ },
232
+ { code: 1004, name_th: "\u0E40\u0E02\u0E15\u0E1A\u0E32\u0E07\u0E23\u0E31\u0E01", name_en: "Bang Rak", province_code: 10 },
233
+ { code: 1005, name_th: "\u0E40\u0E02\u0E15\u0E1A\u0E32\u0E07\u0E40\u0E02\u0E19", name_en: "Bang Khen", province_code: 10 },
234
+ {
235
+ code: 1006,
236
+ name_th: "\u0E40\u0E02\u0E15\u0E1A\u0E32\u0E07\u0E01\u0E30\u0E1B\u0E34",
237
+ name_en: "Bang Kapi",
238
+ province_code: 10
239
+ },
240
+ {
241
+ code: 1007,
242
+ name_th: "\u0E40\u0E02\u0E15\u0E1B\u0E17\u0E38\u0E21\u0E27\u0E31\u0E19",
243
+ name_en: "Pathum Wan",
244
+ province_code: 10
245
+ },
246
+ // สมุทรปราการ
247
+ {
248
+ code: 1101,
249
+ name_th: "\u0E2D\u0E33\u0E40\u0E20\u0E2D\u0E40\u0E21\u0E37\u0E2D\u0E07\u0E2A\u0E21\u0E38\u0E17\u0E23\u0E1B\u0E23\u0E32\u0E01\u0E32\u0E23",
250
+ name_en: "Mueang Samut Prakan",
251
+ province_code: 11
252
+ },
253
+ { code: 1102, name_th: "\u0E2D\u0E33\u0E40\u0E20\u0E2D\u0E1A\u0E32\u0E07\u0E1A\u0E48\u0E2D", name_en: "Bang Bo", province_code: 11 },
254
+ // นนทบุรี
255
+ {
256
+ code: 1201,
257
+ name_th: "\u0E2D\u0E33\u0E40\u0E20\u0E2D\u0E40\u0E21\u0E37\u0E2D\u0E07\u0E19\u0E19\u0E17\u0E1A\u0E38\u0E23\u0E35",
258
+ name_en: "Mueang Nonthaburi",
259
+ province_code: 12
260
+ },
261
+ {
262
+ code: 1202,
263
+ name_th: "\u0E2D\u0E33\u0E40\u0E20\u0E2D\u0E1A\u0E32\u0E07\u0E01\u0E23\u0E27\u0E22",
264
+ name_en: "Bang Kruai",
265
+ province_code: 12
266
+ }
267
+ ];
268
+ const mockSubDistricts = props.subDistricts || [
269
+ // เขตพระนคร
270
+ {
271
+ code: 100101,
272
+ name_th: "\u0E41\u0E02\u0E27\u0E07\u0E1E\u0E23\u0E30\u0E1A\u0E23\u0E21\u0E21\u0E2B\u0E32\u0E23\u0E32\u0E0A\u0E27\u0E31\u0E07",
273
+ name_en: "Phra Borom Maha Ratchawang",
274
+ district_code: 1001,
275
+ zip_code: 10200
276
+ },
277
+ {
278
+ code: 100102,
279
+ name_th: "\u0E41\u0E02\u0E27\u0E07\u0E27\u0E31\u0E07\u0E1A\u0E39\u0E23\u0E1E\u0E32\u0E20\u0E34\u0E23\u0E21\u0E22\u0E4C",
280
+ name_en: "Wang Burapha Phirom",
281
+ district_code: 1001,
282
+ zip_code: 10200
283
+ },
284
+ {
285
+ code: 100103,
286
+ name_th: "\u0E41\u0E02\u0E27\u0E07\u0E27\u0E31\u0E14\u0E23\u0E32\u0E0A\u0E1A\u0E1E\u0E34\u0E18",
287
+ name_en: "Wat Ratchabophit",
288
+ district_code: 1001,
289
+ zip_code: 10200
290
+ },
291
+ // เขตดุสิต
292
+ {
293
+ code: 100201,
294
+ name_th: "\u0E41\u0E02\u0E27\u0E07\u0E14\u0E38\u0E2A\u0E34\u0E15",
295
+ name_en: "Dusit",
296
+ district_code: 1002,
297
+ zip_code: 10300
298
+ },
299
+ {
300
+ code: 100202,
301
+ name_th: "\u0E41\u0E02\u0E27\u0E07\u0E27\u0E0A\u0E34\u0E23\u0E1E\u0E22\u0E32\u0E1A\u0E32\u0E25",
302
+ name_en: "Wachiraphayaban",
303
+ district_code: 1002,
304
+ zip_code: 10300
305
+ },
306
+ // เขตบางรัก
307
+ {
308
+ code: 100401,
309
+ name_th: "\u0E41\u0E02\u0E27\u0E07\u0E21\u0E2B\u0E32\u0E1E\u0E24\u0E12\u0E32\u0E23\u0E32\u0E21",
310
+ name_en: "Maha Phruettharam",
311
+ district_code: 1004,
312
+ zip_code: 10500
313
+ },
314
+ {
315
+ code: 100402,
316
+ name_th: "\u0E41\u0E02\u0E27\u0E07\u0E2A\u0E35\u0E25\u0E21",
317
+ name_en: "Si Lom",
318
+ district_code: 1004,
319
+ zip_code: 10500
320
+ },
321
+ // เขตปทุมวัน
322
+ {
323
+ code: 100701,
324
+ name_th: "\u0E41\u0E02\u0E27\u0E07\u0E23\u0E2D\u0E07\u0E40\u0E21\u0E37\u0E2D\u0E07",
325
+ name_en: "Rong Mueang",
326
+ district_code: 1007,
327
+ zip_code: 10330
328
+ },
329
+ {
330
+ code: 100702,
331
+ name_th: "\u0E41\u0E02\u0E27\u0E07\u0E27\u0E31\u0E07\u0E43\u0E2B\u0E21\u0E48",
332
+ name_en: "Wang Mai",
333
+ district_code: 1007,
334
+ zip_code: 10330
335
+ },
336
+ {
337
+ code: 100703,
338
+ name_th: "\u0E41\u0E02\u0E27\u0E07\u0E1B\u0E17\u0E38\u0E21\u0E27\u0E31\u0E19",
339
+ name_en: "Pathum Wan",
340
+ district_code: 1007,
341
+ zip_code: 10330
342
+ },
343
+ // อำเภอเมืองสมุทรปราการ
344
+ {
345
+ code: 110101,
346
+ name_th: "\u0E15\u0E33\u0E1A\u0E25\u0E1B\u0E32\u0E01\u0E19\u0E49\u0E33",
347
+ name_en: "Pak Nam",
348
+ district_code: 1101,
349
+ zip_code: 10270
350
+ },
351
+ {
352
+ code: 110102,
353
+ name_th: "\u0E15\u0E33\u0E1A\u0E25\u0E2A\u0E33\u0E42\u0E23\u0E07\u0E40\u0E2B\u0E19\u0E37\u0E2D",
354
+ name_en: "Samrong Nuea",
355
+ district_code: 1101,
356
+ zip_code: 10270
357
+ },
358
+ // อำเภอเมืองนนทบุรี
359
+ {
360
+ code: 120101,
361
+ name_th: "\u0E15\u0E33\u0E1A\u0E25\u0E2A\u0E27\u0E19\u0E43\u0E2B\u0E0D\u0E48",
362
+ name_en: "Suan Yai",
363
+ district_code: 1201,
364
+ zip_code: 11e3
365
+ },
366
+ {
367
+ code: 120102,
368
+ name_th: "\u0E15\u0E33\u0E1A\u0E25\u0E15\u0E25\u0E32\u0E14\u0E02\u0E27\u0E31\u0E0D",
369
+ name_en: "Talat Khwan",
370
+ district_code: 1201,
371
+ zip_code: 11e3
372
+ }
373
+ ];
374
+ const filteredProvinces = computed(() => {
375
+ if (!searchQuery.value) return mockProvinces;
376
+ const query = searchQuery.value.toLowerCase();
377
+ return mockProvinces.filter(
378
+ (p) => p.name_th.toLowerCase().includes(query) || p.name_en.toLowerCase().includes(query)
379
+ );
380
+ });
381
+ const filteredDistricts = computed(() => {
382
+ if (!selectedProvince.value) return [];
383
+ const districts = mockDistricts.filter(
384
+ (d) => d.province_code === selectedProvince.value?.code
385
+ );
386
+ if (!searchQuery.value) return districts;
387
+ const query = searchQuery.value.toLowerCase();
388
+ return districts.filter(
389
+ (d) => d.name_th.toLowerCase().includes(query) || d.name_en.toLowerCase().includes(query)
390
+ );
391
+ });
392
+ const filteredSubDistricts = computed(() => {
393
+ if (!selectedDistrict.value) return [];
394
+ const subDistricts = mockSubDistricts.filter(
395
+ (s) => s.district_code === selectedDistrict.value?.code
396
+ );
397
+ if (!searchQuery.value) return subDistricts;
398
+ const query = searchQuery.value.toLowerCase();
399
+ return subDistricts.filter(
400
+ (s) => s.name_th.toLowerCase().includes(query) || s.name_en.toLowerCase().includes(query)
401
+ );
402
+ });
403
+ const onSelectProvince = (province) => {
404
+ selectedProvince.value = province;
405
+ selectedDistrict.value = null;
406
+ selectedSubDistrict.value = null;
407
+ searchQuery.value = "";
408
+ currentTab.value = "district";
409
+ emits("selectProvince", province);
410
+ updateModelValue();
411
+ };
412
+ const onSelectDistrict = (district) => {
413
+ selectedDistrict.value = district;
414
+ selectedSubDistrict.value = null;
415
+ searchQuery.value = "";
416
+ currentTab.value = "subDistrict";
417
+ emits("selectDistrict", district);
418
+ updateModelValue();
419
+ };
420
+ const onSelectSubDistrict = (subDistrict) => {
421
+ selectedSubDistrict.value = subDistrict;
422
+ searchQuery.value = "";
423
+ currentTab.value = "zipCode";
424
+ emits("selectSubDistrict", subDistrict);
425
+ updateModelValue();
426
+ };
427
+ const onSelectZipCode = () => {
428
+ if (selectedSubDistrict.value) {
429
+ emits("selectZipCode", selectedSubDistrict.value.zip_code);
430
+ popoverOpen.value = false;
431
+ currentTab.value = "province";
432
+ updateModelValue();
433
+ }
434
+ };
435
+ const updateModelValue = () => {
436
+ const value = {
437
+ province: selectedProvince.value || void 0,
438
+ district: selectedDistrict.value || void 0,
439
+ subDistrict: selectedSubDistrict.value || void 0,
440
+ zipCode: selectedSubDistrict.value?.zip_code,
441
+ detail: addressDetailValue.value || void 0,
442
+ fullAddress: buildFullAddress()
443
+ };
444
+ modelValue.value = value;
445
+ emits("change", value);
446
+ };
447
+ const buildFullAddress = () => {
448
+ const parts = [];
449
+ if (addressDetailValue.value) parts.push(addressDetailValue.value);
450
+ if (selectedSubDistrict.value) parts.push(selectedSubDistrict.value.name_th);
451
+ if (selectedDistrict.value) parts.push(selectedDistrict.value.name_th);
452
+ if (selectedProvince.value) parts.push(selectedProvince.value.name_th);
453
+ if (selectedSubDistrict.value)
454
+ parts.push(selectedSubDistrict.value.zip_code.toString());
455
+ return parts.join(" ");
456
+ };
457
+ watch(addressDetailValue, () => {
458
+ updateModelValue();
459
+ });
460
+ const defaultRules = (v) => {
461
+ if (!v && props.required) {
462
+ return "\u0E01\u0E23\u0E38\u0E13\u0E32\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E17\u0E35\u0E48\u0E2D\u0E22\u0E39\u0E48";
463
+ }
464
+ if (v && v.split(", ").length < 4 && props.required) {
465
+ return "\u0E01\u0E23\u0E38\u0E13\u0E32\u0E40\u0E25\u0E37\u0E2D\u0E01\u0E17\u0E35\u0E48\u0E2D\u0E22\u0E39\u0E48\u0E43\u0E2B\u0E49\u0E04\u0E23\u0E1A\u0E16\u0E49\u0E27\u0E19";
466
+ }
467
+ return true;
468
+ };
469
+ const validate = async () => {
470
+ const addressValid = await addressFieldRef.value?.validate();
471
+ const detailValid = await detailFieldRef.value?.validate?.();
472
+ return addressValid?.valid !== false && detailValid !== false;
473
+ };
474
+ const setErrors = (errMsg) => {
475
+ addressFieldRef.value?.setErrors(errMsg);
476
+ };
477
+ const reset = () => {
478
+ selectedProvince.value = null;
479
+ selectedDistrict.value = null;
480
+ selectedSubDistrict.value = null;
481
+ addressDetailValue.value = "";
482
+ currentTab.value = "province";
483
+ updateModelValue();
484
+ };
485
+ defineExpose({
486
+ validate,
487
+ setErrors,
488
+ reset,
489
+ addressFieldRef,
490
+ detailFieldRef
491
+ });
87
492
  </script>
@@ -1,3 +1,76 @@
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 Province {
2
+ code: number;
3
+ name_th: string;
4
+ name_en: string;
5
+ }
6
+ interface District {
7
+ code: number;
8
+ name_th: string;
9
+ name_en: string;
10
+ province_code: number;
11
+ }
12
+ interface SubDistrict {
13
+ code: number;
14
+ name_th: string;
15
+ name_en: string;
16
+ district_code: number;
17
+ zip_code: number;
18
+ }
19
+ export interface InputAddressValue {
20
+ province?: Province;
21
+ district?: District;
22
+ subDistrict?: SubDistrict;
23
+ zipCode?: number;
24
+ detail?: string;
25
+ fullAddress?: string;
26
+ }
27
+ export interface InputAddressProps {
28
+ name?: string;
29
+ label?: string;
30
+ placeholder?: string;
31
+ labelDetail?: string;
32
+ placeholderDetail?: string;
33
+ required?: boolean;
34
+ requiredDetail?: boolean;
35
+ rules?: object | string | Function;
36
+ provinces?: Province[];
37
+ districts?: District[];
38
+ subDistricts?: SubDistrict[];
39
+ }
40
+ type __VLS_Props = InputAddressProps;
41
+ type __VLS_ModelProps = {
42
+ modelValue?: InputAddressValue;
43
+ };
44
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
45
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
46
+ validate: () => Promise<boolean>;
47
+ setErrors: (errMsg: string[]) => void;
48
+ reset: () => void;
49
+ addressFieldRef: import("vue").Ref<any, any>;
50
+ detailFieldRef: import("vue").Ref<any, any>;
51
+ }, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
52
+ "update:modelValue": (value: InputAddressValue) => any;
53
+ } & {
54
+ change: (value: InputAddressValue) => any;
55
+ selectProvince: (province: Province) => any;
56
+ selectDistrict: (district: District) => any;
57
+ selectSubDistrict: (subDistrict: SubDistrict) => any;
58
+ selectZipCode: (zipCode: number) => any;
59
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
60
+ onChange?: ((value: InputAddressValue) => any) | undefined;
61
+ "onUpdate:modelValue"?: ((value: InputAddressValue) => any) | undefined;
62
+ onSelectProvince?: ((province: Province) => any) | undefined;
63
+ onSelectDistrict?: ((district: District) => any) | undefined;
64
+ onSelectSubDistrict?: ((subDistrict: SubDistrict) => any) | undefined;
65
+ onSelectZipCode?: ((zipCode: number) => any) | undefined;
66
+ }>, {
67
+ label: string;
68
+ required: boolean;
69
+ name: string;
70
+ placeholder: string;
71
+ labelDetail: string;
72
+ placeholderDetail: string;
73
+ requiredDetail: boolean;
74
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
2
75
  declare const _default: typeof __VLS_export;
3
76
  export default _default;
@@ -1,27 +1,54 @@
1
1
  <template>
2
2
  <ShadPopover v-model:open="open" :modal="false">
3
3
  <ShadPopoverAnchor as-child>
4
- <InputTextField ref="fieldRef" v-model="searchText" :label="props.label" :name="props.name" :rules="props.rules"
5
- :required="props.required" :description="props.description" :placeholder="props.placeholder"
6
- :disabled-border="props.disabledBorder" :disabled-error-message="props.disabledErrorMessage" autocomplete="off"
7
- @focus="open = true" @click="open = true" @blur="handleBlur" :show-counter="props.showCounter"
8
- :limit="props.limit">
4
+ <InputTextField
5
+ ref="fieldRef"
6
+ v-model="searchText"
7
+ :label="props.label"
8
+ :name="props.name"
9
+ :rules="props.rules"
10
+ :required="props.required"
11
+ :description="props.description"
12
+ :placeholder="props.placeholder"
13
+ :disabled-border="props.disabledBorder"
14
+ :disabled-error-message="props.disabledErrorMessage"
15
+ autocomplete="off"
16
+ @focus="open = true"
17
+ @click="console.log('click')"
18
+ @blur="handleBlur"
19
+ :show-counter="props.showCounter"
20
+ :limit="props.limit"
21
+ >
9
22
  <template #append>
10
- <Icon @mousedown.stop.prevent="handleIconMouseDown" :name="modelValue ? 'lucide:x' : 'lucide:search'"
11
- size="16" class="cursor-pointer" />
23
+ <Icon
24
+ @mousedown.stop.prevent="handleIconMouseDown"
25
+ :name="modelValue ? 'lucide:x' : 'lucide:search'"
26
+ size="16"
27
+ class="cursor-pointer"
28
+ />
12
29
  </template>
13
30
  </InputTextField>
14
31
  </ShadPopoverAnchor>
15
- <ShadPopoverContent @openAutoFocus.prevent @closeAutoFocus.prevent align="start"
16
- class="max-h-[312px] overflow-auto">
17
- <div v-if="filteredOptions.length === 0" class="flex flex-col gap-[8px] items-center justify-center h-[192px]">
32
+ <ShadPopoverContent
33
+ @openAutoFocus.prevent
34
+ @closeAutoFocus.prevent
35
+ align="start"
36
+ class="max-h-[312px] overflow-auto"
37
+ >
38
+ <div
39
+ v-if="filteredOptions.length === 0"
40
+ class="flex flex-col gap-[8px] items-center justify-center h-[192px]"
41
+ >
18
42
  <Icon name="pukaad:page-not-found" class="w-[77px] h-[64px]" />
19
43
  <div class="font-body-medium text-center text-gray">ไม่พบข้อมูล</div>
20
44
  </div>
21
45
  <div v-else>
22
- <div v-for="option in filteredOptions" :key="option.value"
46
+ <div
47
+ v-for="option in filteredOptions"
48
+ :key="option.value"
23
49
  class="cursor-pointer hover:bg-smoke flex items-center gap-[8px] px-[8px] py-[6px]"
24
- @mousedown.prevent="selectOption(option)">
50
+ @mousedown.prevent="selectOption(option)"
51
+ >
25
52
  <div class="flex flex-col gap-[4px] w-full">
26
53
  <div class="font-body-medium">
27
54
  {{ option.label }}
@@ -30,7 +57,11 @@
30
57
  {{ option.description }}
31
58
  </div>
32
59
  </div>
33
- <Icon v-if="modelValue === option.value" name="lucide:check" size="16" />
60
+ <Icon
61
+ v-if="modelValue === option.value"
62
+ name="lucide:check"
63
+ size="16"
64
+ />
34
65
  </div>
35
66
  </div>
36
67
  </ShadPopoverContent>
@@ -50,7 +50,7 @@
50
50
  >
51
51
  {{ displayValue }}
52
52
  </span>
53
- <Icon name="lucide:chevrons-up-down" class="h-4 w-4" />
53
+ <Icon name="lucide:chevron-down" class="h-4 w-4" />
54
54
  </ShadButton>
55
55
  </ShadFormControl>
56
56
  </ShadPopoverTrigger>
@@ -55,10 +55,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
55
55
  name: string;
56
56
  disabled: boolean;
57
57
  limit: number;
58
+ readonly: boolean;
58
59
  disabledErrorMessage: boolean;
59
60
  disabledBorder: boolean;
60
61
  showCounter: boolean;
61
- readonly: boolean;
62
62
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
63
63
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
64
64
  declare const _default: typeof __VLS_export;
@@ -55,10 +55,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
55
55
  name: string;
56
56
  disabled: boolean;
57
57
  limit: number;
58
+ readonly: boolean;
58
59
  disabledErrorMessage: boolean;
59
60
  disabledBorder: boolean;
60
61
  showCounter: boolean;
61
- readonly: boolean;
62
62
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
63
63
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
64
64
  declare const _default: typeof __VLS_export;
@@ -45,10 +45,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
45
45
  name: string;
46
46
  disabled: boolean;
47
47
  limit: number;
48
+ readonly: boolean;
48
49
  disabledErrorMessage: boolean;
49
50
  disabledBorder: boolean;
50
51
  showCounter: boolean;
51
- readonly: boolean;
52
52
  resize: "none" | "both" | "horizontal" | "vertical";
53
53
  rows: number;
54
54
  heightScroll: boolean;
@@ -45,10 +45,10 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
45
45
  name: string;
46
46
  disabled: boolean;
47
47
  limit: number;
48
+ readonly: boolean;
48
49
  disabledErrorMessage: boolean;
49
50
  disabledBorder: boolean;
50
51
  showCounter: boolean;
51
- readonly: boolean;
52
52
  resize: "none" | "both" | "horizontal" | "vertical";
53
53
  rows: number;
54
54
  heightScroll: boolean;
@@ -0,0 +1,10 @@
1
+ export interface LoadingProps {
2
+ isLoading?: boolean;
3
+ text?: string;
4
+ }
5
+ declare const __VLS_export: import("vue").DefineComponent<LoadingProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<LoadingProps> & Readonly<{}>, {
6
+ text: string;
7
+ isLoading: boolean;
8
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ declare const _default: typeof __VLS_export;
10
+ export default _default;
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <Transition name="loading-fade">
3
+ <div
4
+ v-if="isLoading"
5
+ class="absolute inset-0 opacity-50 z-50 flex items-center justify-center backdrop-blur-2xl"
6
+ >
7
+ <div class="flex flex-col items-center gap-3">
8
+ <div class="loading-spinner"></div>
9
+ <span v-if="text" class="text-sm text-cloud">{{ text }}</span>
10
+ </div>
11
+ </div>
12
+ </Transition>
13
+ </template>
14
+
15
+ <script setup>
16
+ defineProps({
17
+ isLoading: { type: Boolean, required: false, default: false },
18
+ text: { type: String, required: false, default: "" }
19
+ });
20
+ </script>
21
+
22
+ <style scoped>
23
+ .loading-spinner{animation:spin .8s linear infinite;border:6px solid rgba(0,0,0,.1);border-radius:50%;border-top-color:#3b82f6;height:64px;width:64px}@keyframes spin{to{transform:rotate(1turn)}}.loading-fade-enter-active,.loading-fade-leave-active{transition:opacity .2s ease}.loading-fade-enter-from,.loading-fade-leave-to{opacity:0}
24
+ </style>
@@ -0,0 +1,10 @@
1
+ export interface LoadingProps {
2
+ isLoading?: boolean;
3
+ text?: string;
4
+ }
5
+ declare const __VLS_export: import("vue").DefineComponent<LoadingProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<LoadingProps> & Readonly<{}>, {
6
+ text: string;
7
+ isLoading: boolean;
8
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
9
+ declare const _default: typeof __VLS_export;
10
+ export default _default;
@@ -5,27 +5,29 @@ export interface ModalProps {
5
5
  description?: string;
6
6
  footer?: string;
7
7
  disabledCloseBtn?: boolean;
8
+ isLoading?: boolean;
9
+ loadingText?: string;
8
10
  }
9
11
  type __VLS_Props = ModalProps;
10
12
  type __VLS_ModelProps = {
11
13
  modelValue?: boolean;
12
14
  };
13
15
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
14
- declare var __VLS_23: {}, __VLS_31: {}, __VLS_39: {}, __VLS_41: {
16
+ declare var __VLS_28: {}, __VLS_36: {}, __VLS_44: {}, __VLS_46: {
15
17
  meta: any;
16
- }, __VLS_49: {
18
+ }, __VLS_54: {
17
19
  meta: any;
18
20
  };
19
21
  type __VLS_Slots = {} & {
20
- header?: (props: typeof __VLS_23) => any;
22
+ header?: (props: typeof __VLS_28) => any;
21
23
  } & {
22
- title?: (props: typeof __VLS_31) => any;
24
+ title?: (props: typeof __VLS_36) => any;
23
25
  } & {
24
- description?: (props: typeof __VLS_39) => any;
26
+ description?: (props: typeof __VLS_44) => any;
25
27
  } & {
26
- default?: (props: typeof __VLS_41) => any;
28
+ default?: (props: typeof __VLS_46) => any;
27
29
  } & {
28
- footer?: (props: typeof __VLS_49) => any;
30
+ footer?: (props: typeof __VLS_54) => any;
29
31
  };
30
32
  declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
31
33
  "update:modelValue": (value: boolean) => any;
@@ -7,6 +7,7 @@
7
7
  @submit="onSubmit"
8
8
  @close="onClose"
9
9
  >
10
+ <Loading :is-loading="props.isLoading" :text="props.loadingText" />
10
11
  <ShadDialogHeader>
11
12
  <slot name="header">
12
13
  <ShadDialogTitle>
@@ -36,7 +37,9 @@ const props = defineProps({
36
37
  title: { type: String, required: false },
37
38
  description: { type: String, required: false },
38
39
  footer: { type: String, required: false },
39
- disabledCloseBtn: { type: Boolean, required: false, default: false }
40
+ disabledCloseBtn: { type: Boolean, required: false, default: false },
41
+ isLoading: { type: Boolean, required: false },
42
+ loadingText: { type: String, required: false }
40
43
  });
41
44
  const isOpen = defineModel({ type: Boolean, ...{
42
45
  default: false
@@ -5,27 +5,29 @@ export interface ModalProps {
5
5
  description?: string;
6
6
  footer?: string;
7
7
  disabledCloseBtn?: boolean;
8
+ isLoading?: boolean;
9
+ loadingText?: string;
8
10
  }
9
11
  type __VLS_Props = ModalProps;
10
12
  type __VLS_ModelProps = {
11
13
  modelValue?: boolean;
12
14
  };
13
15
  type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
14
- declare var __VLS_23: {}, __VLS_31: {}, __VLS_39: {}, __VLS_41: {
16
+ declare var __VLS_28: {}, __VLS_36: {}, __VLS_44: {}, __VLS_46: {
15
17
  meta: any;
16
- }, __VLS_49: {
18
+ }, __VLS_54: {
17
19
  meta: any;
18
20
  };
19
21
  type __VLS_Slots = {} & {
20
- header?: (props: typeof __VLS_23) => any;
22
+ header?: (props: typeof __VLS_28) => any;
21
23
  } & {
22
- title?: (props: typeof __VLS_31) => any;
24
+ title?: (props: typeof __VLS_36) => any;
23
25
  } & {
24
- description?: (props: typeof __VLS_39) => any;
26
+ description?: (props: typeof __VLS_44) => any;
25
27
  } & {
26
- default?: (props: typeof __VLS_41) => any;
28
+ default?: (props: typeof __VLS_46) => any;
27
29
  } & {
28
- footer?: (props: typeof __VLS_49) => any;
30
+ footer?: (props: typeof __VLS_54) => any;
29
31
  };
30
32
  declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
31
33
  "update:modelValue": (value: boolean) => any;
@@ -5,7 +5,7 @@ type __VLS_Slots = {} & {
5
5
  };
6
6
  declare const __VLS_base: import("vue").DefineComponent<InputGroupButtonProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<InputGroupButtonProps> & Readonly<{}>, {
7
7
  variant: "default" | "outline" | "ghost" | "link" | "text" | "icon" | null;
8
- size: "sm" | "icon-sm" | "icon-xs" | "xs" | null;
8
+ size: "icon-xs" | "sm" | "icon-sm" | "xs" | null;
9
9
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
10
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
11
11
  declare const _default: typeof __VLS_export;
@@ -5,7 +5,7 @@ type __VLS_Slots = {} & {
5
5
  };
6
6
  declare const __VLS_base: import("vue").DefineComponent<InputGroupButtonProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<InputGroupButtonProps> & Readonly<{}>, {
7
7
  variant: "default" | "outline" | "ghost" | "link" | "text" | "icon" | null;
8
- size: "sm" | "icon-sm" | "icon-xs" | "xs" | null;
8
+ size: "icon-xs" | "sm" | "icon-sm" | "xs" | null;
9
9
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
10
10
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
11
11
  declare const _default: typeof __VLS_export;
@@ -12,7 +12,7 @@ export declare const inputGroupAddonVariants: (props?: ({
12
12
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
13
13
  export type InputGroupVariants = VariantProps<typeof inputGroupAddonVariants>;
14
14
  export declare const inputGroupButtonVariants: (props?: ({
15
- size?: "sm" | "icon-sm" | "icon-xs" | "xs" | null | undefined;
15
+ size?: "icon-xs" | "sm" | "icon-sm" | "xs" | null | undefined;
16
16
  } & import("class-variance-authority/types").ClassProp) | undefined) => string;
17
17
  export type InputGroupButtonVariants = VariantProps<typeof inputGroupButtonVariants>;
18
18
  export interface InputGroupButtonProps {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pukaad-ui-lib",
3
- "version": "1.37.0",
3
+ "version": "1.39.0",
4
4
  "description": "pukaad-ui for MeMSG",
5
5
  "repository": {
6
6
  "type": "git",