@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,19 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<dialog
|
|
3
3
|
ref="dialog"
|
|
4
|
-
class="m-auto w-
|
|
4
|
+
class="m-auto w-full max-w-lg border-0 bg-transparent p-5 backdrop:bg-black/50"
|
|
5
5
|
@close="$emit('update:modelValue', false)"
|
|
6
6
|
@click="$event.target === $event.currentTarget && ($event.currentTarget as HTMLDialogElement).close()"
|
|
7
7
|
>
|
|
8
8
|
<Card :title="title" :description="description" class="shadow-2xl">
|
|
9
9
|
<template #actions>
|
|
10
|
-
<Button
|
|
11
|
-
size="icon"
|
|
12
|
-
variant="ghost"
|
|
13
|
-
icon-prefix="multiplication-sign"
|
|
14
|
-
:aria-label="closeLabel"
|
|
15
|
-
@click="close"
|
|
16
|
-
/>
|
|
10
|
+
<Button size="icon" variant="ghost" :icon-prefix="CloseIcon" :aria-label="closeLabel" @click="close" />
|
|
17
11
|
</template>
|
|
18
12
|
<slot :open="open" :close="close" :toggle="toggle" />
|
|
19
13
|
|
|
@@ -25,21 +19,15 @@
|
|
|
25
19
|
</template>
|
|
26
20
|
|
|
27
21
|
<script setup lang="ts">
|
|
22
|
+
import CloseIcon from "~icons/hugeicons/multiplication-sign";
|
|
28
23
|
import { useTemplateRef, watchEffect } from "vue";
|
|
24
|
+
import type { DialogProps } from "../../components/dialog";
|
|
29
25
|
import Button from "./button.vue";
|
|
30
26
|
import Card from "./card.vue";
|
|
27
|
+
import { useFocusTrap } from "../utilities/use-focus-trap";
|
|
28
|
+
import { useScrollLock } from "../utilities/use-scroll-lock";
|
|
31
29
|
|
|
32
|
-
const {
|
|
33
|
-
modelValue = false,
|
|
34
|
-
title = undefined,
|
|
35
|
-
description = undefined,
|
|
36
|
-
closeLabel = "Close",
|
|
37
|
-
} = defineProps<{
|
|
38
|
-
modelValue?: boolean;
|
|
39
|
-
title?: string;
|
|
40
|
-
description?: string;
|
|
41
|
-
closeLabel?: string;
|
|
42
|
-
}>();
|
|
30
|
+
const { modelValue = false, title, description, closeLabel = "Close" } = defineProps<DialogProps>();
|
|
43
31
|
|
|
44
32
|
const emit = defineEmits<{
|
|
45
33
|
"update:modelValue": [value: boolean];
|
|
@@ -47,6 +35,10 @@ const emit = defineEmits<{
|
|
|
47
35
|
|
|
48
36
|
const dialog = useTemplateRef("dialog");
|
|
49
37
|
|
|
38
|
+
useFocusTrap(dialog, () => modelValue);
|
|
39
|
+
|
|
40
|
+
useScrollLock(() => modelValue);
|
|
41
|
+
|
|
50
42
|
function open() {
|
|
51
43
|
emit("update:modelValue", true);
|
|
52
44
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
<legend v-if="label" class="mb-
|
|
2
|
+
<Stack is="fieldset" :disabled="disabled">
|
|
3
|
+
<legend v-if="label" class="mb-1 text-sm font-medium">
|
|
4
4
|
{{ label }}<span v-if="required" aria-hidden="true"> *</span>
|
|
5
5
|
</legend>
|
|
6
6
|
|
|
@@ -12,21 +12,12 @@
|
|
|
12
12
|
<p v-else-if="description" class="text-sm opacity-60">
|
|
13
13
|
{{ description }}
|
|
14
14
|
</p>
|
|
15
|
-
</
|
|
15
|
+
</Stack>
|
|
16
16
|
</template>
|
|
17
17
|
|
|
18
18
|
<script setup lang="ts">
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
required = false,
|
|
24
|
-
disabled = false,
|
|
25
|
-
} = defineProps<{
|
|
26
|
-
label?: string;
|
|
27
|
-
description?: string;
|
|
28
|
-
error?: string;
|
|
29
|
-
required?: boolean;
|
|
30
|
-
disabled?: boolean;
|
|
31
|
-
}>();
|
|
19
|
+
import type { FieldProps } from "../../components/field";
|
|
20
|
+
import Stack from "./stack.vue";
|
|
21
|
+
|
|
22
|
+
const { label, description, error, required = false, disabled = false } = defineProps<FieldProps>();
|
|
32
23
|
</script>
|
|
@@ -6,19 +6,18 @@
|
|
|
6
6
|
|
|
7
7
|
<script setup lang="ts">
|
|
8
8
|
import type { Component } from "vue";
|
|
9
|
-
import type { HeadingLayout, HeadingText } from "../../components/heading
|
|
9
|
+
import type { HeadingLayout, HeadingProps, HeadingText } from "../../components/heading";
|
|
10
10
|
|
|
11
11
|
const {
|
|
12
12
|
is = "h2",
|
|
13
|
-
label
|
|
13
|
+
label,
|
|
14
14
|
text = "xl",
|
|
15
15
|
layout = "left",
|
|
16
|
-
} = defineProps<
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}>();
|
|
16
|
+
} = defineProps<
|
|
17
|
+
HeadingProps & {
|
|
18
|
+
is?: string | Component;
|
|
19
|
+
}
|
|
20
|
+
>();
|
|
22
21
|
|
|
23
22
|
const layouts: Record<HeadingLayout, string> = {
|
|
24
23
|
left: "text-left",
|
|
@@ -1,49 +1,15 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<span
|
|
3
|
-
|
|
2
|
+
<span
|
|
3
|
+
aria-hidden="true"
|
|
4
|
+
class="block shrink-0"
|
|
5
|
+
:style="{ height: size === undefined ? '1em' : `${size}px`, width: size === undefined ? '1em' : `${size}px` }"
|
|
6
|
+
>
|
|
7
|
+
<component :is="is" class="block size-full" />
|
|
4
8
|
</span>
|
|
5
9
|
</template>
|
|
6
10
|
|
|
7
11
|
<script setup lang="ts">
|
|
8
|
-
import
|
|
9
|
-
import { getIconData, iconToSVG, replaceIDs } from "@iconify/utils";
|
|
10
|
-
import { computed, defineAsyncComponent, h, markRaw } from "vue";
|
|
12
|
+
import type { IconProps } from "../../components/icon";
|
|
11
13
|
|
|
12
|
-
const {
|
|
13
|
-
name: string;
|
|
14
|
-
}>();
|
|
15
|
-
|
|
16
|
-
const icon = computed(() => {
|
|
17
|
-
const separator = name.indexOf(":");
|
|
18
|
-
const prefix = separator === -1 ? undefined : name.slice(0, separator);
|
|
19
|
-
const iconName = separator === -1 ? name : name.slice(separator + 1);
|
|
20
|
-
|
|
21
|
-
return defineAsyncComponent(async () => {
|
|
22
|
-
for (const [key, resolve] of Object.entries(iconSets)) {
|
|
23
|
-
if (prefix && key !== prefix) {
|
|
24
|
-
continue;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const set = await resolve();
|
|
28
|
-
|
|
29
|
-
if (prefix && set.prefix !== prefix) {
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const data = getIconData(set, iconName);
|
|
34
|
-
|
|
35
|
-
if (data) {
|
|
36
|
-
const svg = iconToSVG(data);
|
|
37
|
-
const body = replaceIDs(svg.body);
|
|
38
|
-
|
|
39
|
-
return markRaw({
|
|
40
|
-
name: `${set.prefix}-${iconName}`,
|
|
41
|
-
render: () => h("svg", { ...svg.attributes, innerHTML: body }),
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return markRaw({ render: () => null });
|
|
47
|
-
});
|
|
48
|
-
});
|
|
14
|
+
const { is, size } = defineProps<IconProps>();
|
|
49
15
|
</script>
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
3
|
-
|
|
2
|
+
<Stack
|
|
3
|
+
is="span"
|
|
4
|
+
direction="horizontal"
|
|
5
|
+
align="center"
|
|
6
|
+
class="min-h-10 w-full cursor-text rounded-ui border border-input px-3 text-sm ring-ring focus-within:ring-2"
|
|
4
7
|
:class="interactionVariants[interaction]"
|
|
5
8
|
>
|
|
6
9
|
<slot name="leading" />
|
|
7
10
|
|
|
8
|
-
<Icon v-if="iconPrefix" :
|
|
11
|
+
<Icon v-if="iconPrefix" :is="iconPrefix" :size="iconSize" />
|
|
9
12
|
|
|
10
13
|
<input
|
|
11
14
|
class="w-full min-w-0 appearance-none bg-transparent py-2 outline-none placeholder:opacity-50"
|
|
@@ -20,40 +23,33 @@
|
|
|
20
23
|
@input="input"
|
|
21
24
|
/>
|
|
22
25
|
|
|
23
|
-
<Icon v-if="iconSuffix" :
|
|
26
|
+
<Icon v-if="iconSuffix" :is="iconSuffix" :size="iconSize" />
|
|
24
27
|
|
|
25
28
|
<slot name="trailing" />
|
|
26
|
-
</
|
|
29
|
+
</Stack>
|
|
27
30
|
</template>
|
|
28
31
|
|
|
29
32
|
<script setup lang="ts">
|
|
30
33
|
import { computed } from "vue";
|
|
31
34
|
import type { ControlInteraction } from "../../components/control.ts";
|
|
35
|
+
import type { InputProps } from "../../components/input";
|
|
32
36
|
import Icon from "./icon.vue";
|
|
37
|
+
import Stack from "./stack.vue";
|
|
33
38
|
|
|
34
39
|
const {
|
|
35
|
-
modelValue
|
|
40
|
+
modelValue,
|
|
36
41
|
type = "text",
|
|
37
|
-
iconPrefix
|
|
38
|
-
iconSuffix
|
|
39
|
-
placeholder
|
|
40
|
-
name
|
|
42
|
+
iconPrefix,
|
|
43
|
+
iconSuffix,
|
|
44
|
+
placeholder,
|
|
45
|
+
name,
|
|
41
46
|
spellcheck = undefined,
|
|
42
47
|
required = false,
|
|
43
48
|
readonly = false,
|
|
44
49
|
disabled = false,
|
|
45
|
-
} = defineProps<
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
iconPrefix?: string;
|
|
49
|
-
iconSuffix?: string;
|
|
50
|
-
placeholder?: string;
|
|
51
|
-
name?: string;
|
|
52
|
-
spellcheck?: boolean;
|
|
53
|
-
required?: boolean;
|
|
54
|
-
readonly?: boolean;
|
|
55
|
-
disabled?: boolean;
|
|
56
|
-
}>();
|
|
50
|
+
} = defineProps<InputProps>();
|
|
51
|
+
|
|
52
|
+
const iconSize = 20;
|
|
57
53
|
|
|
58
54
|
const emit = defineEmits<{
|
|
59
55
|
"update:modelValue": [value: string | number];
|
|
@@ -11,19 +11,18 @@
|
|
|
11
11
|
|
|
12
12
|
<script setup lang="ts">
|
|
13
13
|
import type { Component } from "vue";
|
|
14
|
-
import type { LinkVariant } from "../../components/link
|
|
14
|
+
import type { LinkProps, LinkVariant } from "../../components/link";
|
|
15
15
|
|
|
16
16
|
const {
|
|
17
17
|
is = "a",
|
|
18
|
-
href
|
|
19
|
-
label
|
|
18
|
+
href,
|
|
19
|
+
label,
|
|
20
20
|
variant = "primary",
|
|
21
|
-
} = defineProps<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}>();
|
|
21
|
+
} = defineProps<
|
|
22
|
+
LinkProps & {
|
|
23
|
+
is?: string | Component;
|
|
24
|
+
}
|
|
25
|
+
>();
|
|
27
26
|
|
|
28
27
|
const variants: Record<LinkVariant, string> = {
|
|
29
28
|
primary: "text-foreground",
|
|
@@ -12,19 +12,8 @@
|
|
|
12
12
|
</template>
|
|
13
13
|
|
|
14
14
|
<script setup lang="ts">
|
|
15
|
+
import type { NavigateProps } from "../../components/navigate";
|
|
15
16
|
import Button from "./button.vue";
|
|
16
17
|
|
|
17
|
-
const {
|
|
18
|
-
href,
|
|
19
|
-
label = undefined,
|
|
20
|
-
iconPrefix = undefined,
|
|
21
|
-
iconSuffix = undefined,
|
|
22
|
-
active = false,
|
|
23
|
-
} = defineProps<{
|
|
24
|
-
href: string;
|
|
25
|
-
label?: string;
|
|
26
|
-
iconPrefix?: string;
|
|
27
|
-
iconSuffix?: string;
|
|
28
|
-
active?: boolean;
|
|
29
|
-
}>();
|
|
18
|
+
const { href, label, iconPrefix, iconSuffix, active = false } = defineProps<NavigateProps>();
|
|
30
19
|
</script>
|
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div ref="container" class="inline-flex">
|
|
3
|
-
<Button
|
|
4
|
-
variant="outline"
|
|
5
|
-
:class="triggerClass"
|
|
6
|
-
:layout="triggerLayout"
|
|
7
|
-
:disabled="disabled"
|
|
8
|
-
:popovertarget="id"
|
|
9
|
-
>
|
|
2
|
+
<div ref="container" class="relative inline-flex">
|
|
3
|
+
<Button variant="outline" :class="triggerClass" :layout="triggerLayout" :disabled="disabled" @click="toggle">
|
|
10
4
|
<slot name="trigger" :open="open" :close="close" :toggle="toggle">{{ triggerLabel }}</slot>
|
|
11
5
|
</Button>
|
|
12
6
|
|
|
13
7
|
<div
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class="m-0 mt-2 max-w-sm rounded-ui border border-border bg-popover text-popover-foreground shadow-xl"
|
|
18
|
-
:class="contentClass"
|
|
19
|
-
:style="position"
|
|
20
|
-
@toggle="handleToggle"
|
|
8
|
+
v-if="isOpen"
|
|
9
|
+
class="absolute left-0 top-full z-10 mt-1 max-w-sm rounded-ui border border-border bg-popover text-popover-foreground shadow-xl"
|
|
10
|
+
:class="[contentClass, matchTriggerWidth ? 'w-full' : '']"
|
|
21
11
|
>
|
|
22
12
|
<slot :open="open" :close="close" :toggle="toggle" />
|
|
23
13
|
</div>
|
|
@@ -25,88 +15,88 @@
|
|
|
25
15
|
</template>
|
|
26
16
|
|
|
27
17
|
<script setup lang="ts">
|
|
28
|
-
import {
|
|
29
|
-
import type {
|
|
18
|
+
import { onUnmounted, ref, useTemplateRef, watch } from "vue";
|
|
19
|
+
import type { PopoverProps } from "../../components/popover";
|
|
30
20
|
import Button from "./button.vue";
|
|
21
|
+
import { useFocusTrap } from "../utilities/use-focus-trap";
|
|
31
22
|
|
|
32
23
|
const {
|
|
33
24
|
modelValue = false,
|
|
34
25
|
triggerLabel = "Open",
|
|
35
|
-
triggerClass
|
|
26
|
+
triggerClass,
|
|
36
27
|
triggerLayout = "center",
|
|
37
|
-
contentClass = "p-
|
|
28
|
+
contentClass = "flex flex-col gap-1 p-1",
|
|
38
29
|
matchTriggerWidth = false,
|
|
39
30
|
disabled = false,
|
|
40
|
-
} = defineProps<
|
|
41
|
-
modelValue?: boolean;
|
|
42
|
-
triggerLabel?: string;
|
|
43
|
-
triggerClass?: string;
|
|
44
|
-
triggerLayout?: ButtonLayout;
|
|
45
|
-
contentClass?: string;
|
|
46
|
-
matchTriggerWidth?: boolean;
|
|
47
|
-
disabled?: boolean;
|
|
48
|
-
}>();
|
|
31
|
+
} = defineProps<PopoverProps>();
|
|
49
32
|
|
|
50
33
|
const emit = defineEmits<{
|
|
51
34
|
"update:modelValue": [value: boolean];
|
|
52
35
|
}>();
|
|
53
36
|
|
|
54
|
-
const id = `popover-${useId()}`;
|
|
55
|
-
|
|
56
37
|
const container = useTemplateRef("container");
|
|
57
38
|
|
|
58
|
-
const
|
|
39
|
+
const isOpen = ref(modelValue);
|
|
40
|
+
|
|
41
|
+
useFocusTrap(container, isOpen);
|
|
59
42
|
|
|
60
|
-
|
|
43
|
+
function setOpen(value: boolean) {
|
|
44
|
+
isOpen.value = value;
|
|
45
|
+
|
|
46
|
+
emit("update:modelValue", value);
|
|
47
|
+
}
|
|
61
48
|
|
|
62
49
|
function open() {
|
|
63
|
-
|
|
50
|
+
setOpen(true);
|
|
64
51
|
}
|
|
65
52
|
|
|
66
53
|
function close() {
|
|
67
|
-
|
|
54
|
+
setOpen(false);
|
|
68
55
|
}
|
|
69
56
|
|
|
70
57
|
function toggle() {
|
|
71
|
-
|
|
58
|
+
setOpen(!isOpen.value);
|
|
72
59
|
}
|
|
73
60
|
|
|
74
|
-
function
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (open) {
|
|
79
|
-
const trigger = container.value?.querySelector("button");
|
|
80
|
-
|
|
81
|
-
if (trigger) {
|
|
82
|
-
const bounds = trigger.getBoundingClientRect();
|
|
83
|
-
const width = matchTriggerWidth ? bounds.width : element.offsetWidth;
|
|
84
|
-
const left = Math.min(Math.max(8, bounds.left), window.innerWidth - width - 8);
|
|
85
|
-
const top =
|
|
86
|
-
bounds.bottom + element.offsetHeight + 8 <= window.innerHeight
|
|
87
|
-
? bounds.bottom + 8
|
|
88
|
-
: bounds.top - element.offsetHeight - 8;
|
|
89
|
-
|
|
90
|
-
position.value = {
|
|
91
|
-
bottom: "auto",
|
|
92
|
-
left: `${left}px`,
|
|
93
|
-
right: "auto",
|
|
94
|
-
top: `${Math.max(8, top)}px`,
|
|
95
|
-
position: "fixed",
|
|
96
|
-
width: matchTriggerWidth ? `${width}px` : "auto",
|
|
97
|
-
};
|
|
98
|
-
}
|
|
61
|
+
function handlePointerDown(event: PointerEvent) {
|
|
62
|
+
if (!container.value?.contains(event.target as Node)) {
|
|
63
|
+
close();
|
|
99
64
|
}
|
|
100
|
-
|
|
101
|
-
emit("update:modelValue", open);
|
|
102
65
|
}
|
|
103
66
|
|
|
104
|
-
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
} else if (!modelValue && popover.value?.matches(":popover-open")) {
|
|
108
|
-
popover.value.hidePopover();
|
|
67
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
68
|
+
if (event.key === "Escape") {
|
|
69
|
+
close();
|
|
109
70
|
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
watch(
|
|
74
|
+
isOpen,
|
|
75
|
+
(value) => {
|
|
76
|
+
if (value) {
|
|
77
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
78
|
+
|
|
79
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
80
|
+
} else {
|
|
81
|
+
document.removeEventListener("pointerdown", handlePointerDown);
|
|
82
|
+
|
|
83
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{ immediate: true },
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
watch(
|
|
90
|
+
() => modelValue,
|
|
91
|
+
(value) => {
|
|
92
|
+
isOpen.value = value;
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
onUnmounted(() => {
|
|
97
|
+
document.removeEventListener("pointerdown", handlePointerDown);
|
|
98
|
+
|
|
99
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
110
100
|
});
|
|
111
101
|
|
|
112
102
|
defineExpose({ open, close, toggle });
|
|
@@ -1,38 +1,32 @@
|
|
|
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
|
+
import type { ProgressProps } from "../../components/progress";
|
|
27
|
+
import Stack from "./stack.vue";
|
|
26
28
|
|
|
27
|
-
const {
|
|
28
|
-
value = 0,
|
|
29
|
-
max = 100,
|
|
30
|
-
label = undefined,
|
|
31
|
-
} = defineProps<{
|
|
32
|
-
value?: number;
|
|
33
|
-
max?: number;
|
|
34
|
-
label?: string;
|
|
35
|
-
}>();
|
|
29
|
+
const { value = 0, max = 100, label } = defineProps<ProgressProps>();
|
|
36
30
|
|
|
37
31
|
const percent = computed(() => (max > 0 ? Math.min(100, Math.max(0, (value / max) * 100)) : 0));
|
|
38
32
|
</script>
|
|
@@ -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,44 +13,29 @@
|
|
|
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
|
-
import type { RadioValue } from "../../components/radio
|
|
35
|
+
import type { RadioProps, RadioValue } from "../../components/radio";
|
|
36
|
+
import Stack from "./stack.vue";
|
|
36
37
|
|
|
37
|
-
const {
|
|
38
|
-
modelValue = undefined,
|
|
39
|
-
value,
|
|
40
|
-
label = undefined,
|
|
41
|
-
description = undefined,
|
|
42
|
-
name = undefined,
|
|
43
|
-
required = false,
|
|
44
|
-
disabled = false,
|
|
45
|
-
} = defineProps<{
|
|
46
|
-
modelValue?: RadioValue;
|
|
47
|
-
value: RadioValue;
|
|
48
|
-
label?: string;
|
|
49
|
-
description?: string;
|
|
50
|
-
name?: string;
|
|
51
|
-
required?: boolean;
|
|
52
|
-
disabled?: boolean;
|
|
53
|
-
}>();
|
|
38
|
+
const { modelValue, value, label, description, name, required = false, disabled = false } = defineProps<RadioProps>();
|
|
54
39
|
|
|
55
40
|
defineEmits<{
|
|
56
41
|
"update:modelValue": [value: RadioValue];
|
|
@@ -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,32 +28,20 @@
|
|
|
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
|
+
import type { RangeProps } from "../../components/range";
|
|
42
|
+
import Stack from "./stack.vue";
|
|
41
43
|
|
|
42
|
-
const {
|
|
43
|
-
modelValue = 0,
|
|
44
|
-
label = undefined,
|
|
45
|
-
min = 0,
|
|
46
|
-
max = 100,
|
|
47
|
-
step = 1,
|
|
48
|
-
disabled = false,
|
|
49
|
-
} = defineProps<{
|
|
50
|
-
modelValue?: number;
|
|
51
|
-
label?: string;
|
|
52
|
-
min?: number;
|
|
53
|
-
max?: number;
|
|
54
|
-
step?: number;
|
|
55
|
-
disabled?: boolean;
|
|
56
|
-
}>();
|
|
44
|
+
const { modelValue = 0, label, min = 0, max = 100, step = 1, disabled = false } = defineProps<RangeProps>();
|
|
57
45
|
|
|
58
46
|
const emit = defineEmits<{
|
|
59
47
|
"update:modelValue": [value: number];
|