pukaad-ui-lib 1.308.0 → 1.310.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 +1 -1
- package/dist/runtime/components/card/card-review.vue +1 -0
- package/dist/runtime/components/input/input-date-picker.d.vue.ts +2 -0
- package/dist/runtime/components/input/input-date-picker.vue +29 -17
- package/dist/runtime/components/input/input-date-picker.vue.d.ts +2 -0
- package/dist/runtime/components/input/input-rating.vue +51 -46
- package/dist/runtime/components/modal/modal-media-view.d.vue.ts +3 -0
- package/dist/runtime/components/modal/modal-media-view.vue +139 -1
- package/dist/runtime/components/modal/modal-media-view.vue.d.ts +3 -0
- package/package.json +1 -1
package/dist/module.json
CHANGED
|
@@ -21,6 +21,7 @@ export interface InputDatePickerProps {
|
|
|
21
21
|
isDateDisabled?: (date: DateValue) => boolean;
|
|
22
22
|
defaultPlaceholder?: DateValue;
|
|
23
23
|
reverseYears?: boolean;
|
|
24
|
+
variant?: "default" | "button";
|
|
24
25
|
}
|
|
25
26
|
type __VLS_Props = InputDatePickerProps;
|
|
26
27
|
type __VLS_ModelProps = {
|
|
@@ -38,6 +39,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
38
39
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
39
40
|
"onUpdate:modelValue"?: ((value: Date | undefined) => any) | undefined;
|
|
40
41
|
}>, {
|
|
42
|
+
variant: "default" | "button";
|
|
41
43
|
disabled: boolean;
|
|
42
44
|
required: boolean;
|
|
43
45
|
id: string;
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
v-slot="{ errorMessage }"
|
|
7
7
|
v-model="modelValue"
|
|
8
8
|
>
|
|
9
|
-
<ShadFormItem>
|
|
9
|
+
<ShadFormItem :class="cn('flex flex-col w-full', props.class)">
|
|
10
10
|
<ShadFormLabel v-if="props.label || $slots.label" class="w-full">
|
|
11
11
|
<slot name="label">
|
|
12
12
|
<div class="flex-1">
|
|
@@ -25,26 +25,37 @@
|
|
|
25
25
|
:disabled="props.disabled"
|
|
26
26
|
:class="
|
|
27
27
|
cn(
|
|
28
|
-
'w-full flex items-center
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
'w-full flex items-center gap-2 font-body-large',
|
|
29
|
+
!modelValue && props.variant === 'button' ? 'justify-center' : 'justify-between',
|
|
30
|
+
errorMessage && 'border-destructive hover:border-destructive'
|
|
31
31
|
)
|
|
32
32
|
"
|
|
33
33
|
>
|
|
34
|
-
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
34
|
+
<!-- Filled State -->
|
|
35
|
+
<template v-if="modelValue">
|
|
36
|
+
<span>{{ formattedDate }}</span>
|
|
37
|
+
<Icon
|
|
38
|
+
name="lucide:x"
|
|
39
|
+
:size="16"
|
|
40
|
+
class="!pointer-events-auto cursor-pointer shrink-0"
|
|
41
|
+
@click.stop.prevent="clearDate"
|
|
42
|
+
/>
|
|
43
|
+
</template>
|
|
44
|
+
|
|
45
|
+
<!-- Empty State: button mode -->
|
|
46
|
+
<template v-else-if="props.variant === 'button'">
|
|
47
|
+
<span>{{ placeholder }}</span>
|
|
48
|
+
</template>
|
|
49
|
+
|
|
50
|
+
<!-- Empty State: default mode -->
|
|
51
|
+
<template v-else>
|
|
52
|
+
<span class="text-cloud">{{ placeholder }}</span>
|
|
53
|
+
<Icon name="lucide:calendar" :size="16" class="shrink-0" />
|
|
54
|
+
</template>
|
|
44
55
|
</Button>
|
|
45
56
|
</PopoverTrigger>
|
|
46
57
|
<PopoverContent class="w-auto p-0" align="start">
|
|
47
|
-
<div :class="cn('sm:flex', showTime && 'flex-row')">
|
|
58
|
+
<div :class="cn('sm:flex', props.showTime && 'flex-row')">
|
|
48
59
|
<Calendar
|
|
49
60
|
v-model="calendarValue"
|
|
50
61
|
initial-focus
|
|
@@ -58,7 +69,7 @@
|
|
|
58
69
|
/>
|
|
59
70
|
<!-- Time Picker -->
|
|
60
71
|
<div
|
|
61
|
-
v-if="showTime"
|
|
72
|
+
v-if="props.showTime"
|
|
62
73
|
class="flex flex-col sm:flex-row sm:h-[300px] divide-y sm:divide-y-0 sm:divide-x sm:border-t-0"
|
|
63
74
|
>
|
|
64
75
|
<!-- Hours -->
|
|
@@ -144,7 +155,8 @@ const props = defineProps({
|
|
|
144
155
|
minValue: { type: Date, required: false },
|
|
145
156
|
isDateDisabled: { type: Function, required: false },
|
|
146
157
|
defaultPlaceholder: { type: null, required: false },
|
|
147
|
-
reverseYears: { type: Boolean, required: false }
|
|
158
|
+
reverseYears: { type: Boolean, required: false },
|
|
159
|
+
variant: { type: String, required: false, default: "default" }
|
|
148
160
|
});
|
|
149
161
|
const maxCalendarValue = computed(() => {
|
|
150
162
|
if (!props.maxValue) return void 0;
|
|
@@ -21,6 +21,7 @@ export interface InputDatePickerProps {
|
|
|
21
21
|
isDateDisabled?: (date: DateValue) => boolean;
|
|
22
22
|
defaultPlaceholder?: DateValue;
|
|
23
23
|
reverseYears?: boolean;
|
|
24
|
+
variant?: "default" | "button";
|
|
24
25
|
}
|
|
25
26
|
type __VLS_Props = InputDatePickerProps;
|
|
26
27
|
type __VLS_ModelProps = {
|
|
@@ -38,6 +39,7 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_PublicProps, {
|
|
|
38
39
|
}, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
|
|
39
40
|
"onUpdate:modelValue"?: ((value: Date | undefined) => any) | undefined;
|
|
40
41
|
}>, {
|
|
42
|
+
variant: "default" | "button";
|
|
41
43
|
disabled: boolean;
|
|
42
44
|
required: boolean;
|
|
43
45
|
id: string;
|
|
@@ -1,53 +1,51 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
@rating-selected="onSelect"
|
|
46
|
-
:rating-value="ratingValue"
|
|
47
|
-
/>
|
|
2
|
+
<div class="inline-flex items-center gap-0">
|
|
3
|
+
<svg
|
|
4
|
+
v-for="n in 5"
|
|
5
|
+
:key="n"
|
|
6
|
+
:width="props.size"
|
|
7
|
+
:height="props.size"
|
|
8
|
+
viewBox="4 2 16.42 20.21"
|
|
9
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
10
|
+
:style="{
|
|
11
|
+
cursor: props.readonly ? 'default' : 'pointer',
|
|
12
|
+
marginRight: n < 5 ? '2px' : '0'
|
|
13
|
+
}"
|
|
14
|
+
@click="!props.readonly && onSelect(n)"
|
|
15
|
+
>
|
|
16
|
+
<defs>
|
|
17
|
+
<linearGradient
|
|
18
|
+
:id="`star-grad-${uid}-${n}`"
|
|
19
|
+
x1="0"
|
|
20
|
+
x2="100%"
|
|
21
|
+
y1="0"
|
|
22
|
+
y2="0"
|
|
23
|
+
>
|
|
24
|
+
<stop
|
|
25
|
+
:offset="`${getFill(n)}%`"
|
|
26
|
+
:stop-color="props.activeColor"
|
|
27
|
+
stop-opacity="1"
|
|
28
|
+
/>
|
|
29
|
+
<stop
|
|
30
|
+
:offset="`${getFill(n)}%`"
|
|
31
|
+
:stop-color="props.backgroundColor"
|
|
32
|
+
stop-opacity="1"
|
|
33
|
+
/>
|
|
34
|
+
</linearGradient>
|
|
35
|
+
</defs>
|
|
36
|
+
<polygon
|
|
37
|
+
points="12,17.27,16.15,19.78,17.64,18.7,16.54,13.98,20.21,10.8,19.64,9.05,14.81,8.64,12.92,4.18,11.08,4.18,9.19,8.63,4.36,9.04,3.79,10.8,7.46,13.98,6.36,18.7,7.85,19.78,12,17.27"
|
|
38
|
+
:fill="`url(#star-grad-${uid}-${n})`"
|
|
39
|
+
:stroke="props.borderSize > 0 ? props.borderColor : 'none'"
|
|
40
|
+
:stroke-width="props.borderSize"
|
|
41
|
+
stroke-linejoin="round"
|
|
42
|
+
/>
|
|
43
|
+
</svg>
|
|
44
|
+
</div>
|
|
48
45
|
</template>
|
|
49
46
|
|
|
50
47
|
<script setup>
|
|
48
|
+
import { computed } from "vue";
|
|
51
49
|
const emit = defineEmits(["select"]);
|
|
52
50
|
const props = defineProps({
|
|
53
51
|
readonly: { type: Boolean, required: false, default: false },
|
|
@@ -62,6 +60,13 @@ const ratingValue = defineModel({
|
|
|
62
60
|
type: Number,
|
|
63
61
|
default: 0
|
|
64
62
|
});
|
|
63
|
+
const uid = Math.random().toString(36).slice(2, 7);
|
|
64
|
+
const getFill = (n) => {
|
|
65
|
+
const val = ratingValue.value;
|
|
66
|
+
if (val >= n) return 100;
|
|
67
|
+
if (val < n - 1) return 0;
|
|
68
|
+
return Math.round((val - (n - 1)) * 100);
|
|
69
|
+
};
|
|
65
70
|
const onSelect = (value) => {
|
|
66
71
|
ratingValue.value = value;
|
|
67
72
|
emit("select", value);
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type { CardReviewProps } from "#pukaad-ui/types/components/card/card-review";
|
|
1
2
|
export interface ModalMediaViewProps {
|
|
2
3
|
items?: string[];
|
|
3
4
|
title?: string;
|
|
4
5
|
startIndex?: number;
|
|
6
|
+
/** ข้อมูล review เต็ม (user, review, replies) สำหรับแสดงที่ overlay ด้านล่าง */
|
|
7
|
+
review?: CardReviewProps;
|
|
5
8
|
}
|
|
6
9
|
type __VLS_Props = ModalMediaViewProps;
|
|
7
10
|
type __VLS_ModelProps = {
|
|
@@ -9,15 +9,153 @@
|
|
|
9
9
|
:select-index="props.startIndex"
|
|
10
10
|
class="h-full"
|
|
11
11
|
/>
|
|
12
|
+
|
|
13
|
+
<!-- Review info overlay (แสดงเมื่อมีข้อมูล review) -->
|
|
14
|
+
<div
|
|
15
|
+
v-if="props.review"
|
|
16
|
+
class="absolute bottom-0 w-full z-10"
|
|
17
|
+
style="
|
|
18
|
+
background: linear-gradient(
|
|
19
|
+
to top,
|
|
20
|
+
rgba(0, 0, 0, 0.75) 0%,
|
|
21
|
+
transparent 100%
|
|
22
|
+
);
|
|
23
|
+
"
|
|
24
|
+
>
|
|
25
|
+
<div class="flex flex-colw-full">
|
|
26
|
+
<!-- User row -->
|
|
27
|
+
<div
|
|
28
|
+
v-if="!showMoreDescription"
|
|
29
|
+
class="flex gap-[16px] p-[16px] w-full text-white"
|
|
30
|
+
>
|
|
31
|
+
<Avatar
|
|
32
|
+
:src="props.review.user?.avatar"
|
|
33
|
+
:alt="props.review.user?.name"
|
|
34
|
+
:size="36"
|
|
35
|
+
class="shrink-0"
|
|
36
|
+
/>
|
|
37
|
+
<div class="flex flex-col gap-[8px] w-full">
|
|
38
|
+
<div class="flex flex-col gap-[8px]">
|
|
39
|
+
<div class="flex justify-between w-full">
|
|
40
|
+
<span class="font-body-large">
|
|
41
|
+
{{ props.review.user?.name }}
|
|
42
|
+
</span>
|
|
43
|
+
<Icon name="lucide:flag" />
|
|
44
|
+
</div>
|
|
45
|
+
<div class="flex flex-col">
|
|
46
|
+
<span class="font-body-small">
|
|
47
|
+
{{ convertNumber(props.review.user?.review_count ?? 0) }}
|
|
48
|
+
รีวิว •
|
|
49
|
+
{{ convertNumber(props.review.user?.like_count ?? 0) }}
|
|
50
|
+
ชื่นชอบรีวิว
|
|
51
|
+
</span>
|
|
52
|
+
|
|
53
|
+
<!-- Rating + date -->
|
|
54
|
+
<div
|
|
55
|
+
v-if="props.review.review"
|
|
56
|
+
class="flex items-center gap-[8px]"
|
|
57
|
+
>
|
|
58
|
+
<InputRating
|
|
59
|
+
:size="14"
|
|
60
|
+
readonly
|
|
61
|
+
:model-value="props.review.review.rating"
|
|
62
|
+
/>
|
|
63
|
+
<span class="font-body-small opacity-75">
|
|
64
|
+
{{
|
|
65
|
+
convertDateTorelativeText(
|
|
66
|
+
props.review.review.created_at ?? ""
|
|
67
|
+
)
|
|
68
|
+
}}
|
|
69
|
+
</span>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
<!-- Description -->
|
|
74
|
+
<p
|
|
75
|
+
v-if="props.review.review?.description"
|
|
76
|
+
class="font-body-large line-clamp-1 cursor-pointer"
|
|
77
|
+
@click="showMoreDescription = true"
|
|
78
|
+
>
|
|
79
|
+
{{ props.review.review.description }}
|
|
80
|
+
</p>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
<div
|
|
84
|
+
v-if="showMoreDescription"
|
|
85
|
+
class="flex gap-[16px] bg-white rounded-t-[16px] w-full p-[16px] max-h-[322px]"
|
|
86
|
+
>
|
|
87
|
+
<Avatar
|
|
88
|
+
:src="props.review.user?.avatar"
|
|
89
|
+
:alt="props.review.user?.name"
|
|
90
|
+
:size="36"
|
|
91
|
+
class="shrink-0"
|
|
92
|
+
/>
|
|
93
|
+
<div class="flex flex-col gap-[8px] w-full">
|
|
94
|
+
<div class="flex flex-col gap-[8px]">
|
|
95
|
+
<div class="flex justify-between w-full">
|
|
96
|
+
<span class="font-body-large">
|
|
97
|
+
{{ props.review.user?.name }}
|
|
98
|
+
</span>
|
|
99
|
+
<Icon
|
|
100
|
+
name="lucide:x"
|
|
101
|
+
class="cursor-pointer"
|
|
102
|
+
@click="showMoreDescription = false"
|
|
103
|
+
/>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="flex flex-col">
|
|
106
|
+
<span class="font-body-small">
|
|
107
|
+
{{ convertNumber(props.review.user?.review_count ?? 0) }}
|
|
108
|
+
รีวิว •
|
|
109
|
+
{{ convertNumber(props.review.user?.like_count ?? 0) }}
|
|
110
|
+
ชื่นชอบรีวิว
|
|
111
|
+
</span>
|
|
112
|
+
|
|
113
|
+
<!-- Rating + date -->
|
|
114
|
+
<div
|
|
115
|
+
v-if="props.review.review"
|
|
116
|
+
class="flex items-center gap-[8px]"
|
|
117
|
+
>
|
|
118
|
+
<InputRating
|
|
119
|
+
:size="14"
|
|
120
|
+
readonly
|
|
121
|
+
:model-value="props.review.review.rating"
|
|
122
|
+
/>
|
|
123
|
+
<span class="font-body-small opacity-75">
|
|
124
|
+
{{
|
|
125
|
+
convertDateTorelativeText(
|
|
126
|
+
props.review.review.created_at ?? ""
|
|
127
|
+
)
|
|
128
|
+
}}
|
|
129
|
+
</span>
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
<!-- Description -->
|
|
134
|
+
<p
|
|
135
|
+
v-if="props.review.review?.description"
|
|
136
|
+
class="font-body-large max-h-[280px] overflow-y-auto"
|
|
137
|
+
@click="showMoreDescription = true"
|
|
138
|
+
>
|
|
139
|
+
{{ props.review.review.description }}
|
|
140
|
+
</p>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
12
145
|
</div>
|
|
13
146
|
</Modal>
|
|
14
147
|
</template>
|
|
15
148
|
|
|
16
149
|
<script setup>
|
|
150
|
+
import { ref } from "vue";
|
|
151
|
+
import { useConvert } from "../../composables/useConvert";
|
|
152
|
+
const { convertNumber, convertDateTorelativeText } = useConvert();
|
|
17
153
|
const props = defineProps({
|
|
18
154
|
items: { type: Array, required: false, default: () => [] },
|
|
19
155
|
title: { type: String, required: false, default: "" },
|
|
20
|
-
startIndex: { type: Number, required: false, default: 0 }
|
|
156
|
+
startIndex: { type: Number, required: false, default: 0 },
|
|
157
|
+
review: { type: Object, required: false }
|
|
21
158
|
});
|
|
22
159
|
const isOpen = defineModel({ type: Boolean, ...{ default: false } });
|
|
160
|
+
const showMoreDescription = ref(false);
|
|
23
161
|
</script>
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import type { CardReviewProps } from "#pukaad-ui/types/components/card/card-review";
|
|
1
2
|
export interface ModalMediaViewProps {
|
|
2
3
|
items?: string[];
|
|
3
4
|
title?: string;
|
|
4
5
|
startIndex?: number;
|
|
6
|
+
/** ข้อมูล review เต็ม (user, review, replies) สำหรับแสดงที่ overlay ด้านล่าง */
|
|
7
|
+
review?: CardReviewProps;
|
|
5
8
|
}
|
|
6
9
|
type __VLS_Props = ModalMediaViewProps;
|
|
7
10
|
type __VLS_ModelProps = {
|