@revojs/ui 0.2.9 → 0.2.11
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/container.d.ts +2 -0
- package/dist/components/icon.d.ts +1 -0
- 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/index.js +10 -10
- package/dist/style.css +7 -0
- package/dist/vue/components/breadcrumb.vue +12 -4
- package/dist/vue/components/button.vue +8 -3
- package/dist/vue/components/card.vue +18 -15
- package/dist/vue/components/checkbox.vue +6 -5
- package/dist/vue/components/collection.vue +13 -5
- package/dist/vue/components/container.vue +4 -3
- package/dist/vue/components/counter.vue +4 -3
- package/dist/vue/components/dialog.vue +7 -1
- package/dist/vue/components/field.vue +4 -3
- package/dist/vue/components/icon.vue +6 -2
- package/dist/vue/components/input.vue +11 -5
- package/dist/vue/components/popover.vue +54 -56
- package/dist/vue/components/progress.vue +7 -6
- package/dist/vue/components/radio.vue +7 -6
- package/dist/vue/components/range.vue +7 -6
- package/dist/vue/components/rating.vue +6 -5
- package/dist/vue/components/select.vue +78 -95
- package/dist/vue/components/sheet.vue +10 -4
- package/dist/vue/components/stack.vue +70 -0
- package/dist/vue/components/switch.vue +8 -7
- package/dist/vue/components/upload.vue +9 -5
- 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 +1 -1
|
@@ -1,29 +1,30 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
2
|
+
<Stack>
|
|
3
|
+
<Stack v-if="label" direction="horizontal" align="center" justify="between" gap="1" class="text-sm">
|
|
4
4
|
<span class="font-medium">{{ label }}</span>
|
|
5
5
|
|
|
6
6
|
<span class="tabular-nums opacity-60">{{ Math.round(percent) }}%</span>
|
|
7
|
-
</
|
|
7
|
+
</Stack>
|
|
8
8
|
|
|
9
9
|
<progress class="sr-only" :value="value" :max="max">{{ percent }}%</progress>
|
|
10
10
|
|
|
11
11
|
<div
|
|
12
|
-
class="h-2 w-full overflow-hidden rounded-
|
|
12
|
+
class="h-2 w-full overflow-hidden rounded-ui bg-input"
|
|
13
13
|
role="progressbar"
|
|
14
14
|
:aria-label="label"
|
|
15
15
|
:aria-valuenow="value"
|
|
16
16
|
aria-valuemin="0"
|
|
17
17
|
:aria-valuemax="max"
|
|
18
18
|
>
|
|
19
|
-
<div class="h-full rounded-
|
|
19
|
+
<div class="h-full rounded-ui bg-primary transition-[width]" :style="{ width: `${percent}%` }" />
|
|
20
20
|
</div>
|
|
21
|
-
</
|
|
21
|
+
</Stack>
|
|
22
22
|
</template>
|
|
23
23
|
|
|
24
24
|
<script setup lang="ts">
|
|
25
25
|
import { computed } from "vue";
|
|
26
26
|
import type { ProgressProps } from "../../components/progress";
|
|
27
|
+
import Stack from "./stack.vue";
|
|
27
28
|
|
|
28
29
|
const { value = 0, max = 100, label } = defineProps<ProgressProps>();
|
|
29
30
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<label
|
|
2
|
+
<Stack is="label" direction="horizontal" align="start" gap="3" :class="interactionVariants[interaction]">
|
|
3
3
|
<input
|
|
4
4
|
class="peer sr-only"
|
|
5
5
|
type="radio"
|
|
@@ -13,26 +13,27 @@
|
|
|
13
13
|
|
|
14
14
|
<span
|
|
15
15
|
aria-hidden="true"
|
|
16
|
-
class="mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-
|
|
16
|
+
class="mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-ui border transition-colors peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-ring"
|
|
17
17
|
:class="modelValue === value ? 'border-primary bg-background' : 'border-input bg-background'"
|
|
18
18
|
>
|
|
19
|
-
<span v-if="modelValue === value" class="size-2 rounded-
|
|
19
|
+
<span v-if="modelValue === value" class="size-2 rounded-ui bg-primary" />
|
|
20
20
|
</span>
|
|
21
21
|
|
|
22
|
-
<
|
|
22
|
+
<Stack is="span" gap="0">
|
|
23
23
|
<span class="text-sm font-medium">
|
|
24
24
|
<slot> {{ label }}</slot>
|
|
25
25
|
</span>
|
|
26
26
|
|
|
27
27
|
<span v-if="description" class="text-sm opacity-60">{{ description }}</span>
|
|
28
|
-
</
|
|
29
|
-
</
|
|
28
|
+
</Stack>
|
|
29
|
+
</Stack>
|
|
30
30
|
</template>
|
|
31
31
|
|
|
32
32
|
<script setup lang="ts">
|
|
33
33
|
import { computed } from "vue";
|
|
34
34
|
import type { ControlInteraction } from "../../components/control.ts";
|
|
35
35
|
import type { RadioProps, RadioValue } from "../../components/radio";
|
|
36
|
+
import Stack from "./stack.vue";
|
|
36
37
|
|
|
37
38
|
const { modelValue, value, label, description, name, required = false, disabled = false } = defineProps<RadioProps>();
|
|
38
39
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<
|
|
2
|
+
<Stack :class="interactionVariants[interaction]">
|
|
3
|
+
<Stack v-if="label" direction="horizontal" align="center" justify="between" gap="1" class="text-sm">
|
|
4
4
|
<span class="font-medium"> {{ label }} </span>
|
|
5
5
|
|
|
6
6
|
<output class="tabular-nums opacity-60">
|
|
7
7
|
{{ modelValue }}
|
|
8
8
|
</output>
|
|
9
|
-
</
|
|
9
|
+
</Stack>
|
|
10
10
|
|
|
11
11
|
<div
|
|
12
12
|
ref="track"
|
|
13
|
-
class="relative h-2 w-full touch-none rounded-
|
|
13
|
+
class="relative h-2 w-full touch-none rounded-ui bg-input outline-ring focus-visible:outline-2 focus-visible:outline-offset-2"
|
|
14
14
|
:class="disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'"
|
|
15
15
|
role="slider"
|
|
16
16
|
:tabindex="disabled ? -1 : 0"
|
|
@@ -28,17 +28,18 @@
|
|
|
28
28
|
<div class="absolute inset-y-0 left-0 rounded-full bg-primary" :style="{ width: `${percent}%` }" />
|
|
29
29
|
|
|
30
30
|
<span
|
|
31
|
-
class="absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-
|
|
31
|
+
class="absolute top-1/2 size-4 -translate-x-1/2 -translate-y-1/2 rounded-ui border-2 border-primary bg-background shadow"
|
|
32
32
|
:style="{ left: `${percent}%` }"
|
|
33
33
|
/>
|
|
34
34
|
</div>
|
|
35
|
-
</
|
|
35
|
+
</Stack>
|
|
36
36
|
</template>
|
|
37
37
|
|
|
38
38
|
<script setup lang="ts">
|
|
39
39
|
import { computed, useTemplateRef } from "vue";
|
|
40
40
|
import type { ControlInteraction } from "../../components/control.ts";
|
|
41
41
|
import type { RangeProps } from "../../components/range";
|
|
42
|
+
import Stack from "./stack.vue";
|
|
42
43
|
|
|
43
44
|
const { modelValue = 0, label, min = 0, max = 100, step = 1, disabled = false } = defineProps<RangeProps>();
|
|
44
45
|
|
|
@@ -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"
|
|
@@ -18,14 +18,15 @@
|
|
|
18
18
|
>
|
|
19
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
26
|
import StarIcon from "~icons/hugeicons/star";
|
|
27
27
|
import Button from "./button.vue";
|
|
28
28
|
import Icon from "./icon.vue";
|
|
29
|
+
import Stack from "./stack.vue";
|
|
29
30
|
import type { RatingProps, RatingState } from "../../components/rating";
|
|
30
31
|
|
|
31
32
|
const { modelValue = 0, max = 5, label, readonly = false, disabled = false } = defineProps<RatingProps>();
|
|
@@ -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 :is="ArrowDownIcon" />
|
|
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,30 +64,32 @@
|
|
|
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" :is="TickIcon" />
|
|
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
85
|
import ArrowDownIcon from "~icons/hugeicons/arrow-down-01";
|
|
85
86
|
import TickIcon from "~icons/hugeicons/tick-02";
|
|
86
|
-
import { computed,
|
|
87
|
+
import { computed, onUnmounted, ref, useId, useTemplateRef, watch } from "vue";
|
|
87
88
|
import type { SelectOption, SelectProps, SelectValue } from "../../components/select";
|
|
88
89
|
import Button from "./button.vue";
|
|
89
90
|
import Icon from "./icon.vue";
|
|
91
|
+
import Stack from "./stack.vue";
|
|
92
|
+
import { useFocusTrap } from "../utilities/use-focus-trap";
|
|
90
93
|
|
|
91
94
|
const {
|
|
92
95
|
modelValue,
|
|
@@ -107,40 +110,32 @@ const open = ref(false);
|
|
|
107
110
|
|
|
108
111
|
const container = useTemplateRef("container");
|
|
109
112
|
|
|
110
|
-
|
|
113
|
+
useFocusTrap(container, open);
|
|
111
114
|
|
|
112
115
|
const search = useTemplateRef("search");
|
|
113
116
|
|
|
114
|
-
const dropdown = useTemplateRef("dropdown");
|
|
115
|
-
|
|
116
117
|
const listboxId = `select-${useId()}`;
|
|
117
118
|
|
|
118
|
-
const
|
|
119
|
+
const iconSize = 20;
|
|
119
120
|
|
|
120
121
|
const selected = computed(() => options.find((option) => option.value === modelValue));
|
|
121
122
|
|
|
122
123
|
const query = ref(selected.value?.label ?? "");
|
|
123
124
|
|
|
124
|
-
const panel = ref<Record<string, string>>({});
|
|
125
|
-
|
|
126
125
|
const filtered = computed(() => {
|
|
127
126
|
const term = query.value.trim().toLowerCase();
|
|
128
127
|
|
|
129
128
|
return term === "" ? options : options.filter((option) => option.label.toLowerCase().includes(term));
|
|
130
129
|
});
|
|
131
130
|
|
|
132
|
-
const
|
|
133
|
-
if (popoverSupported.value) {
|
|
134
|
-
return undefined;
|
|
135
|
-
}
|
|
131
|
+
const highlighted = ref(-1);
|
|
136
132
|
|
|
137
|
-
|
|
138
|
-
}
|
|
133
|
+
const activeId = computed(() =>
|
|
134
|
+
open.value && highlighted.value >= 0 ? `${listboxId}-${highlighted.value}` : undefined,
|
|
135
|
+
);
|
|
139
136
|
|
|
140
137
|
function handlePointerDown(event: PointerEvent) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (!container.value?.contains(target) && !dropdown.value?.contains(target)) {
|
|
138
|
+
if (!container.value?.contains(event.target as Node)) {
|
|
144
139
|
open.value = false;
|
|
145
140
|
}
|
|
146
141
|
}
|
|
@@ -152,65 +147,71 @@ function select(option: SelectOption) {
|
|
|
152
147
|
}
|
|
153
148
|
}
|
|
154
149
|
|
|
155
|
-
function
|
|
156
|
-
const
|
|
157
|
-
const list = dropdown.value;
|
|
150
|
+
function highlight(step: 1 | -1) {
|
|
151
|
+
const count = filtered.value.length;
|
|
158
152
|
|
|
159
|
-
if (
|
|
153
|
+
if (count === 0) {
|
|
160
154
|
return;
|
|
161
155
|
}
|
|
162
156
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
top: `${top}px`,
|
|
177
|
-
width: `${listWidth}px`,
|
|
178
|
-
};
|
|
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
|
+
}
|
|
179
170
|
}
|
|
180
171
|
|
|
181
|
-
|
|
172
|
+
function stepHighlight(step: 1 | -1) {
|
|
182
173
|
if (!open.value) {
|
|
183
|
-
|
|
174
|
+
open.value = true;
|
|
175
|
+
|
|
176
|
+
return;
|
|
184
177
|
}
|
|
185
|
-
});
|
|
186
178
|
|
|
187
|
-
|
|
188
|
-
|
|
179
|
+
highlight(step);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function confirm() {
|
|
183
|
+
if (!open.value) {
|
|
184
|
+
open.value = true;
|
|
189
185
|
|
|
190
|
-
if (!list) {
|
|
191
186
|
return;
|
|
192
187
|
}
|
|
193
188
|
|
|
194
|
-
|
|
195
|
-
query.value = "";
|
|
196
|
-
|
|
197
|
-
document.addEventListener("pointerdown", handlePointerDown);
|
|
189
|
+
const option = filtered.value[highlighted.value] ?? filtered.value[0];
|
|
198
190
|
|
|
199
|
-
|
|
191
|
+
if (option) {
|
|
192
|
+
select(option);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
200
195
|
|
|
201
|
-
|
|
196
|
+
function input() {
|
|
197
|
+
open.value = true;
|
|
202
198
|
|
|
203
|
-
|
|
199
|
+
highlighted.value = -1;
|
|
200
|
+
}
|
|
204
201
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
202
|
+
watch(selected, (value) => {
|
|
203
|
+
if (!open.value) {
|
|
204
|
+
query.value = value?.label ?? "";
|
|
205
|
+
}
|
|
206
|
+
});
|
|
208
207
|
|
|
209
|
-
|
|
208
|
+
watch(open, (value) => {
|
|
209
|
+
if (value) {
|
|
210
|
+
query.value = "";
|
|
210
211
|
|
|
211
|
-
|
|
212
|
+
highlighted.value = filtered.value.findIndex((option) => option.value === modelValue);
|
|
212
213
|
|
|
213
|
-
|
|
214
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
214
215
|
|
|
215
216
|
if (document.activeElement !== search.value) {
|
|
216
217
|
search.value?.focus();
|
|
@@ -218,31 +219,13 @@ watch(open, async (value) => {
|
|
|
218
219
|
} else {
|
|
219
220
|
document.removeEventListener("pointerdown", handlePointerDown);
|
|
220
221
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
window.removeEventListener("scroll", updatePanel, true);
|
|
224
|
-
|
|
225
|
-
window.visualViewport?.removeEventListener("resize", updatePanel);
|
|
226
|
-
|
|
227
|
-
if (popoverSupported.value && list.matches(":popover-open")) {
|
|
228
|
-
list.hidePopover();
|
|
229
|
-
}
|
|
222
|
+
highlighted.value = -1;
|
|
230
223
|
|
|
231
224
|
query.value = selected.value?.label ?? "";
|
|
232
225
|
}
|
|
233
226
|
});
|
|
234
227
|
|
|
235
|
-
onMounted(() => {
|
|
236
|
-
popoverSupported.value = typeof dropdown.value?.showPopover === "function";
|
|
237
|
-
});
|
|
238
|
-
|
|
239
228
|
onUnmounted(() => {
|
|
240
229
|
document.removeEventListener("pointerdown", handlePointerDown);
|
|
241
|
-
|
|
242
|
-
window.removeEventListener("resize", updatePanel);
|
|
243
|
-
|
|
244
|
-
window.removeEventListener("scroll", updatePanel, true);
|
|
245
|
-
|
|
246
|
-
window.visualViewport?.removeEventListener("resize", updatePanel);
|
|
247
230
|
});
|
|
248
231
|
</script>
|
|
@@ -34,6 +34,8 @@ import { useTemplateRef, watchEffect } from "vue";
|
|
|
34
34
|
import type { SheetProps, SheetSide } from "../../components/sheet";
|
|
35
35
|
import Button from "./button.vue";
|
|
36
36
|
import Card from "./card.vue";
|
|
37
|
+
import { useFocusTrap } from "../utilities/use-focus-trap";
|
|
38
|
+
import { useScrollLock } from "../utilities/use-scroll-lock";
|
|
37
39
|
|
|
38
40
|
const { modelValue = false, side = "right", title, description, closeLabel = "Close" } = defineProps<SheetProps>();
|
|
39
41
|
|
|
@@ -43,6 +45,10 @@ defineEmits<{
|
|
|
43
45
|
|
|
44
46
|
const sheet = useTemplateRef("sheet");
|
|
45
47
|
|
|
48
|
+
useFocusTrap(sheet, () => modelValue);
|
|
49
|
+
|
|
50
|
+
useScrollLock(() => modelValue);
|
|
51
|
+
|
|
46
52
|
const positions: Record<SheetSide, string> = {
|
|
47
53
|
top: "bottom-auto left-0 right-0 top-0 h-auto w-full",
|
|
48
54
|
right: "bottom-0 left-auto right-0 top-0 h-full max-h-full w-[min(28rem,90vw)]",
|
|
@@ -51,10 +57,10 @@ const positions: Record<SheetSide, string> = {
|
|
|
51
57
|
};
|
|
52
58
|
|
|
53
59
|
const cardClasses: Record<SheetSide, string> = {
|
|
54
|
-
top: "max-h-[85vh] w-full shadow-
|
|
55
|
-
right: "h-full w-full shadow-
|
|
56
|
-
bottom: "max-h-[85vh] w-full shadow-
|
|
57
|
-
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",
|
|
58
64
|
};
|
|
59
65
|
|
|
60
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,23 +11,24 @@
|
|
|
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
30
|
import type { SwitchProps, SwitchState } from "../../components/switch";
|
|
31
|
+
import Stack from "./stack.vue";
|
|
31
32
|
|
|
32
33
|
const { modelValue = false, label, description, name, disabled = false } = defineProps<SwitchProps>();
|
|
33
34
|
|
|
@@ -46,7 +47,7 @@ const interactionVariants: Record<ControlInteraction, string> = {
|
|
|
46
47
|
|
|
47
48
|
const thumbVariants: Record<SwitchState, string> = {
|
|
48
49
|
checked: "translate-x-5 bg-primary-foreground",
|
|
49
|
-
unchecked: "translate-x-
|
|
50
|
+
unchecked: "translate-x-1 bg-white",
|
|
50
51
|
};
|
|
51
52
|
|
|
52
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,10 +38,10 @@
|
|
|
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">
|
|
@@ -46,6 +49,7 @@ import { computed, ref, useTemplateRef } from "vue";
|
|
|
46
49
|
import type { ControlInteraction } from "../../components/control.ts";
|
|
47
50
|
import type { UploadProps, UploadState } from "../../components/upload";
|
|
48
51
|
import Button from "./button.vue";
|
|
52
|
+
import Stack from "./stack.vue";
|
|
49
53
|
|
|
50
54
|
const { modelValue, label, description, accept, name, required = false, disabled = false } = defineProps<UploadProps>();
|
|
51
55
|
|