@revojs/ui 0.2.11 → 0.2.13
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 +1 -0
- package/dist/components/card.d.ts +8 -2
- package/dist/components/collection.d.ts +3 -0
- package/dist/components/container.d.ts +2 -3
- package/dist/components/dialog.d.ts +3 -3
- package/dist/components/entry.d.ts +6 -0
- package/dist/components/entry.js +1 -0
- package/dist/components/field.d.ts +4 -0
- package/dist/components/input.d.ts +3 -0
- package/dist/components/notification.d.ts +9 -0
- package/dist/components/notification.js +1 -0
- package/dist/components/pagination.d.ts +8 -0
- package/dist/components/pagination.js +1 -0
- package/dist/components/popover.d.ts +9 -1
- package/dist/components/select.d.ts +8 -5
- package/dist/components/sheet.d.ts +3 -3
- package/dist/components/shell.d.ts +6 -0
- package/dist/components/shell.js +1 -0
- package/dist/components/textarea.d.ts +13 -0
- package/dist/components/textarea.js +1 -0
- package/dist/components/upload.d.ts +2 -0
- package/dist/index.d.ts +5 -0
- package/dist/module/index.d.ts +16 -0
- package/dist/module/index.js +32 -0
- package/dist/style.css +16 -0
- package/dist/vue/components/breadcrumb.vue +6 -1
- package/dist/vue/components/button.vue +34 -13
- package/dist/vue/components/card.vue +27 -14
- package/dist/vue/components/checkbox.vue +18 -4
- package/dist/vue/components/collection.vue +22 -7
- package/dist/vue/components/container.vue +11 -2
- package/dist/vue/components/counter.vue +7 -4
- package/dist/vue/components/dialog.vue +53 -36
- package/dist/vue/components/entry.vue +52 -0
- package/dist/vue/components/field.vue +44 -9
- package/dist/vue/components/input.vue +33 -6
- package/dist/vue/components/link.vue +1 -1
- package/dist/vue/components/navigate.vue +1 -0
- package/dist/vue/components/notification.vue +58 -0
- package/dist/vue/components/pagination.vue +24 -0
- package/dist/vue/components/popover.vue +46 -18
- package/dist/vue/components/progress.vue +2 -2
- package/dist/vue/components/radio.vue +19 -5
- package/dist/vue/components/range.vue +1 -1
- package/dist/vue/components/rating.vue +2 -2
- package/dist/vue/components/select.vue +116 -35
- package/dist/vue/components/separator.vue +2 -2
- package/dist/vue/components/sheet.vue +56 -22
- package/dist/vue/components/shell.vue +38 -0
- package/dist/vue/components/switch.vue +21 -7
- package/dist/vue/components/textarea.vue +49 -0
- package/dist/vue/components/upload.vue +74 -23
- package/dist/vue/index.d.ts +5 -0
- package/dist/vue/index.js +6 -1
- package/dist/vue/utilities/use-control-attributes.d.ts +11 -0
- package/dist/vue/utilities/use-control-attributes.js +14 -0
- package/dist/vue/utilities/use-modal-dialog.d.ts +9 -0
- package/dist/vue/utilities/use-modal-dialog.js +48 -0
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<Stack>
|
|
3
|
-
<span v-if="label" class="text-sm font-
|
|
3
|
+
<span v-if="label" class="text-sm font-normal">{{ label }}</span>
|
|
4
4
|
|
|
5
5
|
<Stack direction="horizontal" align="center" gap="0" role="radiogroup" :aria-label="label">
|
|
6
6
|
<Button
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
:disabled="readonly || disabled"
|
|
17
17
|
@click="$emit('update:modelValue', value)"
|
|
18
18
|
>
|
|
19
|
-
<Icon :is="StarIcon" />
|
|
19
|
+
<Icon :is="StarIcon" :class="{ '[&_path]:fill-current': value <= modelValue }" />
|
|
20
20
|
</Button>
|
|
21
21
|
</Stack>
|
|
22
22
|
</Stack>
|
|
@@ -1,56 +1,99 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Stack>
|
|
3
|
-
<
|
|
2
|
+
<Stack v-bind="rootAttributes">
|
|
3
|
+
<label v-if="label" :for="controlAttributes.id ?? controlId" class="text-sm font-normal">
|
|
4
4
|
{{ label }}
|
|
5
|
-
</
|
|
6
|
-
|
|
7
|
-
<input v-if="name" type="hidden" :name="name" :value="modelValue" :
|
|
5
|
+
</label>
|
|
6
|
+
|
|
7
|
+
<input v-if="name && searchable" type="hidden" :name="name" :value="modelValue" :disabled="disabled" />
|
|
8
|
+
|
|
9
|
+
<select
|
|
10
|
+
v-if="!searchable"
|
|
11
|
+
v-bind="controlAttributes"
|
|
12
|
+
ref="nativeControl"
|
|
13
|
+
:id="controlAttributes.id ?? controlId"
|
|
14
|
+
:value="modelValue ?? ''"
|
|
15
|
+
:name="name"
|
|
16
|
+
:required="required"
|
|
17
|
+
:disabled="disabled"
|
|
18
|
+
:aria-invalid="invalid ?? controlAttributes['aria-invalid']"
|
|
19
|
+
class="w-full rounded-ui bg-secondary p-3 text-sm ring-1 ring-input ring-inset outline-ring focus-visible:outline-2 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-2 aria-invalid:ring-destructive"
|
|
20
|
+
@change="
|
|
21
|
+
const option = options.find(
|
|
22
|
+
(option) => String(option.value) === ($event.currentTarget as HTMLSelectElement).value,
|
|
23
|
+
);
|
|
24
|
+
if (option) {
|
|
25
|
+
select(option);
|
|
26
|
+
}
|
|
27
|
+
"
|
|
28
|
+
>
|
|
29
|
+
<option v-if="modelValue === undefined" value="" disabled>{{ placeholder ?? "Select" }}</option>
|
|
30
|
+
|
|
31
|
+
<option
|
|
32
|
+
v-for="option in options"
|
|
33
|
+
:key="option.value"
|
|
34
|
+
:value="option.value"
|
|
35
|
+
:selected="option.value === modelValue"
|
|
36
|
+
:disabled="option.disabled"
|
|
37
|
+
>
|
|
38
|
+
{{ option.label }}
|
|
39
|
+
</option>
|
|
40
|
+
</select>
|
|
8
41
|
|
|
9
|
-
<div ref="container" class="relative">
|
|
42
|
+
<div v-else ref="container" class="relative">
|
|
10
43
|
<Stack
|
|
11
44
|
direction="horizontal"
|
|
12
45
|
align="center"
|
|
13
|
-
class="
|
|
14
|
-
:class="
|
|
15
|
-
|
|
46
|
+
class="w-full cursor-text rounded-ui bg-secondary p-3 text-sm font-semibold ring-1 ring-input ring-inset focus-within:ring-2 focus-within:ring-ring"
|
|
47
|
+
:class="[
|
|
48
|
+
disabled ? 'cursor-not-allowed opacity-50' : '',
|
|
49
|
+
(invalid ??
|
|
50
|
+
(controlAttributes['aria-invalid'] === true || controlAttributes['aria-invalid'] === 'true'))
|
|
51
|
+
? 'ring-2 ring-destructive'
|
|
52
|
+
: '',
|
|
53
|
+
]"
|
|
54
|
+
@click="
|
|
55
|
+
if (!disabled) {
|
|
56
|
+
open = true;
|
|
57
|
+
search?.focus();
|
|
58
|
+
}
|
|
59
|
+
"
|
|
16
60
|
>
|
|
17
61
|
<input
|
|
62
|
+
v-bind="controlAttributes"
|
|
63
|
+
:id="controlAttributes.id ?? controlId"
|
|
18
64
|
ref="search"
|
|
19
65
|
v-model="query"
|
|
20
66
|
role="combobox"
|
|
21
|
-
class="w-full min-w-0 bg-transparent
|
|
67
|
+
class="w-full min-w-0 bg-transparent px-1 outline-none placeholder:opacity-50"
|
|
22
68
|
:placeholder="placeholder ?? 'Select'"
|
|
23
69
|
:aria-expanded="open"
|
|
24
70
|
:aria-controls="listboxId"
|
|
25
71
|
:aria-activedescendant="activeId"
|
|
26
72
|
aria-autocomplete="list"
|
|
27
73
|
:disabled="disabled"
|
|
28
|
-
|
|
74
|
+
:required="required"
|
|
75
|
+
:aria-invalid="invalid ?? controlAttributes['aria-invalid']"
|
|
76
|
+
@invalid="open = true"
|
|
29
77
|
@input="input"
|
|
30
78
|
@keydown.enter.prevent="confirm"
|
|
31
|
-
@keydown.escape="
|
|
79
|
+
@keydown.escape="escape"
|
|
80
|
+
@keydown.tab="open = false"
|
|
32
81
|
@keydown.arrow-down.prevent="stepHighlight(1)"
|
|
33
82
|
@keydown.arrow-up.prevent="stepHighlight(-1)"
|
|
34
83
|
/>
|
|
35
84
|
|
|
36
|
-
<button
|
|
37
|
-
|
|
38
|
-
tabindex="-1"
|
|
39
|
-
class="shrink-0 opacity-60 transition-transform"
|
|
40
|
-
:class="open ? 'rotate-180' : ''"
|
|
41
|
-
:disabled="disabled"
|
|
42
|
-
@click.stop="open = !open"
|
|
43
|
-
>
|
|
44
|
-
<Icon :is="ArrowDownIcon" :size="iconSize" />
|
|
85
|
+
<button type="button" tabindex="-1" class="shrink-0" :disabled="disabled" @click.stop="open = !open">
|
|
86
|
+
<Icon :is="open ? ArrowUpIcon : ArrowDownIcon" :size="iconSize" />
|
|
45
87
|
</button>
|
|
46
88
|
</Stack>
|
|
47
89
|
|
|
48
|
-
<
|
|
90
|
+
<Stack
|
|
49
91
|
v-if="open"
|
|
50
92
|
:id="listboxId"
|
|
51
93
|
role="listbox"
|
|
52
94
|
:aria-label="label"
|
|
53
|
-
|
|
95
|
+
padding="1"
|
|
96
|
+
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 text-popover-foreground shadow-xl [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
54
97
|
>
|
|
55
98
|
<Button
|
|
56
99
|
v-for="(option, index) in filtered"
|
|
@@ -60,13 +103,14 @@
|
|
|
60
103
|
variant="ghost"
|
|
61
104
|
layout="left"
|
|
62
105
|
:disabled="option.disabled"
|
|
106
|
+
tabindex="-1"
|
|
63
107
|
role="option"
|
|
64
108
|
:aria-selected="option.value === modelValue"
|
|
65
109
|
@click="select(option)"
|
|
66
110
|
>
|
|
67
111
|
<Stack direction="horizontal" align="center" justify="between" gap="1" class="w-full text-left">
|
|
68
112
|
<slot name="option" :option="option" :selected="option.value === modelValue">
|
|
69
|
-
{{ option.label }}
|
|
113
|
+
<span class="min-w-0 flex-1 truncate px-1">{{ option.label }}</span>
|
|
70
114
|
</slot>
|
|
71
115
|
|
|
72
116
|
<Icon v-if="option.value === modelValue" :is="TickIcon" :size="iconSize" />
|
|
@@ -76,20 +120,23 @@
|
|
|
76
120
|
<p v-if="filtered.length === 0" class="px-1 py-5 text-center text-sm opacity-60">
|
|
77
121
|
{{ emptyLabel }}
|
|
78
122
|
</p>
|
|
79
|
-
</
|
|
123
|
+
</Stack>
|
|
80
124
|
</div>
|
|
81
125
|
</Stack>
|
|
82
126
|
</template>
|
|
83
127
|
|
|
84
|
-
<script setup lang="ts">
|
|
128
|
+
<script setup lang="ts" generic="Value extends SelectValue = SelectValue">
|
|
85
129
|
import ArrowDownIcon from "~icons/hugeicons/arrow-down-01";
|
|
130
|
+
import ArrowUpIcon from "~icons/hugeicons/arrow-up-01";
|
|
86
131
|
import TickIcon from "~icons/hugeicons/tick-02";
|
|
87
|
-
import { computed, onUnmounted, ref, useId, useTemplateRef, watch } from "vue";
|
|
132
|
+
import { computed, onUnmounted, ref, useId, useTemplateRef, watch, watchEffect } from "vue";
|
|
88
133
|
import type { SelectOption, SelectProps, SelectValue } from "../../components/select";
|
|
89
134
|
import Button from "./button.vue";
|
|
90
135
|
import Icon from "./icon.vue";
|
|
91
136
|
import Stack from "./stack.vue";
|
|
92
|
-
import {
|
|
137
|
+
import { useControlAttributes } from "../utilities/use-control-attributes";
|
|
138
|
+
|
|
139
|
+
defineOptions({ inheritAttrs: false });
|
|
93
140
|
|
|
94
141
|
const {
|
|
95
142
|
modelValue,
|
|
@@ -100,30 +147,41 @@ const {
|
|
|
100
147
|
name,
|
|
101
148
|
required = false,
|
|
102
149
|
disabled = false,
|
|
103
|
-
|
|
150
|
+
searchable = true,
|
|
151
|
+
invalid = undefined,
|
|
152
|
+
requiredLabel = "Please select an option",
|
|
153
|
+
} = defineProps<SelectProps<Value>>();
|
|
104
154
|
|
|
105
155
|
const emit = defineEmits<{
|
|
106
|
-
"update:modelValue": [value:
|
|
156
|
+
"update:modelValue": [value: Value];
|
|
107
157
|
}>();
|
|
108
158
|
|
|
109
159
|
const open = ref(false);
|
|
110
160
|
|
|
111
161
|
const container = useTemplateRef("container");
|
|
112
162
|
|
|
113
|
-
|
|
163
|
+
const { rootAttributes, controlAttributes } = useControlAttributes();
|
|
114
164
|
|
|
115
165
|
const search = useTemplateRef("search");
|
|
116
166
|
|
|
167
|
+
const nativeControl = useTemplateRef("nativeControl");
|
|
168
|
+
|
|
117
169
|
const listboxId = `select-${useId()}`;
|
|
118
170
|
|
|
171
|
+
const controlId = `${listboxId}-control`;
|
|
172
|
+
|
|
173
|
+
defineExpose({ focus: () => (search.value ?? nativeControl.value)?.focus() });
|
|
174
|
+
|
|
119
175
|
const iconSize = 20;
|
|
120
176
|
|
|
121
177
|
const selected = computed(() => options.find((option) => option.value === modelValue));
|
|
122
178
|
|
|
123
179
|
const query = ref(selected.value?.label ?? "");
|
|
124
180
|
|
|
181
|
+
const searching = ref(false);
|
|
182
|
+
|
|
125
183
|
const filtered = computed(() => {
|
|
126
|
-
const term = query.value.trim().toLowerCase();
|
|
184
|
+
const term = searching.value ? query.value.trim().toLowerCase() : "";
|
|
127
185
|
|
|
128
186
|
return term === "" ? options : options.filter((option) => option.label.toLowerCase().includes(term));
|
|
129
187
|
});
|
|
@@ -140,10 +198,19 @@ function handlePointerDown(event: PointerEvent) {
|
|
|
140
198
|
}
|
|
141
199
|
}
|
|
142
200
|
|
|
143
|
-
function
|
|
201
|
+
function escape(event: KeyboardEvent) {
|
|
202
|
+
if (open.value) {
|
|
203
|
+
event.preventDefault();
|
|
204
|
+
event.stopPropagation();
|
|
205
|
+
open.value = false;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function select(option: SelectOption<Value>) {
|
|
144
210
|
if (!option.disabled) {
|
|
145
211
|
emit("update:modelValue", option.value);
|
|
146
212
|
open.value = false;
|
|
213
|
+
search.value?.focus();
|
|
147
214
|
}
|
|
148
215
|
}
|
|
149
216
|
|
|
@@ -194,6 +261,12 @@ function confirm() {
|
|
|
194
261
|
}
|
|
195
262
|
|
|
196
263
|
function input() {
|
|
264
|
+
if (!searchable) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
searching.value = true;
|
|
269
|
+
|
|
197
270
|
open.value = true;
|
|
198
271
|
|
|
199
272
|
highlighted.value = -1;
|
|
@@ -205,10 +278,16 @@ watch(selected, (value) => {
|
|
|
205
278
|
}
|
|
206
279
|
});
|
|
207
280
|
|
|
281
|
+
watchEffect(() => {
|
|
282
|
+
search.value?.setCustomValidity(required && !disabled && !selected.value ? requiredLabel : "");
|
|
283
|
+
|
|
284
|
+
if (disabled) {
|
|
285
|
+
open.value = false;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
208
289
|
watch(open, (value) => {
|
|
209
290
|
if (value) {
|
|
210
|
-
query.value = "";
|
|
211
|
-
|
|
212
291
|
highlighted.value = filtered.value.findIndex((option) => option.value === modelValue);
|
|
213
292
|
|
|
214
293
|
document.addEventListener("pointerdown", handlePointerDown);
|
|
@@ -221,6 +300,8 @@ watch(open, (value) => {
|
|
|
221
300
|
|
|
222
301
|
highlighted.value = -1;
|
|
223
302
|
|
|
303
|
+
searching.value = false;
|
|
304
|
+
|
|
224
305
|
query.value = selected.value?.label ?? "";
|
|
225
306
|
}
|
|
226
307
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div
|
|
3
|
-
class="shrink-0 bg-border"
|
|
3
|
+
class="shrink-0 self-center bg-border"
|
|
4
4
|
:class="variants[selected]"
|
|
5
5
|
:role="decorative ? 'none' : 'separator'"
|
|
6
6
|
:aria-orientation="decorative ? undefined : selected"
|
|
@@ -17,6 +17,6 @@ const selected = computed(() => variant ?? orientation);
|
|
|
17
17
|
|
|
18
18
|
const variants: Record<SeparatorOrientation, string> = {
|
|
19
19
|
horizontal: "h-px w-full",
|
|
20
|
-
vertical: "h-
|
|
20
|
+
vertical: "h-5 w-px",
|
|
21
21
|
};
|
|
22
22
|
</script>
|
|
@@ -1,25 +1,47 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<dialog
|
|
3
3
|
ref="sheet"
|
|
4
|
-
class="m-0 max-w-none border-0 bg-transparent p-0 backdrop:bg-black/50"
|
|
4
|
+
class="m-0 max-w-none border-0 bg-transparent p-0 backdrop:bg-black/50 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
5
5
|
:class="positions[side]"
|
|
6
|
-
|
|
7
|
-
@
|
|
6
|
+
:aria-labelledby="label && !$slots.header ? (labelId ?? id) : ($attrs as HTMLAttributes)['aria-labelledby']"
|
|
7
|
+
@close="handleClose"
|
|
8
|
+
@cancel="dismiss"
|
|
9
|
+
@click="$event.target === $event.currentTarget && dismiss($event)"
|
|
8
10
|
>
|
|
9
|
-
<Card
|
|
11
|
+
<Card
|
|
12
|
+
:label="label"
|
|
13
|
+
:label-id="labelId ?? id"
|
|
14
|
+
:body-padding="bodyPadding"
|
|
15
|
+
:body-gap="bodyGap"
|
|
16
|
+
:class="cardClasses[side]"
|
|
17
|
+
>
|
|
18
|
+
<template v-if="$slots.header" #header>
|
|
19
|
+
<slot name="header" />
|
|
20
|
+
</template>
|
|
21
|
+
|
|
10
22
|
<template #actions>
|
|
23
|
+
<slot name="actions" />
|
|
24
|
+
|
|
11
25
|
<Button
|
|
12
26
|
size="icon"
|
|
13
27
|
variant="ghost"
|
|
14
28
|
:icon-prefix="CloseIcon"
|
|
15
29
|
:aria-label="closeLabel"
|
|
16
|
-
|
|
30
|
+
:disabled="!dismissible"
|
|
31
|
+
@click="dismiss"
|
|
17
32
|
/>
|
|
18
33
|
</template>
|
|
19
34
|
|
|
20
|
-
<
|
|
35
|
+
<Stack
|
|
36
|
+
:gap="bodyGap"
|
|
37
|
+
:direction="bodyDirection"
|
|
38
|
+
:align="bodyAlign"
|
|
39
|
+
:justify="bodyJustify"
|
|
40
|
+
:wrap="bodyWrap"
|
|
41
|
+
class="min-h-0 flex-1 overflow-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
42
|
+
>
|
|
21
43
|
<slot />
|
|
22
|
-
</
|
|
44
|
+
</Stack>
|
|
23
45
|
|
|
24
46
|
<template v-if="$slots.footer" #footer>
|
|
25
47
|
<slot name="footer" />
|
|
@@ -29,25 +51,45 @@
|
|
|
29
51
|
</template>
|
|
30
52
|
|
|
31
53
|
<script setup lang="ts">
|
|
54
|
+
import { useId, useTemplateRef, type HTMLAttributes } from "vue";
|
|
32
55
|
import CloseIcon from "~icons/hugeicons/multiplication-sign";
|
|
33
|
-
import { useTemplateRef, watchEffect } from "vue";
|
|
34
56
|
import type { SheetProps, SheetSide } from "../../components/sheet";
|
|
57
|
+
import { useModalDialog } from "../utilities/use-modal-dialog";
|
|
35
58
|
import Button from "./button.vue";
|
|
36
59
|
import Card from "./card.vue";
|
|
37
|
-
import
|
|
38
|
-
import { useScrollLock } from "../utilities/use-scroll-lock";
|
|
60
|
+
import Stack from "./stack.vue";
|
|
39
61
|
|
|
40
|
-
const {
|
|
62
|
+
const {
|
|
63
|
+
modelValue = undefined,
|
|
64
|
+
side = "right",
|
|
65
|
+
label,
|
|
66
|
+
labelId,
|
|
67
|
+
bodyPadding,
|
|
68
|
+
bodyGap = "5",
|
|
69
|
+
bodyDirection,
|
|
70
|
+
bodyAlign,
|
|
71
|
+
bodyJustify,
|
|
72
|
+
bodyWrap,
|
|
73
|
+
closeLabel = "Close",
|
|
74
|
+
dismissible = true,
|
|
75
|
+
} = defineProps<SheetProps>();
|
|
41
76
|
|
|
42
|
-
defineEmits<{
|
|
77
|
+
const emit = defineEmits<{
|
|
43
78
|
"update:modelValue": [value: boolean];
|
|
44
79
|
}>();
|
|
45
80
|
|
|
46
81
|
const sheet = useTemplateRef("sheet");
|
|
47
82
|
|
|
48
|
-
|
|
83
|
+
const id = `sheet-${useId()}`;
|
|
49
84
|
|
|
50
|
-
|
|
85
|
+
const { open, close, toggle, dismiss, handleClose } = useModalDialog(
|
|
86
|
+
sheet,
|
|
87
|
+
() => modelValue,
|
|
88
|
+
(value) => emit("update:modelValue", value),
|
|
89
|
+
() => dismissible,
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
defineExpose({ open, close, toggle });
|
|
51
93
|
|
|
52
94
|
const positions: Record<SheetSide, string> = {
|
|
53
95
|
top: "bottom-auto left-0 right-0 top-0 h-auto w-full",
|
|
@@ -62,12 +104,4 @@ const cardClasses: Record<SheetSide, string> = {
|
|
|
62
104
|
bottom: "max-h-[85vh] w-full border-b-0 border-l-0 border-r-0 shadow-sheet-bottom",
|
|
63
105
|
left: "h-full w-full border-b-0 border-l-0 border-t-0 shadow-sheet-left",
|
|
64
106
|
};
|
|
65
|
-
|
|
66
|
-
watchEffect(() => {
|
|
67
|
-
if (modelValue && !sheet.value?.open) {
|
|
68
|
-
sheet.value?.showModal();
|
|
69
|
-
} else if (!modelValue && sheet.value?.open) {
|
|
70
|
-
sheet.value.close();
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
107
|
</script>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<header class="z-10 shrink-0 border-b border-border bg-background" :class="sticky ? 'sticky top-0' : ''">
|
|
3
|
+
<Stack direction="horizontal" align="center" gap="1" padding="1">
|
|
4
|
+
<slot name="prefix" />
|
|
5
|
+
|
|
6
|
+
<h1 v-if="label || $slots.heading" class="min-w-0 flex-1 truncate p-3 text-sm font-semibold">
|
|
7
|
+
<slot name="heading">{{ label }}</slot>
|
|
8
|
+
</h1>
|
|
9
|
+
<template v-else-if="headingLabel">
|
|
10
|
+
<h1 class="sr-only">{{ headingLabel }}</h1>
|
|
11
|
+
|
|
12
|
+
<span aria-hidden="true" class="min-w-0 flex-1" />
|
|
13
|
+
</template>
|
|
14
|
+
<span v-else aria-hidden="true" class="min-w-0 flex-1" />
|
|
15
|
+
|
|
16
|
+
<slot name="actions"><slot /></slot>
|
|
17
|
+
</Stack>
|
|
18
|
+
|
|
19
|
+
<Stack
|
|
20
|
+
v-if="$slots.navigation"
|
|
21
|
+
is="nav"
|
|
22
|
+
:aria-label="navigationLabel"
|
|
23
|
+
direction="horizontal"
|
|
24
|
+
gap="1"
|
|
25
|
+
padding="1"
|
|
26
|
+
class="overflow-x-auto border-t border-border [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
27
|
+
>
|
|
28
|
+
<slot name="navigation" />
|
|
29
|
+
</Stack>
|
|
30
|
+
</header>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script setup lang="ts">
|
|
34
|
+
import type { ShellProps } from "../../components/shell";
|
|
35
|
+
import Stack from "./stack.vue";
|
|
36
|
+
|
|
37
|
+
const { label, headingLabel, sticky = true, navigationLabel = "Page navigation" } = defineProps<ShellProps>();
|
|
38
|
+
</script>
|
|
@@ -1,21 +1,35 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<Stack
|
|
2
|
+
<Stack
|
|
3
|
+
is="label"
|
|
4
|
+
direction="horizontal"
|
|
5
|
+
align="center"
|
|
6
|
+
gap="3"
|
|
7
|
+
class="group"
|
|
8
|
+
:class="interactionVariants[interaction]"
|
|
9
|
+
>
|
|
3
10
|
<input v-if="name" type="hidden" :name="name" :value="modelValue ? 'on' : 'off'" />
|
|
4
11
|
|
|
5
12
|
<button
|
|
6
|
-
class="relative
|
|
7
|
-
:class="
|
|
13
|
+
class="relative flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-ui p-0.5 outline-ring focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed"
|
|
14
|
+
:class="[
|
|
15
|
+
variants[state],
|
|
16
|
+
disabled
|
|
17
|
+
? ''
|
|
18
|
+
: modelValue
|
|
19
|
+
? 'group-hover:bg-primary-hover group-active:bg-primary-active'
|
|
20
|
+
: 'group-hover:bg-secondary-hover group-active:bg-secondary-active',
|
|
21
|
+
]"
|
|
8
22
|
type="button"
|
|
9
23
|
role="switch"
|
|
10
24
|
:aria-checked="modelValue"
|
|
11
25
|
:disabled="disabled"
|
|
12
26
|
@click="$emit('update:modelValue', !modelValue)"
|
|
13
27
|
>
|
|
14
|
-
<span class="block size-5 rounded-ui shadow
|
|
28
|
+
<span class="block size-5 rounded-ui shadow" :class="thumbVariants[state]" />
|
|
15
29
|
</button>
|
|
16
30
|
|
|
17
31
|
<Stack is="span" v-if="label || description || $slots.default" gap="0">
|
|
18
|
-
<span class="text-sm font-
|
|
32
|
+
<span class="text-sm font-normal">
|
|
19
33
|
<slot> {{ label }} </slot>
|
|
20
34
|
</span>
|
|
21
35
|
|
|
@@ -47,11 +61,11 @@ const interactionVariants: Record<ControlInteraction, string> = {
|
|
|
47
61
|
|
|
48
62
|
const thumbVariants: Record<SwitchState, string> = {
|
|
49
63
|
checked: "translate-x-5 bg-primary-foreground",
|
|
50
|
-
unchecked: "translate-x-
|
|
64
|
+
unchecked: "translate-x-0 bg-foreground",
|
|
51
65
|
};
|
|
52
66
|
|
|
53
67
|
const variants: Record<SwitchState, string> = {
|
|
54
68
|
checked: "bg-primary",
|
|
55
|
-
unchecked: "bg-input",
|
|
69
|
+
unchecked: "bg-background ring-1 ring-input ring-inset",
|
|
56
70
|
};
|
|
57
71
|
</script>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<textarea
|
|
3
|
+
ref="control"
|
|
4
|
+
class="block w-full min-w-0 rounded-ui outline-ring placeholder:opacity-50 focus-visible:outline-2 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:ring-2 aria-invalid:ring-destructive"
|
|
5
|
+
:class="[texts[text], resizes[resize], variants[variant]]"
|
|
6
|
+
:value="modelValue"
|
|
7
|
+
:disabled="disabled"
|
|
8
|
+
:readonly="readonly"
|
|
9
|
+
:required="required"
|
|
10
|
+
:aria-invalid="invalid ?? ($attrs as HTMLAttributes)['aria-invalid']"
|
|
11
|
+
@input="$emit('update:modelValue', ($event.currentTarget as HTMLTextAreaElement).value)"
|
|
12
|
+
></textarea>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup lang="ts">
|
|
16
|
+
import { useTemplateRef, type HTMLAttributes } from "vue";
|
|
17
|
+
import type { TextareaProps, TextareaResize, TextareaText, TextareaVariant } from "../../components/textarea";
|
|
18
|
+
|
|
19
|
+
const {
|
|
20
|
+
modelValue = "",
|
|
21
|
+
text = "sm",
|
|
22
|
+
resize = "vertical",
|
|
23
|
+
variant = "default",
|
|
24
|
+
disabled = false,
|
|
25
|
+
readonly = false,
|
|
26
|
+
required = false,
|
|
27
|
+
invalid = undefined,
|
|
28
|
+
} = defineProps<TextareaProps>();
|
|
29
|
+
|
|
30
|
+
defineEmits<{ "update:modelValue": [value: string] }>();
|
|
31
|
+
|
|
32
|
+
const control = useTemplateRef("control");
|
|
33
|
+
|
|
34
|
+
defineExpose({ focus: () => control.value?.focus() });
|
|
35
|
+
|
|
36
|
+
const texts: Record<TextareaText, string> = { sm: "text-sm", md: "text-base" };
|
|
37
|
+
|
|
38
|
+
const resizes: Record<TextareaResize, string> = {
|
|
39
|
+
none: "resize-none",
|
|
40
|
+
vertical: "resize-y",
|
|
41
|
+
horizontal: "resize-x",
|
|
42
|
+
both: "resize",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const variants: Record<TextareaVariant, string> = {
|
|
46
|
+
default: "bg-secondary p-3 ring-1 ring-input ring-inset",
|
|
47
|
+
plain: "bg-transparent p-0",
|
|
48
|
+
};
|
|
49
|
+
</script>
|