@revojs/ui 0.2.8 → 0.2.10
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/components/button.d.ts +3 -2
- package/dist/components/container.d.ts +2 -0
- package/dist/components/icon.d.ts +3 -1
- package/dist/components/input.d.ts +3 -2
- package/dist/components/navigate.d.ts +3 -2
- package/dist/components/separator.d.ts +1 -6
- package/dist/components/stack.d.ts +12 -0
- package/dist/components/stack.js +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/module/icons.d.ts +2 -0
- package/dist/module/icons.js +12 -0
- package/dist/module/index.d.ts +1 -1
- package/dist/module/index.js +13 -23
- package/dist/style.css +7 -0
- package/dist/types/index.d.ts +0 -7
- package/dist/vue/components/breadcrumb.vue +14 -8
- package/dist/vue/components/button.vue +24 -19
- package/dist/vue/components/card.vue +21 -24
- package/dist/vue/components/checkbox.vue +11 -16
- package/dist/vue/components/collection.vue +15 -15
- package/dist/vue/components/container.vue +5 -6
- package/dist/vue/components/counter.vue +11 -14
- package/dist/vue/components/dialog.vue +11 -19
- package/dist/vue/components/field.vue +7 -16
- package/dist/vue/components/heading.vue +7 -8
- package/dist/vue/components/icon.vue +8 -42
- package/dist/vue/components/input.vue +18 -22
- package/dist/vue/components/link.vue +8 -9
- package/dist/vue/components/navigate.vue +2 -13
- package/dist/vue/components/popover.vue +57 -67
- package/dist/vue/components/progress.vue +9 -15
- package/dist/vue/components/radio.vue +9 -24
- package/dist/vue/components/range.vue +9 -21
- package/dist/vue/components/rating.vue +10 -20
- package/dist/vue/components/select.vue +86 -110
- package/dist/vue/components/separator.vue +2 -10
- package/dist/vue/components/sheet.vue +14 -19
- package/dist/vue/components/stack.vue +70 -0
- package/dist/vue/components/switch.vue +10 -21
- package/dist/vue/components/upload.vue +11 -23
- package/dist/vue/index.d.ts +3 -1
- package/dist/vue/index.js +5 -2
- package/dist/vue/utilities/use-focus-trap.d.ts +2 -0
- package/dist/vue/utilities/use-focus-trap.js +34 -0
- package/dist/vue/utilities/use-scroll-lock.d.ts +2 -0
- package/dist/vue/utilities/use-scroll-lock.js +33 -0
- package/package.json +5 -4
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<Stack>
|
|
3
3
|
<span v-if="label" class="text-sm font-medium">{{ label }}</span>
|
|
4
4
|
|
|
5
|
-
<
|
|
5
|
+
<Stack direction="horizontal" align="center" gap="0" role="radiogroup" :aria-label="label">
|
|
6
6
|
<Button
|
|
7
7
|
v-for="value in max"
|
|
8
8
|
:key="value"
|
|
9
|
-
class="text-2xl
|
|
9
|
+
class="text-2xl"
|
|
10
10
|
:class="variants[state(value)]"
|
|
11
11
|
size="icon"
|
|
12
12
|
variant="ghost"
|
|
@@ -16,30 +16,20 @@
|
|
|
16
16
|
:disabled="readonly || disabled"
|
|
17
17
|
@click="$emit('update:modelValue', value)"
|
|
18
18
|
>
|
|
19
|
-
<Icon
|
|
19
|
+
<Icon :is="StarIcon" />
|
|
20
20
|
</Button>
|
|
21
|
-
</
|
|
22
|
-
</
|
|
21
|
+
</Stack>
|
|
22
|
+
</Stack>
|
|
23
23
|
</template>
|
|
24
24
|
|
|
25
25
|
<script setup lang="ts">
|
|
26
|
+
import StarIcon from "~icons/hugeicons/star";
|
|
26
27
|
import Button from "./button.vue";
|
|
27
28
|
import Icon from "./icon.vue";
|
|
28
|
-
import
|
|
29
|
+
import Stack from "./stack.vue";
|
|
30
|
+
import type { RatingProps, RatingState } from "../../components/rating";
|
|
29
31
|
|
|
30
|
-
const {
|
|
31
|
-
modelValue = 0,
|
|
32
|
-
max = 5,
|
|
33
|
-
label = undefined,
|
|
34
|
-
readonly = false,
|
|
35
|
-
disabled = false,
|
|
36
|
-
} = defineProps<{
|
|
37
|
-
modelValue?: number;
|
|
38
|
-
max?: number;
|
|
39
|
-
label?: string;
|
|
40
|
-
readonly?: boolean;
|
|
41
|
-
disabled?: boolean;
|
|
42
|
-
}>();
|
|
32
|
+
const { modelValue = 0, max = 5, label, readonly = false, disabled = false } = defineProps<RatingProps>();
|
|
43
33
|
|
|
44
34
|
defineEmits<{
|
|
45
35
|
"update:modelValue": [value: number];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<Stack>
|
|
3
3
|
<span v-if="label" class="text-sm font-medium">
|
|
4
4
|
{{ label }}
|
|
5
5
|
</span>
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
<input v-if="name" type="hidden" :name="name" :value="modelValue" :required="required" />
|
|
8
8
|
|
|
9
9
|
<div ref="container" class="relative">
|
|
10
|
-
<
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
<Stack
|
|
11
|
+
direction="horizontal"
|
|
12
|
+
align="center"
|
|
13
|
+
class="min-h-10 w-full cursor-text rounded-ui border border-input px-3 text-sm ring-ring focus-within:ring-2"
|
|
13
14
|
:class="disabled ? 'cursor-not-allowed opacity-50' : ''"
|
|
14
15
|
@click="search?.focus()"
|
|
15
16
|
>
|
|
@@ -21,13 +22,15 @@
|
|
|
21
22
|
:placeholder="placeholder ?? 'Select'"
|
|
22
23
|
:aria-expanded="open"
|
|
23
24
|
:aria-controls="listboxId"
|
|
25
|
+
:aria-activedescendant="activeId"
|
|
24
26
|
aria-autocomplete="list"
|
|
25
27
|
:disabled="disabled"
|
|
26
28
|
@focus="open = true"
|
|
27
|
-
@input="
|
|
28
|
-
@keydown.enter.prevent="
|
|
29
|
+
@input="input"
|
|
30
|
+
@keydown.enter.prevent="confirm"
|
|
29
31
|
@keydown.escape="open = false"
|
|
30
|
-
@keydown.arrow-down.prevent="
|
|
32
|
+
@keydown.arrow-down.prevent="stepHighlight(1)"
|
|
33
|
+
@keydown.arrow-up.prevent="stepHighlight(-1)"
|
|
31
34
|
/>
|
|
32
35
|
|
|
33
36
|
<button
|
|
@@ -38,24 +41,22 @@
|
|
|
38
41
|
:disabled="disabled"
|
|
39
42
|
@click.stop="open = !open"
|
|
40
43
|
>
|
|
41
|
-
<Icon
|
|
44
|
+
<Icon :is="ArrowDownIcon" :size="iconSize" />
|
|
42
45
|
</button>
|
|
43
|
-
</
|
|
46
|
+
</Stack>
|
|
44
47
|
|
|
45
48
|
<div
|
|
46
|
-
|
|
47
|
-
:popover="popoverSupported ? 'manual' : undefined"
|
|
49
|
+
v-if="open"
|
|
48
50
|
:id="listboxId"
|
|
49
51
|
role="listbox"
|
|
50
52
|
:aria-label="label"
|
|
51
|
-
class="
|
|
52
|
-
:class="fallbackClass"
|
|
53
|
-
:style="popoverSupported ? panel : undefined"
|
|
53
|
+
class="absolute inset-x-0 top-full z-10 mt-1 max-h-[min(16rem,50vh)] overflow-y-auto rounded-ui border border-border bg-popover p-1 text-popover-foreground shadow-xl"
|
|
54
54
|
>
|
|
55
55
|
<Button
|
|
56
|
-
v-for="option in filtered"
|
|
56
|
+
v-for="(option, index) in filtered"
|
|
57
57
|
:key="option.value"
|
|
58
|
-
|
|
58
|
+
:id="`${listboxId}-${index}`"
|
|
59
|
+
:class="['w-full', highlighted === index ? 'bg-accent!' : '']"
|
|
59
60
|
variant="ghost"
|
|
60
61
|
layout="left"
|
|
61
62
|
:disabled="option.disabled"
|
|
@@ -63,48 +64,43 @@
|
|
|
63
64
|
:aria-selected="option.value === modelValue"
|
|
64
65
|
@click="select(option)"
|
|
65
66
|
>
|
|
66
|
-
<
|
|
67
|
+
<Stack direction="horizontal" align="center" justify="between" gap="1" class="w-full text-left">
|
|
67
68
|
<slot name="option" :option="option" :selected="option.value === modelValue">
|
|
68
69
|
{{ option.label }}
|
|
69
70
|
</slot>
|
|
70
71
|
|
|
71
|
-
<Icon v-if="option.value === modelValue"
|
|
72
|
-
</
|
|
72
|
+
<Icon v-if="option.value === modelValue" :is="TickIcon" :size="iconSize" />
|
|
73
|
+
</Stack>
|
|
73
74
|
</Button>
|
|
74
75
|
|
|
75
|
-
<p v-if="filtered.length === 0" class="px-
|
|
76
|
+
<p v-if="filtered.length === 0" class="px-1 py-5 text-center text-sm opacity-60">
|
|
76
77
|
{{ emptyLabel }}
|
|
77
78
|
</p>
|
|
78
79
|
</div>
|
|
79
80
|
</div>
|
|
80
|
-
</
|
|
81
|
+
</Stack>
|
|
81
82
|
</template>
|
|
82
83
|
|
|
83
84
|
<script setup lang="ts">
|
|
84
|
-
import
|
|
85
|
-
import
|
|
85
|
+
import ArrowDownIcon from "~icons/hugeicons/arrow-down-01";
|
|
86
|
+
import TickIcon from "~icons/hugeicons/tick-02";
|
|
87
|
+
import { computed, onUnmounted, ref, useId, useTemplateRef, watch } from "vue";
|
|
88
|
+
import type { SelectOption, SelectProps, SelectValue } from "../../components/select";
|
|
86
89
|
import Button from "./button.vue";
|
|
87
90
|
import Icon from "./icon.vue";
|
|
91
|
+
import Stack from "./stack.vue";
|
|
92
|
+
import { useFocusTrap } from "../utilities/use-focus-trap";
|
|
88
93
|
|
|
89
94
|
const {
|
|
90
|
-
modelValue
|
|
95
|
+
modelValue,
|
|
91
96
|
options,
|
|
92
|
-
label
|
|
93
|
-
placeholder
|
|
97
|
+
label,
|
|
98
|
+
placeholder,
|
|
94
99
|
emptyLabel = "No results found",
|
|
95
|
-
name
|
|
100
|
+
name,
|
|
96
101
|
required = false,
|
|
97
102
|
disabled = false,
|
|
98
|
-
} = defineProps<
|
|
99
|
-
modelValue?: SelectValue;
|
|
100
|
-
options: Array<SelectOption>;
|
|
101
|
-
label?: string;
|
|
102
|
-
placeholder?: string;
|
|
103
|
-
emptyLabel?: string;
|
|
104
|
-
name?: string;
|
|
105
|
-
required?: boolean;
|
|
106
|
-
disabled?: boolean;
|
|
107
|
-
}>();
|
|
103
|
+
} = defineProps<SelectProps>();
|
|
108
104
|
|
|
109
105
|
const emit = defineEmits<{
|
|
110
106
|
"update:modelValue": [value: SelectValue];
|
|
@@ -114,40 +110,32 @@ const open = ref(false);
|
|
|
114
110
|
|
|
115
111
|
const container = useTemplateRef("container");
|
|
116
112
|
|
|
117
|
-
|
|
113
|
+
useFocusTrap(container, open);
|
|
118
114
|
|
|
119
115
|
const search = useTemplateRef("search");
|
|
120
116
|
|
|
121
|
-
const dropdown = useTemplateRef("dropdown");
|
|
122
|
-
|
|
123
117
|
const listboxId = `select-${useId()}`;
|
|
124
118
|
|
|
125
|
-
const
|
|
119
|
+
const iconSize = 20;
|
|
126
120
|
|
|
127
121
|
const selected = computed(() => options.find((option) => option.value === modelValue));
|
|
128
122
|
|
|
129
123
|
const query = ref(selected.value?.label ?? "");
|
|
130
124
|
|
|
131
|
-
const panel = ref<Record<string, string>>({});
|
|
132
|
-
|
|
133
125
|
const filtered = computed(() => {
|
|
134
126
|
const term = query.value.trim().toLowerCase();
|
|
135
127
|
|
|
136
128
|
return term === "" ? options : options.filter((option) => option.label.toLowerCase().includes(term));
|
|
137
129
|
});
|
|
138
130
|
|
|
139
|
-
const
|
|
140
|
-
if (popoverSupported.value) {
|
|
141
|
-
return undefined;
|
|
142
|
-
}
|
|
131
|
+
const highlighted = ref(-1);
|
|
143
132
|
|
|
144
|
-
|
|
145
|
-
}
|
|
133
|
+
const activeId = computed(() =>
|
|
134
|
+
open.value && highlighted.value >= 0 ? `${listboxId}-${highlighted.value}` : undefined,
|
|
135
|
+
);
|
|
146
136
|
|
|
147
137
|
function handlePointerDown(event: PointerEvent) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (!container.value?.contains(target) && !dropdown.value?.contains(target)) {
|
|
138
|
+
if (!container.value?.contains(event.target as Node)) {
|
|
151
139
|
open.value = false;
|
|
152
140
|
}
|
|
153
141
|
}
|
|
@@ -159,65 +147,71 @@ function select(option: SelectOption) {
|
|
|
159
147
|
}
|
|
160
148
|
}
|
|
161
149
|
|
|
162
|
-
function
|
|
163
|
-
const
|
|
164
|
-
const list = dropdown.value;
|
|
150
|
+
function highlight(step: 1 | -1) {
|
|
151
|
+
const count = filtered.value.length;
|
|
165
152
|
|
|
166
|
-
if (
|
|
153
|
+
if (count === 0) {
|
|
167
154
|
return;
|
|
168
155
|
}
|
|
169
156
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
top: `${top}px`,
|
|
184
|
-
width: `${listWidth}px`,
|
|
185
|
-
};
|
|
157
|
+
let index = highlighted.value;
|
|
158
|
+
|
|
159
|
+
for (let round = 0; round < count; round++) {
|
|
160
|
+
index = (index + step + count) % count;
|
|
161
|
+
|
|
162
|
+
if (!filtered.value[index].disabled) {
|
|
163
|
+
highlighted.value = index;
|
|
164
|
+
|
|
165
|
+
document.getElementById(`${listboxId}-${index}`)?.scrollIntoView({ block: "nearest" });
|
|
166
|
+
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
186
170
|
}
|
|
187
171
|
|
|
188
|
-
|
|
172
|
+
function stepHighlight(step: 1 | -1) {
|
|
189
173
|
if (!open.value) {
|
|
190
|
-
|
|
174
|
+
open.value = true;
|
|
175
|
+
|
|
176
|
+
return;
|
|
191
177
|
}
|
|
192
|
-
});
|
|
193
178
|
|
|
194
|
-
|
|
195
|
-
|
|
179
|
+
highlight(step);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function confirm() {
|
|
183
|
+
if (!open.value) {
|
|
184
|
+
open.value = true;
|
|
196
185
|
|
|
197
|
-
if (!list) {
|
|
198
186
|
return;
|
|
199
187
|
}
|
|
200
188
|
|
|
201
|
-
|
|
202
|
-
query.value = "";
|
|
189
|
+
const option = filtered.value[highlighted.value] ?? filtered.value[0];
|
|
203
190
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
191
|
+
if (option) {
|
|
192
|
+
select(option);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
207
195
|
|
|
208
|
-
|
|
196
|
+
function input() {
|
|
197
|
+
open.value = true;
|
|
209
198
|
|
|
210
|
-
|
|
199
|
+
highlighted.value = -1;
|
|
200
|
+
}
|
|
211
201
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
202
|
+
watch(selected, (value) => {
|
|
203
|
+
if (!open.value) {
|
|
204
|
+
query.value = value?.label ?? "";
|
|
205
|
+
}
|
|
206
|
+
});
|
|
215
207
|
|
|
216
|
-
|
|
208
|
+
watch(open, (value) => {
|
|
209
|
+
if (value) {
|
|
210
|
+
query.value = "";
|
|
217
211
|
|
|
218
|
-
|
|
212
|
+
highlighted.value = filtered.value.findIndex((option) => option.value === modelValue);
|
|
219
213
|
|
|
220
|
-
|
|
214
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
221
215
|
|
|
222
216
|
if (document.activeElement !== search.value) {
|
|
223
217
|
search.value?.focus();
|
|
@@ -225,31 +219,13 @@ watch(open, async (value) => {
|
|
|
225
219
|
} else {
|
|
226
220
|
document.removeEventListener("pointerdown", handlePointerDown);
|
|
227
221
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
window.removeEventListener("scroll", updatePanel, true);
|
|
231
|
-
|
|
232
|
-
window.visualViewport?.removeEventListener("resize", updatePanel);
|
|
233
|
-
|
|
234
|
-
if (popoverSupported.value && list.matches(":popover-open")) {
|
|
235
|
-
list.hidePopover();
|
|
236
|
-
}
|
|
222
|
+
highlighted.value = -1;
|
|
237
223
|
|
|
238
224
|
query.value = selected.value?.label ?? "";
|
|
239
225
|
}
|
|
240
226
|
});
|
|
241
227
|
|
|
242
|
-
onMounted(() => {
|
|
243
|
-
popoverSupported.value = typeof dropdown.value?.showPopover === "function";
|
|
244
|
-
});
|
|
245
|
-
|
|
246
228
|
onUnmounted(() => {
|
|
247
229
|
document.removeEventListener("pointerdown", handlePointerDown);
|
|
248
|
-
|
|
249
|
-
window.removeEventListener("resize", updatePanel);
|
|
250
|
-
|
|
251
|
-
window.removeEventListener("scroll", updatePanel, true);
|
|
252
|
-
|
|
253
|
-
window.visualViewport?.removeEventListener("resize", updatePanel);
|
|
254
230
|
});
|
|
255
231
|
</script>
|
|
@@ -9,17 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
<script setup lang="ts">
|
|
11
11
|
import { computed } from "vue";
|
|
12
|
-
import type {
|
|
12
|
+
import type { SeparatorOrientation, SeparatorProps } from "../../components/separator";
|
|
13
13
|
|
|
14
|
-
const {
|
|
15
|
-
orientation = "horizontal",
|
|
16
|
-
variant = undefined,
|
|
17
|
-
decorative = false,
|
|
18
|
-
} = defineProps<{
|
|
19
|
-
orientation?: SeparatorOrientation;
|
|
20
|
-
variant?: DividerVariant;
|
|
21
|
-
decorative?: boolean;
|
|
22
|
-
}>();
|
|
14
|
+
const { orientation = "horizontal", variant, decorative = false } = defineProps<SeparatorProps>();
|
|
23
15
|
|
|
24
16
|
const selected = computed(() => variant ?? orientation);
|
|
25
17
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
<Button
|
|
12
12
|
size="icon"
|
|
13
13
|
variant="ghost"
|
|
14
|
-
icon-prefix="
|
|
14
|
+
:icon-prefix="CloseIcon"
|
|
15
15
|
:aria-label="closeLabel"
|
|
16
16
|
@click="sheet?.close()"
|
|
17
17
|
/>
|
|
@@ -29,24 +29,15 @@
|
|
|
29
29
|
</template>
|
|
30
30
|
|
|
31
31
|
<script setup lang="ts">
|
|
32
|
+
import CloseIcon from "~icons/hugeicons/multiplication-sign";
|
|
32
33
|
import { useTemplateRef, watchEffect } from "vue";
|
|
33
|
-
import type { SheetSide } from "../../components/sheet
|
|
34
|
+
import type { SheetProps, SheetSide } from "../../components/sheet";
|
|
34
35
|
import Button from "./button.vue";
|
|
35
36
|
import Card from "./card.vue";
|
|
37
|
+
import { useFocusTrap } from "../utilities/use-focus-trap";
|
|
38
|
+
import { useScrollLock } from "../utilities/use-scroll-lock";
|
|
36
39
|
|
|
37
|
-
const {
|
|
38
|
-
modelValue = false,
|
|
39
|
-
side = "right",
|
|
40
|
-
title = undefined,
|
|
41
|
-
description = undefined,
|
|
42
|
-
closeLabel = "Close",
|
|
43
|
-
} = defineProps<{
|
|
44
|
-
modelValue?: boolean;
|
|
45
|
-
side?: SheetSide;
|
|
46
|
-
title?: string;
|
|
47
|
-
description?: string;
|
|
48
|
-
closeLabel?: string;
|
|
49
|
-
}>();
|
|
40
|
+
const { modelValue = false, side = "right", title, description, closeLabel = "Close" } = defineProps<SheetProps>();
|
|
50
41
|
|
|
51
42
|
defineEmits<{
|
|
52
43
|
"update:modelValue": [value: boolean];
|
|
@@ -54,6 +45,10 @@ defineEmits<{
|
|
|
54
45
|
|
|
55
46
|
const sheet = useTemplateRef("sheet");
|
|
56
47
|
|
|
48
|
+
useFocusTrap(sheet, () => modelValue);
|
|
49
|
+
|
|
50
|
+
useScrollLock(() => modelValue);
|
|
51
|
+
|
|
57
52
|
const positions: Record<SheetSide, string> = {
|
|
58
53
|
top: "bottom-auto left-0 right-0 top-0 h-auto w-full",
|
|
59
54
|
right: "bottom-0 left-auto right-0 top-0 h-full max-h-full w-[min(28rem,90vw)]",
|
|
@@ -62,10 +57,10 @@ const positions: Record<SheetSide, string> = {
|
|
|
62
57
|
};
|
|
63
58
|
|
|
64
59
|
const cardClasses: Record<SheetSide, string> = {
|
|
65
|
-
top: "max-h-[85vh] w-full shadow-
|
|
66
|
-
right: "h-full w-full shadow-
|
|
67
|
-
bottom: "max-h-[85vh] w-full shadow-
|
|
68
|
-
left: "h-full w-full shadow-
|
|
60
|
+
top: "max-h-[85vh] w-full border-l-0 border-r-0 border-t-0 shadow-sheet-top",
|
|
61
|
+
right: "h-full w-full border-b-0 border-r-0 border-t-0 shadow-sheet-right",
|
|
62
|
+
bottom: "max-h-[85vh] w-full border-b-0 border-l-0 border-r-0 shadow-sheet-bottom",
|
|
63
|
+
left: "h-full w-full border-b-0 border-l-0 border-t-0 shadow-sheet-left",
|
|
69
64
|
};
|
|
70
65
|
|
|
71
66
|
watchEffect(() => {
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="is"
|
|
4
|
+
class="flex min-w-0"
|
|
5
|
+
:class="[
|
|
6
|
+
directions[direction],
|
|
7
|
+
gaps[gap],
|
|
8
|
+
paddings[padding],
|
|
9
|
+
aligns[align],
|
|
10
|
+
justifies[justify],
|
|
11
|
+
wrap ? 'flex-wrap' : '',
|
|
12
|
+
]"
|
|
13
|
+
>
|
|
14
|
+
<slot />
|
|
15
|
+
</component>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script setup lang="ts">
|
|
19
|
+
import type { Component } from "vue";
|
|
20
|
+
import type { StackAlign, StackDirection, StackGap, StackJustify, StackProps } from "../../components/stack";
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
is = "div",
|
|
24
|
+
direction = "vertical",
|
|
25
|
+
gap = "1",
|
|
26
|
+
padding = "0",
|
|
27
|
+
align = "stretch",
|
|
28
|
+
justify = "start",
|
|
29
|
+
wrap = false,
|
|
30
|
+
} = defineProps<
|
|
31
|
+
StackProps & {
|
|
32
|
+
is?: string | Component;
|
|
33
|
+
}
|
|
34
|
+
>();
|
|
35
|
+
|
|
36
|
+
const directions: Record<StackDirection, string> = {
|
|
37
|
+
horizontal: "flex-row",
|
|
38
|
+
vertical: "flex-col",
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const gaps: Record<StackGap, string> = {
|
|
42
|
+
"0": "gap-0",
|
|
43
|
+
"1": "gap-1",
|
|
44
|
+
"3": "gap-3",
|
|
45
|
+
"5": "gap-5",
|
|
46
|
+
"7": "gap-7",
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const paddings: Record<StackGap, string> = {
|
|
50
|
+
"0": "p-0",
|
|
51
|
+
"1": "p-1",
|
|
52
|
+
"3": "p-3",
|
|
53
|
+
"5": "p-5",
|
|
54
|
+
"7": "p-7",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const aligns: Record<StackAlign, string> = {
|
|
58
|
+
center: "items-center",
|
|
59
|
+
end: "items-end",
|
|
60
|
+
start: "items-start",
|
|
61
|
+
stretch: "items-stretch",
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const justifies: Record<StackJustify, string> = {
|
|
65
|
+
between: "justify-between",
|
|
66
|
+
center: "justify-center",
|
|
67
|
+
end: "justify-end",
|
|
68
|
+
start: "justify-start",
|
|
69
|
+
};
|
|
70
|
+
</script>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<label
|
|
2
|
+
<Stack is="label" direction="horizontal" align="start" gap="3" :class="interactionVariants[interaction]">
|
|
3
3
|
<input v-if="name" type="hidden" :name="name" :value="modelValue ? 'on' : 'off'" />
|
|
4
4
|
|
|
5
5
|
<button
|
|
6
|
-
class="relative mt-0.5 flex h-6 w-11 shrink-0 items-center rounded-
|
|
6
|
+
class="relative mt-0.5 flex h-6 w-11 shrink-0 items-center rounded-ui outline-ring transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed"
|
|
7
7
|
:class="variants[state]"
|
|
8
8
|
type="button"
|
|
9
9
|
role="switch"
|
|
@@ -11,37 +11,26 @@
|
|
|
11
11
|
:disabled="disabled"
|
|
12
12
|
@click="$emit('update:modelValue', !modelValue)"
|
|
13
13
|
>
|
|
14
|
-
<span class="block size-5 rounded-
|
|
14
|
+
<span class="block size-5 rounded-ui shadow transition-transform" :class="thumbVariants[state]" />
|
|
15
15
|
</button>
|
|
16
16
|
|
|
17
|
-
<span v-if="label || description || $slots.default"
|
|
17
|
+
<Stack is="span" v-if="label || description || $slots.default" gap="0">
|
|
18
18
|
<span class="text-sm font-medium">
|
|
19
19
|
<slot> {{ label }} </slot>
|
|
20
20
|
</span>
|
|
21
21
|
|
|
22
22
|
<span v-if="description" class="text-sm opacity-60"> {{ description }} </span>
|
|
23
|
-
</
|
|
24
|
-
</
|
|
23
|
+
</Stack>
|
|
24
|
+
</Stack>
|
|
25
25
|
</template>
|
|
26
26
|
|
|
27
27
|
<script setup lang="ts">
|
|
28
28
|
import { computed } from "vue";
|
|
29
29
|
import type { ControlInteraction } from "../../components/control.ts";
|
|
30
|
-
import type { SwitchState } from "../../components/switch
|
|
30
|
+
import type { SwitchProps, SwitchState } from "../../components/switch";
|
|
31
|
+
import Stack from "./stack.vue";
|
|
31
32
|
|
|
32
|
-
const {
|
|
33
|
-
modelValue = false,
|
|
34
|
-
label = undefined,
|
|
35
|
-
description = undefined,
|
|
36
|
-
name = undefined,
|
|
37
|
-
disabled = false,
|
|
38
|
-
} = defineProps<{
|
|
39
|
-
modelValue?: boolean;
|
|
40
|
-
label?: string;
|
|
41
|
-
description?: string;
|
|
42
|
-
name?: string;
|
|
43
|
-
disabled?: boolean;
|
|
44
|
-
}>();
|
|
33
|
+
const { modelValue = false, label, description, name, disabled = false } = defineProps<SwitchProps>();
|
|
45
34
|
|
|
46
35
|
defineEmits<{
|
|
47
36
|
"update:modelValue": [value: boolean];
|
|
@@ -58,7 +47,7 @@ const interactionVariants: Record<ControlInteraction, string> = {
|
|
|
58
47
|
|
|
59
48
|
const thumbVariants: Record<SwitchState, string> = {
|
|
60
49
|
checked: "translate-x-5 bg-primary-foreground",
|
|
61
|
-
unchecked: "translate-x-
|
|
50
|
+
unchecked: "translate-x-1 bg-white",
|
|
62
51
|
};
|
|
63
52
|
|
|
64
53
|
const variants: Record<SwitchState, string> = {
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<Stack>
|
|
3
3
|
<span v-if="label" class="text-sm font-medium">
|
|
4
4
|
{{ label }}<span v-if="required" aria-hidden="true"> *</span>
|
|
5
5
|
</span>
|
|
6
6
|
|
|
7
|
-
<
|
|
8
|
-
|
|
7
|
+
<Stack
|
|
8
|
+
align="center"
|
|
9
|
+
justify="center"
|
|
10
|
+
gap="3"
|
|
11
|
+
class="relative min-h-32 rounded-ui border border-input border-dashed p-5 text-center transition-colors"
|
|
9
12
|
:class="[variants[state], interactionVariants[interaction]]"
|
|
10
13
|
@dragenter.prevent="isOver = true"
|
|
11
14
|
@dragover.prevent="isOver = true"
|
|
@@ -35,35 +38,20 @@
|
|
|
35
38
|
:disabled="disabled"
|
|
36
39
|
@change="change"
|
|
37
40
|
/>
|
|
38
|
-
</
|
|
41
|
+
</Stack>
|
|
39
42
|
|
|
40
43
|
<span v-if="description" class="text-sm opacity-60">{{ description }}</span>
|
|
41
|
-
</
|
|
44
|
+
</Stack>
|
|
42
45
|
</template>
|
|
43
46
|
|
|
44
47
|
<script setup lang="ts">
|
|
45
48
|
import { computed, ref, useTemplateRef } from "vue";
|
|
46
49
|
import type { ControlInteraction } from "../../components/control.ts";
|
|
47
|
-
import type { UploadState } from "../../components/upload
|
|
50
|
+
import type { UploadProps, UploadState } from "../../components/upload";
|
|
48
51
|
import Button from "./button.vue";
|
|
52
|
+
import Stack from "./stack.vue";
|
|
49
53
|
|
|
50
|
-
const {
|
|
51
|
-
modelValue = undefined,
|
|
52
|
-
label = undefined,
|
|
53
|
-
description = undefined,
|
|
54
|
-
accept = undefined,
|
|
55
|
-
name = undefined,
|
|
56
|
-
required = false,
|
|
57
|
-
disabled = false,
|
|
58
|
-
} = defineProps<{
|
|
59
|
-
modelValue?: Blob | File;
|
|
60
|
-
label?: string;
|
|
61
|
-
description?: string;
|
|
62
|
-
accept?: string;
|
|
63
|
-
name?: string;
|
|
64
|
-
required?: boolean;
|
|
65
|
-
disabled?: boolean;
|
|
66
|
-
}>();
|
|
54
|
+
const { modelValue, label, description, accept, name, required = false, disabled = false } = defineProps<UploadProps>();
|
|
67
55
|
|
|
68
56
|
const emit = defineEmits<{
|
|
69
57
|
"update:modelValue": [value?: Blob | File];
|