@revojs/ui 0.1.58
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/breadcrumb.d.ts +4 -0
- package/dist/components/breadcrumb.js +1 -0
- package/dist/components/button.d.ts +18 -0
- package/dist/components/button.js +1 -0
- package/dist/components/card.d.ts +5 -0
- package/dist/components/card.js +1 -0
- package/dist/components/checkbox.d.ts +8 -0
- package/dist/components/checkbox.js +1 -0
- package/dist/components/collection.d.ts +6 -0
- package/dist/components/collection.js +1 -0
- package/dist/components/container.d.ts +4 -0
- package/dist/components/container.js +1 -0
- package/dist/components/control.d.ts +1 -0
- package/dist/components/control.js +1 -0
- package/dist/components/counter.d.ts +8 -0
- package/dist/components/counter.js +1 -0
- package/dist/components/dialog.d.ts +6 -0
- package/dist/components/dialog.js +1 -0
- package/dist/components/field.d.ts +7 -0
- package/dist/components/field.js +1 -0
- package/dist/components/heading.d.ts +7 -0
- package/dist/components/heading.js +1 -0
- package/dist/components/icon.d.ts +3 -0
- package/dist/components/icon.js +1 -0
- package/dist/components/input.d.ts +12 -0
- package/dist/components/input.js +1 -0
- package/dist/components/link.d.ts +6 -0
- package/dist/components/link.js +1 -0
- package/dist/components/navigate.d.ts +7 -0
- package/dist/components/navigate.js +1 -0
- package/dist/components/popover.d.ts +10 -0
- package/dist/components/popover.js +1 -0
- package/dist/components/progress.d.ts +5 -0
- package/dist/components/progress.js +1 -0
- package/dist/components/radio.d.ts +10 -0
- package/dist/components/radio.js +1 -0
- package/dist/components/range.d.ts +8 -0
- package/dist/components/range.js +1 -0
- package/dist/components/rating.d.ts +8 -0
- package/dist/components/rating.js +1 -0
- package/dist/components/select.d.ts +16 -0
- package/dist/components/select.js +1 -0
- package/dist/components/separator.d.ts +11 -0
- package/dist/components/separator.js +1 -0
- package/dist/components/sheet.d.ts +8 -0
- package/dist/components/sheet.js +1 -0
- package/dist/components/switch.d.ts +8 -0
- package/dist/components/switch.js +1 -0
- package/dist/components/upload.d.ts +10 -0
- package/dist/components/upload.js +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +1 -0
- package/dist/module/index.d.ts +35 -0
- package/dist/module/index.js +123 -0
- package/dist/types/index.d.ts +20 -0
- package/dist/vue/components/breadcrumb.vue +20 -0
- package/dist/vue/components/button.vue +124 -0
- package/dist/vue/components/card.vue +53 -0
- package/dist/vue/components/checkbox.vue +54 -0
- package/dist/vue/components/collection.vue +51 -0
- package/dist/vue/components/container.vue +19 -0
- package/dist/vue/components/counter.vue +61 -0
- package/dist/vue/components/dialog.vue +76 -0
- package/dist/vue/components/field.vue +38 -0
- package/dist/vue/components/heading.vue +34 -0
- package/dist/vue/components/icon.vue +49 -0
- package/dist/vue/components/input.vue +82 -0
- package/dist/vue/components/link.vue +33 -0
- package/dist/vue/components/navigate.vue +30 -0
- package/dist/vue/components/popover.vue +123 -0
- package/dist/vue/components/progress.vue +44 -0
- package/dist/vue/components/radio.vue +59 -0
- package/dist/vue/components/range.vue +56 -0
- package/dist/vue/components/rating.vue +56 -0
- package/dist/vue/components/select.vue +99 -0
- package/dist/vue/components/separator.vue +31 -0
- package/dist/vue/components/sheet.vue +71 -0
- package/dist/vue/components/switch.vue +72 -0
- package/dist/vue/components/upload.vue +133 -0
- package/dist/vue/index.d.ts +25 -0
- package/dist/vue/index.js +25 -0
- package/package.json +46 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="is"
|
|
4
|
+
class="inline-flex cursor-pointer items-center justify-center gap-2 border border-transparent font-[inherit] font-medium leading-none no-underline transition-[background-color,border-color,color,opacity] duration-150 hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50 disabled:hover:opacity-50 aria-disabled:cursor-not-allowed aria-disabled:opacity-50 aria-disabled:hover:opacity-50"
|
|
5
|
+
:class="[
|
|
6
|
+
config.radius,
|
|
7
|
+
`outline-${config.light.ring}`,
|
|
8
|
+
`dark:outline-${config.dark.ring}`,
|
|
9
|
+
fonts[font],
|
|
10
|
+
layouts[layout],
|
|
11
|
+
sizes[size],
|
|
12
|
+
texts[text],
|
|
13
|
+
variants[variant],
|
|
14
|
+
]"
|
|
15
|
+
:type="attributes.type ?? 'button'"
|
|
16
|
+
:disabled="disabled"
|
|
17
|
+
>
|
|
18
|
+
<Icon v-if="iconPrefix" :name="iconPrefix" />
|
|
19
|
+
|
|
20
|
+
<slot>
|
|
21
|
+
<span v-if="label" class="truncate px-1" :class="labelLayouts[layout]">{{ label }}</span>
|
|
22
|
+
</slot>
|
|
23
|
+
|
|
24
|
+
<Icon v-if="iconSuffix" :name="iconSuffix" />
|
|
25
|
+
</component>
|
|
26
|
+
</template>
|
|
27
|
+
|
|
28
|
+
<script setup lang="ts">
|
|
29
|
+
import config from "#config/ui";
|
|
30
|
+
import { useAttrs, type Component } from "vue";
|
|
31
|
+
import type { ButtonFont, ButtonLayout, ButtonSize, ButtonText, ButtonVariant } from "../../components/button.ts";
|
|
32
|
+
import Icon from "./icon.vue";
|
|
33
|
+
|
|
34
|
+
const {
|
|
35
|
+
is = "button",
|
|
36
|
+
iconPrefix = undefined,
|
|
37
|
+
iconSuffix = undefined,
|
|
38
|
+
label = undefined,
|
|
39
|
+
font = "medium",
|
|
40
|
+
layout = "center",
|
|
41
|
+
size = "default",
|
|
42
|
+
text = "sm",
|
|
43
|
+
variant = "primary",
|
|
44
|
+
disabled = false,
|
|
45
|
+
} = defineProps<{
|
|
46
|
+
is?: string | Component;
|
|
47
|
+
iconPrefix?: string;
|
|
48
|
+
iconSuffix?: string;
|
|
49
|
+
label?: string;
|
|
50
|
+
font?: ButtonFont;
|
|
51
|
+
layout?: ButtonLayout;
|
|
52
|
+
size?: ButtonSize;
|
|
53
|
+
text?: ButtonText;
|
|
54
|
+
variant?: ButtonVariant;
|
|
55
|
+
disabled?: boolean;
|
|
56
|
+
}>();
|
|
57
|
+
|
|
58
|
+
const attributes = useAttrs();
|
|
59
|
+
|
|
60
|
+
const fonts: Record<ButtonFont, string> = {
|
|
61
|
+
normal: "font-normal",
|
|
62
|
+
medium: "font-medium",
|
|
63
|
+
semibold: "font-semibold",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const layouts: Record<ButtonLayout, string> = {
|
|
67
|
+
left: "justify-start text-left",
|
|
68
|
+
center: "justify-center text-center",
|
|
69
|
+
right: "justify-end text-right",
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const labelLayouts: Record<ButtonLayout, string> = {
|
|
73
|
+
left: "flex-1",
|
|
74
|
+
center: "",
|
|
75
|
+
right: "flex-1",
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const sizes: Record<ButtonSize, string> = {
|
|
79
|
+
default: "min-h-10 px-4 py-2",
|
|
80
|
+
icon: "size-10 shrink-0 p-0",
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const texts: Record<ButtonText, string> = {
|
|
84
|
+
sm: "text-sm",
|
|
85
|
+
md: "text-base",
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const variants: Record<ButtonVariant, Array<string>> = {
|
|
89
|
+
primary: [
|
|
90
|
+
`bg-${config.light.primary}`,
|
|
91
|
+
`text-${config.light.primaryForeground}`,
|
|
92
|
+
`dark:bg-${config.dark.primary}`,
|
|
93
|
+
`dark:text-${config.dark.primaryForeground}`,
|
|
94
|
+
],
|
|
95
|
+
secondary: [
|
|
96
|
+
`bg-${config.light.secondary}`,
|
|
97
|
+
`text-${config.light.secondaryForeground}`,
|
|
98
|
+
`dark:bg-${config.dark.secondary}`,
|
|
99
|
+
`dark:text-${config.dark.secondaryForeground}`,
|
|
100
|
+
],
|
|
101
|
+
outline: [
|
|
102
|
+
`border-${config.light.border}`,
|
|
103
|
+
`bg-${config.light.background}`,
|
|
104
|
+
`text-${config.light.foreground}`,
|
|
105
|
+
`dark:border-${config.dark.border}`,
|
|
106
|
+
`dark:bg-${config.dark.background}`,
|
|
107
|
+
`dark:text-${config.dark.foreground}`,
|
|
108
|
+
],
|
|
109
|
+
ghost: [`text-${config.light.foreground}`, `dark:text-${config.dark.foreground}`],
|
|
110
|
+
destructive: [
|
|
111
|
+
`bg-${config.light.destructive}`,
|
|
112
|
+
`text-${config.light.destructiveForeground}`,
|
|
113
|
+
`dark:bg-${config.dark.destructive}`,
|
|
114
|
+
`dark:text-${config.dark.destructiveForeground}`,
|
|
115
|
+
],
|
|
116
|
+
danger: [
|
|
117
|
+
`bg-${config.light.destructive}`,
|
|
118
|
+
`text-${config.light.destructiveForeground}`,
|
|
119
|
+
`dark:bg-${config.dark.destructive}`,
|
|
120
|
+
`dark:text-${config.dark.destructiveForeground}`,
|
|
121
|
+
],
|
|
122
|
+
text: [`text-${config.light.foreground}`, `dark:text-${config.dark.foreground}`],
|
|
123
|
+
};
|
|
124
|
+
</script>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<section
|
|
3
|
+
class="flex flex-col overflow-hidden border shadow-sm"
|
|
4
|
+
:class="[
|
|
5
|
+
config.radius,
|
|
6
|
+
`border-${config.light.border}`,
|
|
7
|
+
`bg-${config.light.card}`,
|
|
8
|
+
`text-${config.light.cardForeground}`,
|
|
9
|
+
`dark:border-${config.dark.border}`,
|
|
10
|
+
`dark:bg-${config.dark.card}`,
|
|
11
|
+
`dark:text-${config.dark.cardForeground}`,
|
|
12
|
+
]"
|
|
13
|
+
>
|
|
14
|
+
<header
|
|
15
|
+
v-if="label || title || description || $slots.header || $slots.actions"
|
|
16
|
+
class="flex items-start justify-between gap-4 border-b p-5"
|
|
17
|
+
:class="[`border-${config.light.border}`, `dark:border-${config.dark.border}`]"
|
|
18
|
+
>
|
|
19
|
+
<slot name="header">
|
|
20
|
+
<div class="flex flex-col gap-1.5">
|
|
21
|
+
<h3 v-if="label || title" class="text-lg font-semibold leading-none">{{ label ?? title }}</h3>
|
|
22
|
+
<p v-if="description" class="text-sm opacity-70">{{ description }}</p>
|
|
23
|
+
</div>
|
|
24
|
+
</slot>
|
|
25
|
+
|
|
26
|
+
<div v-if="$slots.actions" class="flex shrink-0 items-center gap-1">
|
|
27
|
+
<slot name="actions" />
|
|
28
|
+
</div>
|
|
29
|
+
</header>
|
|
30
|
+
|
|
31
|
+
<div class="flex flex-1 flex-col gap-4 p-5">
|
|
32
|
+
<slot />
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<footer v-if="$slots.footer" class="flex items-center gap-3 px-5 pb-5">
|
|
36
|
+
<slot name="footer" />
|
|
37
|
+
</footer>
|
|
38
|
+
</section>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<script setup lang="ts">
|
|
42
|
+
import config from "#config/ui";
|
|
43
|
+
|
|
44
|
+
const {
|
|
45
|
+
label = undefined,
|
|
46
|
+
title = undefined,
|
|
47
|
+
description = undefined,
|
|
48
|
+
} = defineProps<{
|
|
49
|
+
label?: string;
|
|
50
|
+
title?: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
}>();
|
|
53
|
+
</script>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<label class="inline-flex items-start gap-3" :class="interactionVariants[interaction]">
|
|
3
|
+
<input
|
|
4
|
+
class="mt-0.5 size-5 shrink-0 cursor-inherit accent-current"
|
|
5
|
+
:class="[`text-${config.light.primary}`, `dark:text-${config.dark.primary}`]"
|
|
6
|
+
type="checkbox"
|
|
7
|
+
:checked="modelValue"
|
|
8
|
+
:name="name"
|
|
9
|
+
:required="required"
|
|
10
|
+
:disabled="disabled"
|
|
11
|
+
@change="$emit('update:modelValue', ($event.currentTarget as HTMLInputElement).checked)"
|
|
12
|
+
/>
|
|
13
|
+
|
|
14
|
+
<span v-if="label || description || $slots.default" class="flex flex-col gap-0.5">
|
|
15
|
+
<span class="text-sm font-medium"
|
|
16
|
+
><slot>{{ label }}</slot></span
|
|
17
|
+
>
|
|
18
|
+
<span v-if="description" class="text-sm opacity-60">{{ description }}</span>
|
|
19
|
+
</span>
|
|
20
|
+
</label>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<script setup lang="ts">
|
|
24
|
+
import config from "#config/ui";
|
|
25
|
+
import { computed } from "vue";
|
|
26
|
+
import type { ControlInteraction } from "../../components/control.ts";
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
modelValue = false,
|
|
30
|
+
label = undefined,
|
|
31
|
+
description = undefined,
|
|
32
|
+
name = undefined,
|
|
33
|
+
required = false,
|
|
34
|
+
disabled = false,
|
|
35
|
+
} = defineProps<{
|
|
36
|
+
modelValue?: boolean;
|
|
37
|
+
label?: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
name?: string;
|
|
40
|
+
required?: boolean;
|
|
41
|
+
disabled?: boolean;
|
|
42
|
+
}>();
|
|
43
|
+
|
|
44
|
+
defineEmits<{
|
|
45
|
+
"update:modelValue": [value: boolean];
|
|
46
|
+
}>();
|
|
47
|
+
|
|
48
|
+
const interaction = computed<ControlInteraction>(() => (disabled ? "disabled" : "enabled"));
|
|
49
|
+
|
|
50
|
+
const interactionVariants: Record<ControlInteraction, string> = {
|
|
51
|
+
disabled: "cursor-not-allowed opacity-50",
|
|
52
|
+
enabled: "cursor-pointer",
|
|
53
|
+
};
|
|
54
|
+
</script>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-if="items"
|
|
4
|
+
class="flex w-full flex-col divide-y overflow-hidden border"
|
|
5
|
+
:class="[
|
|
6
|
+
config.radius,
|
|
7
|
+
`divide-${config.light.border}`,
|
|
8
|
+
`border-${config.light.border}`,
|
|
9
|
+
`bg-${config.light.card}`,
|
|
10
|
+
`dark:divide-${config.dark.border}`,
|
|
11
|
+
`dark:border-${config.dark.border}`,
|
|
12
|
+
`dark:bg-${config.dark.card}`,
|
|
13
|
+
]"
|
|
14
|
+
>
|
|
15
|
+
<template v-if="items.length">
|
|
16
|
+
<div v-for="(item, index) in items" :key="index">
|
|
17
|
+
<slot :item="item" :index="index" />
|
|
18
|
+
</div>
|
|
19
|
+
</template>
|
|
20
|
+
<div v-else class="flex flex-col items-center p-5 text-sm opacity-60">{{ emptyLabel }}</div>
|
|
21
|
+
</div>
|
|
22
|
+
<div v-else class="grid gap-4" :class="columnsClass[columns]">
|
|
23
|
+
<slot />
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup generic="T" lang="ts">
|
|
28
|
+
import config from "#config/ui";
|
|
29
|
+
import type { CollectionColumns } from "../../components/collection.ts";
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
items = undefined,
|
|
33
|
+
columns = 1,
|
|
34
|
+
emptyLabel = "Nothing found",
|
|
35
|
+
} = defineProps<{
|
|
36
|
+
items?: Array<T>;
|
|
37
|
+
columns?: CollectionColumns;
|
|
38
|
+
emptyLabel?: string;
|
|
39
|
+
}>();
|
|
40
|
+
|
|
41
|
+
defineSlots<{
|
|
42
|
+
default: (() => unknown) & ((props: { item: T; index: number }) => unknown);
|
|
43
|
+
}>();
|
|
44
|
+
|
|
45
|
+
const columnsClass: Record<CollectionColumns, string> = {
|
|
46
|
+
1: "grid-cols-1",
|
|
47
|
+
2: "grid-cols-1 sm:grid-cols-2",
|
|
48
|
+
3: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
|
|
49
|
+
4: "grid-cols-1 sm:grid-cols-2 lg:grid-cols-4",
|
|
50
|
+
};
|
|
51
|
+
</script>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="mx-auto flex w-full flex-col gap-5 p-5" :class="sizes[size]">
|
|
3
|
+
<slot />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import type { ContainerSize } from "../../components/container.ts";
|
|
9
|
+
|
|
10
|
+
const { size = "5xl" } = defineProps<{
|
|
11
|
+
size?: ContainerSize;
|
|
12
|
+
}>();
|
|
13
|
+
|
|
14
|
+
const sizes: Record<ContainerSize, string> = {
|
|
15
|
+
"3xl": "max-w-3xl",
|
|
16
|
+
"5xl": "max-w-5xl",
|
|
17
|
+
"7xl": "max-w-7xl",
|
|
18
|
+
};
|
|
19
|
+
</script>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="inline-flex flex-col gap-2">
|
|
3
|
+
<span v-if="label" class="text-sm font-medium">{{ label }}</span>
|
|
4
|
+
|
|
5
|
+
<div
|
|
6
|
+
class="grid h-10 grid-cols-[2.5rem_minmax(0,1fr)_2.5rem] items-center overflow-hidden border"
|
|
7
|
+
:class="[config.radius, `border-${config.light.input}`, `dark:border-${config.dark.input}`]"
|
|
8
|
+
>
|
|
9
|
+
<Button
|
|
10
|
+
class="text-lg"
|
|
11
|
+
size="icon"
|
|
12
|
+
variant="ghost"
|
|
13
|
+
icon-prefix="minus-sign"
|
|
14
|
+
:disabled="disabled || modelValue <= min"
|
|
15
|
+
aria-label="Decrease"
|
|
16
|
+
@click="change(-step)"
|
|
17
|
+
/>
|
|
18
|
+
|
|
19
|
+
<output class="px-2 text-center tabular-nums">{{ modelValue }}</output>
|
|
20
|
+
|
|
21
|
+
<Button
|
|
22
|
+
class="text-lg"
|
|
23
|
+
size="icon"
|
|
24
|
+
variant="ghost"
|
|
25
|
+
icon-prefix="add-01"
|
|
26
|
+
:disabled="disabled || modelValue >= max"
|
|
27
|
+
aria-label="Increase"
|
|
28
|
+
@click="change(step)"
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
</template>
|
|
33
|
+
|
|
34
|
+
<script setup lang="ts">
|
|
35
|
+
import config from "#config/ui";
|
|
36
|
+
import Button from "./button.vue";
|
|
37
|
+
|
|
38
|
+
const {
|
|
39
|
+
modelValue = 0,
|
|
40
|
+
label = undefined,
|
|
41
|
+
min = -Infinity,
|
|
42
|
+
max = Infinity,
|
|
43
|
+
step = 1,
|
|
44
|
+
disabled = false,
|
|
45
|
+
} = defineProps<{
|
|
46
|
+
modelValue?: number;
|
|
47
|
+
label?: string;
|
|
48
|
+
min?: number;
|
|
49
|
+
max?: number;
|
|
50
|
+
step?: number;
|
|
51
|
+
disabled?: boolean;
|
|
52
|
+
}>();
|
|
53
|
+
|
|
54
|
+
const emit = defineEmits<{
|
|
55
|
+
"update:modelValue": [value: number];
|
|
56
|
+
}>();
|
|
57
|
+
|
|
58
|
+
function change(amount: number) {
|
|
59
|
+
emit("update:modelValue", Math.min(max, Math.max(min, modelValue + amount)));
|
|
60
|
+
}
|
|
61
|
+
</script>
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<dialog
|
|
3
|
+
ref="dialog"
|
|
4
|
+
class="m-auto w-[min(32rem,calc(100%-2rem))] border-0 bg-transparent p-0 backdrop:bg-black/50"
|
|
5
|
+
@close="$emit('update:modelValue', false)"
|
|
6
|
+
@click="$event.target === $event.currentTarget && ($event.currentTarget as HTMLDialogElement).close()"
|
|
7
|
+
>
|
|
8
|
+
<Card :title="title" :description="description" class="shadow-2xl">
|
|
9
|
+
<template #actions>
|
|
10
|
+
<Button
|
|
11
|
+
size="icon"
|
|
12
|
+
variant="ghost"
|
|
13
|
+
icon-prefix="multiplication-sign"
|
|
14
|
+
:aria-label="closeLabel"
|
|
15
|
+
@click="close"
|
|
16
|
+
/>
|
|
17
|
+
</template>
|
|
18
|
+
<slot :open="open" :close="close" :toggle="toggle" />
|
|
19
|
+
|
|
20
|
+
<template v-if="$slots.footer" #footer>
|
|
21
|
+
<slot name="footer" />
|
|
22
|
+
</template>
|
|
23
|
+
</Card>
|
|
24
|
+
</dialog>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { useTemplateRef, watchEffect } from "vue";
|
|
29
|
+
import Button from "./button.vue";
|
|
30
|
+
import Card from "./card.vue";
|
|
31
|
+
|
|
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
|
+
}>();
|
|
43
|
+
|
|
44
|
+
const emit = defineEmits<{
|
|
45
|
+
"update:modelValue": [value: boolean];
|
|
46
|
+
}>();
|
|
47
|
+
|
|
48
|
+
const dialog = useTemplateRef("dialog");
|
|
49
|
+
|
|
50
|
+
function open() {
|
|
51
|
+
emit("update:modelValue", true);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function close() {
|
|
55
|
+
dialog.value?.close();
|
|
56
|
+
emit("update:modelValue", false);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function toggle() {
|
|
60
|
+
if (modelValue) {
|
|
61
|
+
close();
|
|
62
|
+
} else {
|
|
63
|
+
open();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
watchEffect(() => {
|
|
68
|
+
if (modelValue && !dialog.value?.open) {
|
|
69
|
+
dialog.value?.showModal();
|
|
70
|
+
} else if (!modelValue && dialog.value?.open) {
|
|
71
|
+
dialog.value.close();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
defineExpose({ open, close, toggle });
|
|
76
|
+
</script>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<fieldset class="flex min-w-0 flex-col gap-2" :disabled="disabled">
|
|
3
|
+
<legend v-if="label" class="mb-2 text-sm font-medium">
|
|
4
|
+
{{ label }}<span v-if="required" aria-hidden="true"> *</span>
|
|
5
|
+
</legend>
|
|
6
|
+
|
|
7
|
+
<slot />
|
|
8
|
+
|
|
9
|
+
<p
|
|
10
|
+
v-if="error"
|
|
11
|
+
class="text-sm"
|
|
12
|
+
:class="[`text-${config.light.destructive}`, `dark:text-${config.dark.destructive}`]"
|
|
13
|
+
>
|
|
14
|
+
{{ error }}
|
|
15
|
+
</p>
|
|
16
|
+
<p v-else-if="description" class="text-sm opacity-60">
|
|
17
|
+
{{ description }}
|
|
18
|
+
</p>
|
|
19
|
+
</fieldset>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import config from "#config/ui";
|
|
24
|
+
|
|
25
|
+
const {
|
|
26
|
+
label = undefined,
|
|
27
|
+
description = undefined,
|
|
28
|
+
error = undefined,
|
|
29
|
+
required = false,
|
|
30
|
+
disabled = false,
|
|
31
|
+
} = defineProps<{
|
|
32
|
+
label?: string;
|
|
33
|
+
description?: string;
|
|
34
|
+
error?: string;
|
|
35
|
+
required?: boolean;
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
}>();
|
|
38
|
+
</script>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component :is="is" class="font-bold text-pretty" :class="[texts[text], layouts[layout]]">
|
|
3
|
+
<slot> {{ label }} </slot>
|
|
4
|
+
</component>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import type { Component } from "vue";
|
|
9
|
+
import type { HeadingLayout, HeadingText } from "../../components/heading.ts";
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
is = "h2",
|
|
13
|
+
label = undefined,
|
|
14
|
+
text = "xl",
|
|
15
|
+
layout = "left",
|
|
16
|
+
} = defineProps<{
|
|
17
|
+
is?: string | Component;
|
|
18
|
+
label?: string;
|
|
19
|
+
text?: HeadingText;
|
|
20
|
+
layout?: HeadingLayout;
|
|
21
|
+
}>();
|
|
22
|
+
|
|
23
|
+
const layouts: Record<HeadingLayout, string> = {
|
|
24
|
+
left: "text-left",
|
|
25
|
+
center: "text-center",
|
|
26
|
+
right: "text-right",
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const texts: Record<HeadingText, string> = {
|
|
30
|
+
sm: "text-sm",
|
|
31
|
+
xl: "text-xl",
|
|
32
|
+
"3xl": "text-3xl",
|
|
33
|
+
};
|
|
34
|
+
</script>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span aria-hidden="true" class="block size-[1em] shrink-0">
|
|
3
|
+
<component :is="icon" class="block size-full" />
|
|
4
|
+
</span>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import iconSets from "#ui/icons";
|
|
9
|
+
import { getIconData, iconToSVG, replaceIDs } from "@iconify/utils";
|
|
10
|
+
import { computed, defineAsyncComponent, h, markRaw } from "vue";
|
|
11
|
+
|
|
12
|
+
const { name } = defineProps<{
|
|
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
|
+
});
|
|
49
|
+
</script>
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span
|
|
3
|
+
class="flex min-h-10 w-full cursor-text items-center gap-2 border px-3 text-sm focus-within:ring-2"
|
|
4
|
+
:class="[
|
|
5
|
+
config.radius,
|
|
6
|
+
`border-${config.light.input}`,
|
|
7
|
+
`ring-${config.light.ring}`,
|
|
8
|
+
`dark:border-${config.dark.input}`,
|
|
9
|
+
`dark:ring-${config.dark.ring}`,
|
|
10
|
+
interactionVariants[interaction],
|
|
11
|
+
]"
|
|
12
|
+
>
|
|
13
|
+
<slot name="leading" />
|
|
14
|
+
|
|
15
|
+
<Icon v-if="iconPrefix" :name="iconPrefix" />
|
|
16
|
+
|
|
17
|
+
<input
|
|
18
|
+
class="w-full min-w-0 appearance-none bg-transparent py-2 outline-none placeholder:opacity-50"
|
|
19
|
+
:value="modelValue"
|
|
20
|
+
:type="type"
|
|
21
|
+
:name="name"
|
|
22
|
+
:spellcheck="spellcheck"
|
|
23
|
+
:placeholder="placeholder"
|
|
24
|
+
:required="required"
|
|
25
|
+
:readonly="readonly"
|
|
26
|
+
:disabled="disabled"
|
|
27
|
+
@input="input"
|
|
28
|
+
/>
|
|
29
|
+
|
|
30
|
+
<Icon v-if="iconSuffix" :name="iconSuffix" />
|
|
31
|
+
|
|
32
|
+
<slot name="trailing" />
|
|
33
|
+
</span>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<script setup lang="ts">
|
|
37
|
+
import config from "#config/ui";
|
|
38
|
+
import { computed } from "vue";
|
|
39
|
+
import type { ControlInteraction } from "../../components/control.ts";
|
|
40
|
+
import Icon from "./icon.vue";
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
modelValue = undefined,
|
|
44
|
+
type = "text",
|
|
45
|
+
iconPrefix = undefined,
|
|
46
|
+
iconSuffix = undefined,
|
|
47
|
+
placeholder = undefined,
|
|
48
|
+
name = undefined,
|
|
49
|
+
spellcheck = undefined,
|
|
50
|
+
required = false,
|
|
51
|
+
readonly = false,
|
|
52
|
+
disabled = false,
|
|
53
|
+
} = defineProps<{
|
|
54
|
+
modelValue?: string | number;
|
|
55
|
+
type?: string;
|
|
56
|
+
iconPrefix?: string;
|
|
57
|
+
iconSuffix?: string;
|
|
58
|
+
placeholder?: string;
|
|
59
|
+
name?: string;
|
|
60
|
+
spellcheck?: boolean;
|
|
61
|
+
required?: boolean;
|
|
62
|
+
readonly?: boolean;
|
|
63
|
+
disabled?: boolean;
|
|
64
|
+
}>();
|
|
65
|
+
|
|
66
|
+
const emit = defineEmits<{
|
|
67
|
+
"update:modelValue": [value: string | number];
|
|
68
|
+
}>();
|
|
69
|
+
|
|
70
|
+
const interaction = computed<ControlInteraction>(() => (disabled ? "disabled" : "enabled"));
|
|
71
|
+
|
|
72
|
+
const interactionVariants: Record<ControlInteraction, string> = {
|
|
73
|
+
disabled: "cursor-not-allowed opacity-50",
|
|
74
|
+
enabled: "",
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function input(event: Event) {
|
|
78
|
+
const value = (event.currentTarget as HTMLInputElement).value;
|
|
79
|
+
|
|
80
|
+
emit("update:modelValue", type === "number" ? Number(value) : value);
|
|
81
|
+
}
|
|
82
|
+
</script>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="is"
|
|
4
|
+
class="cursor-pointer underline-offset-4 transition-colors hover:underline"
|
|
5
|
+
:class="variants[variant]"
|
|
6
|
+
:href="href"
|
|
7
|
+
>
|
|
8
|
+
<slot>{{ label }}</slot>
|
|
9
|
+
</component>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import config from "#config/ui";
|
|
14
|
+
import type { Component } from "vue";
|
|
15
|
+
import type { LinkVariant } from "../../components/link.ts";
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
is = "a",
|
|
19
|
+
href = undefined,
|
|
20
|
+
label = undefined,
|
|
21
|
+
variant = "primary",
|
|
22
|
+
} = defineProps<{
|
|
23
|
+
is?: string | Component;
|
|
24
|
+
href?: string;
|
|
25
|
+
label?: string;
|
|
26
|
+
variant?: LinkVariant;
|
|
27
|
+
}>();
|
|
28
|
+
|
|
29
|
+
const variants: Record<LinkVariant, Array<string>> = {
|
|
30
|
+
primary: [`text-${config.light.foreground}`, `dark:text-${config.dark.foreground}`],
|
|
31
|
+
muted: [`text-${config.light.mutedForeground}`, `dark:text-${config.dark.mutedForeground}`],
|
|
32
|
+
};
|
|
33
|
+
</script>
|