v-uixy 1.4.0 → 1.6.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/package.json +1 -1
- package/templates/components/Alert/Alert.types.ts +4 -0
- package/templates/components/Alert/AlertItem.component.vue +2 -4
- package/templates/components/Alert/index.ts +6 -1
- package/templates/components/Breadcrumb/Breadcrumb.component.vue +116 -0
- package/templates/components/Breadcrumb/Breadcrumb.styles.ts +40 -0
- package/templates/components/Breadcrumb/Breadcrumb.types.ts +30 -0
- package/templates/components/Breadcrumb/index.ts +17 -0
- package/templates/components/Button/Button.styles.ts +4 -4
- package/templates/components/Calendar/Calendar.component.vue +50 -2
- package/templates/components/Calendar/Calendar.styles.ts +4 -0
- package/templates/components/Calendar/Calendar.types.ts +8 -0
- package/templates/components/Chart/Chart.component.vue +308 -0
- package/templates/components/Chart/Chart.types.ts +18 -0
- package/templates/components/Chart/index.ts +13 -0
- package/templates/components/Checkbox/Checkbox.component.vue +19 -5
- package/templates/components/Checkbox/Checkbox.styles.ts +10 -0
- package/templates/components/Checkbox/Checkbox.types.ts +1 -0
- package/templates/components/DatePicker/DatePicker.component.vue +219 -0
- package/templates/components/DatePicker/DatePicker.styles.ts +74 -0
- package/templates/components/DatePicker/DatePicker.types.ts +18 -0
- package/templates/components/DatePicker/DatePicker.utils.ts +46 -0
- package/templates/components/DatePicker/TimeWheel.vue +145 -0
- package/templates/components/DatePicker/TimeWheelColumn.vue +147 -0
- package/templates/components/DatePicker/index.ts +11 -0
- package/templates/components/DateRangePicker/DateRangePicker.component.vue +304 -0
- package/templates/components/DateRangePicker/DateRangePicker.types.ts +23 -0
- package/templates/components/DateRangePicker/index.ts +11 -0
- package/templates/components/FileUpload/FileUpload.component.vue +306 -0
- package/templates/components/FileUpload/FileUpload.styles.ts +74 -0
- package/templates/components/FileUpload/FileUpload.types.ts +31 -0
- package/templates/components/FileUpload/index.ts +15 -0
- package/templates/components/InputOTP/InputOTP.component.vue +4 -2
- package/templates/components/InputOTP/InputOTP.types.ts +1 -0
- package/templates/components/Popover/Popover.component.vue +1 -1
- package/templates/components/SegmentedControl/SegmentedControl.component.vue +85 -0
- package/templates/components/SegmentedControl/SegmentedControl.styles.ts +55 -0
- package/templates/components/SegmentedControl/SegmentedControl.types.ts +15 -0
- package/templates/components/SegmentedControl/index.ts +11 -0
- package/templates/components/Table/Table.component.vue +507 -0
- package/templates/components/Table/Table.styles.ts +140 -0
- package/templates/components/Table/Table.types.ts +98 -0
- package/templates/components/Table/index.ts +39 -0
- package/templates/components.json +42 -0
- package/templates/composables/usePosition.ts +6 -4
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
ref="containerRef"
|
|
4
|
+
class="relative overflow-y-scroll [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
5
|
+
:class="props.disabled ? 'pointer-events-none opacity-40' : ''"
|
|
6
|
+
:style="{ height: `${itemHeight * visibleCount}px`, scrollSnapType: 'y mandatory' }"
|
|
7
|
+
@scroll="onScroll"
|
|
8
|
+
>
|
|
9
|
+
<div :style="{ height: `${spacer}px` }" aria-hidden="true" />
|
|
10
|
+
<button
|
|
11
|
+
v-for="(value, index) in props.values"
|
|
12
|
+
:key="index"
|
|
13
|
+
type="button"
|
|
14
|
+
class="flex w-full items-center justify-center font-500 tabular-nums select-none transition-colors"
|
|
15
|
+
style="scroll-snap-align: center"
|
|
16
|
+
:style="itemStyle(index)"
|
|
17
|
+
@click="selectIndex(index)"
|
|
18
|
+
>
|
|
19
|
+
{{ props.format ? props.format(value) : value }}
|
|
20
|
+
</button>
|
|
21
|
+
<div :style="{ height: `${spacer}px` }" aria-hidden="true" />
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
|
|
25
|
+
<script setup lang="ts">
|
|
26
|
+
const props = withDefaults(
|
|
27
|
+
defineProps<{
|
|
28
|
+
values: (number | string)[];
|
|
29
|
+
disabled?: boolean;
|
|
30
|
+
itemHeight?: number;
|
|
31
|
+
visibleCount?: number;
|
|
32
|
+
format?: (value: number | string) => string;
|
|
33
|
+
}>(),
|
|
34
|
+
{
|
|
35
|
+
itemHeight: 34,
|
|
36
|
+
visibleCount: 5,
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const model = defineModel<number | string>();
|
|
41
|
+
|
|
42
|
+
const containerRef = ref<HTMLElement>();
|
|
43
|
+
|
|
44
|
+
const scrollTop = ref(0);
|
|
45
|
+
|
|
46
|
+
const itemHeight = computed(() => props.itemHeight);
|
|
47
|
+
|
|
48
|
+
const visibleCount = computed(() => props.visibleCount);
|
|
49
|
+
|
|
50
|
+
const spacer = computed(
|
|
51
|
+
() => (itemHeight.value * (visibleCount.value - 1)) / 2
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const currentIndex = computed(() => {
|
|
55
|
+
const idx = props.values.findIndex((v) => v === model.value);
|
|
56
|
+
return idx === -1 ? 0 : idx;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const itemStyle = (index: number) => {
|
|
60
|
+
const distance =
|
|
61
|
+
Math.abs(index * itemHeight.value - scrollTop.value) / itemHeight.value;
|
|
62
|
+
const opacity = Math.max(0.25, 1 - distance * 0.32);
|
|
63
|
+
const scale = Math.max(0.78, 1 - distance * 0.12);
|
|
64
|
+
return {
|
|
65
|
+
height: `${itemHeight.value}px`,
|
|
66
|
+
lineHeight: `${itemHeight.value}px`,
|
|
67
|
+
opacity,
|
|
68
|
+
transform: `scale(${scale})`,
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
let settleTimer: ReturnType<typeof setTimeout> | undefined;
|
|
73
|
+
let rafId = 0;
|
|
74
|
+
|
|
75
|
+
const onScroll = () => {
|
|
76
|
+
const el = containerRef.value;
|
|
77
|
+
if (!el) return;
|
|
78
|
+
|
|
79
|
+
if (!rafId) {
|
|
80
|
+
rafId = requestAnimationFrame(() => {
|
|
81
|
+
scrollTop.value = el.scrollTop;
|
|
82
|
+
rafId = 0;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (settleTimer) clearTimeout(settleTimer);
|
|
87
|
+
settleTimer = setTimeout(settle, 110);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const settle = () => {
|
|
91
|
+
const el = containerRef.value;
|
|
92
|
+
if (!el) return;
|
|
93
|
+
|
|
94
|
+
const index = Math.max(
|
|
95
|
+
0,
|
|
96
|
+
Math.min(props.values.length - 1, Math.round(el.scrollTop / itemHeight.value))
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const value = props.values[index];
|
|
100
|
+
if (value !== undefined && value !== model.value) model.value = value;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const scrollToIndex = (index: number) => {
|
|
104
|
+
const el = containerRef.value;
|
|
105
|
+
if (!el) return;
|
|
106
|
+
|
|
107
|
+
const top = index * itemHeight.value;
|
|
108
|
+
if (Math.abs(el.scrollTop - top) < 1) return;
|
|
109
|
+
|
|
110
|
+
el.scrollTo({ top, behavior: "smooth" });
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const selectIndex = (index: number) => {
|
|
114
|
+
if (props.disabled) return;
|
|
115
|
+
|
|
116
|
+
const value = props.values[index];
|
|
117
|
+
if (value !== undefined && value !== model.value) model.value = value;
|
|
118
|
+
|
|
119
|
+
scrollToIndex(index);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
onMounted(() => {
|
|
123
|
+
nextTick(() => {
|
|
124
|
+
const el = containerRef.value;
|
|
125
|
+
if (el) {
|
|
126
|
+
el.scrollTop = currentIndex.value * itemHeight.value;
|
|
127
|
+
scrollTop.value = el.scrollTop;
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
watch(
|
|
133
|
+
() => model.value,
|
|
134
|
+
() => {
|
|
135
|
+
const el = containerRef.value;
|
|
136
|
+
if (!el) return;
|
|
137
|
+
|
|
138
|
+
const target = currentIndex.value * itemHeight.value;
|
|
139
|
+
if (Math.abs(el.scrollTop - target) > 1) scrollToIndex(currentIndex.value);
|
|
140
|
+
}
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
onUnmounted(() => {
|
|
144
|
+
if (settleTimer) clearTimeout(settleTimer);
|
|
145
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
146
|
+
});
|
|
147
|
+
</script>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import UixyDatePicker from "./DatePicker.component.vue";
|
|
2
|
+
import UixyTimeWheel from "./TimeWheel.vue";
|
|
3
|
+
import { type UixyDatePickerProps } from "./DatePicker.types";
|
|
4
|
+
import { type UixyTimeParts } from "./DatePicker.utils";
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
UixyDatePicker,
|
|
8
|
+
UixyTimeWheel,
|
|
9
|
+
type UixyDatePickerProps,
|
|
10
|
+
type UixyTimeParts,
|
|
11
|
+
};
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="flex w-full flex-col gap-1">
|
|
3
|
+
<label v-if="props.label" :class="labelStyles({ status })">
|
|
4
|
+
{{ props.label }}
|
|
5
|
+
</label>
|
|
6
|
+
|
|
7
|
+
<div
|
|
8
|
+
ref="triggerRef"
|
|
9
|
+
role="button"
|
|
10
|
+
:tabindex="props.disabled ? -1 : 0"
|
|
11
|
+
:aria-expanded="active"
|
|
12
|
+
:class="triggerStyles({ status, open: active })"
|
|
13
|
+
@click="onTriggerClick"
|
|
14
|
+
@keydown.enter.prevent="onTriggerClick"
|
|
15
|
+
@keydown.space.prevent="onTriggerClick"
|
|
16
|
+
>
|
|
17
|
+
<uixy-icon
|
|
18
|
+
:name="props.icon ?? 'calendar'"
|
|
19
|
+
:class="triggerIconStyles({ disabled: props.disabled })"
|
|
20
|
+
/>
|
|
21
|
+
<span
|
|
22
|
+
:class="triggerValueStyles({ placeholder: !hasValue, disabled: props.disabled })"
|
|
23
|
+
>
|
|
24
|
+
{{ displayValue }}
|
|
25
|
+
</span>
|
|
26
|
+
<uixy-icon
|
|
27
|
+
v-if="hasValue && !props.disabled"
|
|
28
|
+
name="close"
|
|
29
|
+
class="h-4 w-4 shrink-0 cursor-pointer text-gray-400 hover:text-black dark:text-gray-600 dark:hover:text-white"
|
|
30
|
+
@click.stop="clear"
|
|
31
|
+
/>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div v-if="!props.hideHelper && helperText" class="h-4">
|
|
35
|
+
<p :class="helperStyles({ status })">{{ helperText }}</p>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<teleport to="body">
|
|
39
|
+
<animate-presence>
|
|
40
|
+
<motion.div
|
|
41
|
+
v-if="active"
|
|
42
|
+
:initial="{ opacity: 0 }"
|
|
43
|
+
:animate="{ opacity: 1 }"
|
|
44
|
+
:exit="{ opacity: 0 }"
|
|
45
|
+
:transition="{ duration: 0.15, ease: 'easeInOut' }"
|
|
46
|
+
:style="positionStyles"
|
|
47
|
+
>
|
|
48
|
+
<div ref="refElement">
|
|
49
|
+
<motion.div
|
|
50
|
+
:initial="{ y: 4, scale: 0.96, opacity: 0 }"
|
|
51
|
+
:animate="{ y: 0, scale: 1, opacity: 1 }"
|
|
52
|
+
:exit="{ opacity: 0, scale: 0.96 }"
|
|
53
|
+
:transition="{ duration: 0.15, ease: 'easeInOut' }"
|
|
54
|
+
:class="panelStyles()"
|
|
55
|
+
>
|
|
56
|
+
<div class="flex items-stretch gap-3">
|
|
57
|
+
<uixy-calendar
|
|
58
|
+
:year="viewYear"
|
|
59
|
+
:month="viewMonth"
|
|
60
|
+
:start-of-week="props.startOfWeek"
|
|
61
|
+
:is-date-disabled="props.isDateDisabled"
|
|
62
|
+
:range-start="model.start"
|
|
63
|
+
:range-end="model.end"
|
|
64
|
+
:range-hover="rangeHover"
|
|
65
|
+
@update:model-value="onDayPick"
|
|
66
|
+
@day-hover="onDayHover"
|
|
67
|
+
@day-leave="rangeHover = null"
|
|
68
|
+
/>
|
|
69
|
+
|
|
70
|
+
<div v-if="props.withTime" class="flex gap-3">
|
|
71
|
+
<div class="flex flex-col items-center gap-1">
|
|
72
|
+
<span :class="sectionLabelStyles()">Start</span>
|
|
73
|
+
<uixy-time-wheel
|
|
74
|
+
v-model="startTime"
|
|
75
|
+
:hour-cycle="24"
|
|
76
|
+
:with-seconds="props.withSeconds"
|
|
77
|
+
:disabled="!model.start"
|
|
78
|
+
/>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="flex flex-col items-center gap-1">
|
|
81
|
+
<span :class="sectionLabelStyles()">End</span>
|
|
82
|
+
<uixy-time-wheel
|
|
83
|
+
v-model="endTime"
|
|
84
|
+
:hour-cycle="24"
|
|
85
|
+
:with-seconds="props.withSeconds"
|
|
86
|
+
:disabled="!model.end"
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div :class="footerStyles()">
|
|
93
|
+
<uixy-button variant="tertiary" size="sm" @click="clear">
|
|
94
|
+
Clear
|
|
95
|
+
</uixy-button>
|
|
96
|
+
<uixy-button variant="primary" size="sm" @click="handleLeave">
|
|
97
|
+
Done
|
|
98
|
+
</uixy-button>
|
|
99
|
+
</div>
|
|
100
|
+
</motion.div>
|
|
101
|
+
</div>
|
|
102
|
+
</motion.div>
|
|
103
|
+
</animate-presence>
|
|
104
|
+
</teleport>
|
|
105
|
+
</div>
|
|
106
|
+
</template>
|
|
107
|
+
|
|
108
|
+
<script setup lang="ts">
|
|
109
|
+
import { AnimatePresence, motion } from "motion-v";
|
|
110
|
+
import { usePosition } from "~/composables";
|
|
111
|
+
import { UixyIcon } from "../Icon";
|
|
112
|
+
import { UixyButton } from "../Button";
|
|
113
|
+
import { UixyCalendar } from "../Calendar";
|
|
114
|
+
import { UixyTimeWheel } from "../DatePicker";
|
|
115
|
+
import type {
|
|
116
|
+
UixyDateRange,
|
|
117
|
+
UixyDateRangePickerProps,
|
|
118
|
+
} from "./DateRangePicker.types";
|
|
119
|
+
import {
|
|
120
|
+
triggerStyles,
|
|
121
|
+
triggerValueStyles,
|
|
122
|
+
triggerIconStyles,
|
|
123
|
+
labelStyles,
|
|
124
|
+
helperStyles,
|
|
125
|
+
panelStyles,
|
|
126
|
+
sectionLabelStyles,
|
|
127
|
+
footerStyles,
|
|
128
|
+
} from "../DatePicker/DatePicker.styles";
|
|
129
|
+
import {
|
|
130
|
+
formatDate,
|
|
131
|
+
timePartsOf,
|
|
132
|
+
withTimeParts,
|
|
133
|
+
startOfDay,
|
|
134
|
+
type UixyTimeParts,
|
|
135
|
+
} from "../DatePicker/DatePicker.utils";
|
|
136
|
+
|
|
137
|
+
const props = defineProps<UixyDateRangePickerProps>();
|
|
138
|
+
|
|
139
|
+
const model = defineModel<UixyDateRange>({
|
|
140
|
+
default: () => ({ start: null, end: null }),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const {
|
|
144
|
+
active,
|
|
145
|
+
styles: positionStyles,
|
|
146
|
+
refElement,
|
|
147
|
+
handleOpen,
|
|
148
|
+
handleLeave,
|
|
149
|
+
} = usePosition({ direction: props.direction ?? "bottom" });
|
|
150
|
+
|
|
151
|
+
const triggerRef = ref<HTMLElement>();
|
|
152
|
+
|
|
153
|
+
const rangeHover = ref<Date | null>(null);
|
|
154
|
+
|
|
155
|
+
const draftStartTime = ref<UixyTimeParts>(timePartsOf(model.value.start));
|
|
156
|
+
|
|
157
|
+
const draftEndTime = ref<UixyTimeParts>(timePartsOf(model.value.end));
|
|
158
|
+
|
|
159
|
+
const status = computed(() => {
|
|
160
|
+
if (props.disabled) return "disabled";
|
|
161
|
+
|
|
162
|
+
return props.status ?? "default";
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const helperText = computed(() => {
|
|
166
|
+
if (status.value === "error") return props.errorText ?? "";
|
|
167
|
+
|
|
168
|
+
return props.helperText ?? "";
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const hasValue = computed(() => !!model.value.start || !!model.value.end);
|
|
172
|
+
|
|
173
|
+
const formatBound = (date: Date | null) =>
|
|
174
|
+
formatDate(date, {
|
|
175
|
+
withTime: props.withTime,
|
|
176
|
+
withSeconds: props.withSeconds,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const displayValue = computed(() => {
|
|
180
|
+
const { start, end } = model.value;
|
|
181
|
+
|
|
182
|
+
if (start && end) return `${formatBound(start)} – ${formatBound(end)}`;
|
|
183
|
+
if (start) return `${formatBound(start)} – …`;
|
|
184
|
+
|
|
185
|
+
return props.placeholder ?? "Select range";
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
const viewBase = computed(
|
|
189
|
+
() => model.value.start ?? model.value.end ?? new Date(),
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const viewYear = computed(() => viewBase.value.getFullYear());
|
|
193
|
+
|
|
194
|
+
const viewMonth = computed(() => viewBase.value.getMonth());
|
|
195
|
+
|
|
196
|
+
const startTime = computed<UixyTimeParts>({
|
|
197
|
+
get: () =>
|
|
198
|
+
model.value.start ? timePartsOf(model.value.start) : draftStartTime.value,
|
|
199
|
+
set: (parts) => {
|
|
200
|
+
draftStartTime.value = parts;
|
|
201
|
+
|
|
202
|
+
if (!model.value.start) return;
|
|
203
|
+
|
|
204
|
+
model.value = {
|
|
205
|
+
...model.value,
|
|
206
|
+
start: withTimeParts(model.value.start, parts),
|
|
207
|
+
};
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const endTime = computed<UixyTimeParts>({
|
|
212
|
+
get: () =>
|
|
213
|
+
model.value.end ? timePartsOf(model.value.end) : draftEndTime.value,
|
|
214
|
+
set: (parts) => {
|
|
215
|
+
draftEndTime.value = parts;
|
|
216
|
+
|
|
217
|
+
if (!model.value.end) return;
|
|
218
|
+
|
|
219
|
+
model.value = {
|
|
220
|
+
...model.value,
|
|
221
|
+
end: withTimeParts(model.value.end, parts),
|
|
222
|
+
};
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const onDayPick = (date: Date | null | undefined) => {
|
|
227
|
+
if (!date) return;
|
|
228
|
+
|
|
229
|
+
const { start, end } = model.value;
|
|
230
|
+
|
|
231
|
+
if (!start || end) {
|
|
232
|
+
model.value = {
|
|
233
|
+
start: withTimeParts(date, draftStartTime.value),
|
|
234
|
+
end: null,
|
|
235
|
+
};
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const dateIsEarlier = startOfDay(date) <= startOfDay(start);
|
|
240
|
+
const earlier = dateIsEarlier ? date : start;
|
|
241
|
+
const later = dateIsEarlier ? start : date;
|
|
242
|
+
|
|
243
|
+
model.value = {
|
|
244
|
+
start: withTimeParts(earlier, draftStartTime.value),
|
|
245
|
+
end: withTimeParts(later, draftEndTime.value),
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
rangeHover.value = null;
|
|
249
|
+
|
|
250
|
+
if (!props.withTime && props.closeOnSelect) handleLeave();
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
const onDayHover = (date: Date) => {
|
|
254
|
+
if (model.value.start && !model.value.end) rangeHover.value = date;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const onTriggerClick = (event: MouseEvent | KeyboardEvent) => {
|
|
258
|
+
if (props.disabled) return;
|
|
259
|
+
|
|
260
|
+
handleOpen(event as MouseEvent);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const clear = () => {
|
|
264
|
+
model.value = { start: null, end: null };
|
|
265
|
+
rangeHover.value = null;
|
|
266
|
+
|
|
267
|
+
draftStartTime.value = { hour: 0, minute: 0, second: 0 };
|
|
268
|
+
draftEndTime.value = { hour: 0, minute: 0, second: 0 };
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
watch(active, (isActive) => {
|
|
272
|
+
if (isActive) {
|
|
273
|
+
draftStartTime.value = timePartsOf(model.value.start);
|
|
274
|
+
draftEndTime.value = timePartsOf(model.value.end);
|
|
275
|
+
} else {
|
|
276
|
+
rangeHover.value = null;
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
const onDocClick = (event: MouseEvent) => {
|
|
281
|
+
if (!active.value) return;
|
|
282
|
+
|
|
283
|
+
const target = event.target as Node;
|
|
284
|
+
|
|
285
|
+
const clickedInside =
|
|
286
|
+
refElement.value?.contains(target) || triggerRef.value?.contains(target);
|
|
287
|
+
|
|
288
|
+
if (!clickedInside) handleLeave();
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
292
|
+
if (active.value && event.key === "Escape") handleLeave();
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
onMounted(() => {
|
|
296
|
+
window.addEventListener("click", onDocClick);
|
|
297
|
+
window.addEventListener("keydown", onKeyDown);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
onUnmounted(() => {
|
|
301
|
+
window.removeEventListener("click", onDocClick);
|
|
302
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
303
|
+
});
|
|
304
|
+
</script>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { IconName } from "~/types/icons";
|
|
2
|
+
|
|
3
|
+
export interface UixyDateRange {
|
|
4
|
+
start: Date | null;
|
|
5
|
+
end: Date | null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface UixyDateRangePickerProps {
|
|
9
|
+
placeholder?: string;
|
|
10
|
+
label?: string;
|
|
11
|
+
helperText?: string;
|
|
12
|
+
errorText?: string;
|
|
13
|
+
status?: "default" | "error";
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
icon?: IconName;
|
|
16
|
+
withTime?: boolean;
|
|
17
|
+
withSeconds?: boolean;
|
|
18
|
+
startOfWeek?: "Sunday" | "Monday";
|
|
19
|
+
isDateDisabled?: (date: Date) => boolean;
|
|
20
|
+
direction?: "top" | "bottom";
|
|
21
|
+
hideHelper?: boolean;
|
|
22
|
+
closeOnSelect?: boolean;
|
|
23
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import UixyDateRangePicker from "./DateRangePicker.component.vue";
|
|
2
|
+
import {
|
|
3
|
+
type UixyDateRangePickerProps,
|
|
4
|
+
type UixyDateRange,
|
|
5
|
+
} from "./DateRangePicker.types";
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
UixyDateRangePicker,
|
|
9
|
+
type UixyDateRangePickerProps,
|
|
10
|
+
type UixyDateRange,
|
|
11
|
+
};
|