@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,11 +1,6 @@
|
|
|
1
1
|
export type SeparatorOrientation = "horizontal" | "vertical";
|
|
2
|
-
export type DividerVariant = SeparatorOrientation;
|
|
3
2
|
export interface SeparatorProps {
|
|
4
3
|
orientation?: SeparatorOrientation;
|
|
5
|
-
variant?:
|
|
6
|
-
decorative?: boolean;
|
|
7
|
-
}
|
|
8
|
-
export interface DividerProps {
|
|
9
|
-
variant?: DividerVariant;
|
|
4
|
+
variant?: SeparatorOrientation;
|
|
10
5
|
decorative?: boolean;
|
|
11
6
|
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type StackDirection = "vertical" | "horizontal";
|
|
2
|
+
export type StackGap = "0" | "1" | "3" | "5" | "7";
|
|
3
|
+
export type StackAlign = "start" | "center" | "end" | "stretch";
|
|
4
|
+
export type StackJustify = "start" | "center" | "end" | "between";
|
|
5
|
+
export interface StackProps {
|
|
6
|
+
direction?: StackDirection;
|
|
7
|
+
gap?: StackGap;
|
|
8
|
+
padding?: StackGap;
|
|
9
|
+
align?: StackAlign;
|
|
10
|
+
justify?: StackJustify;
|
|
11
|
+
wrap?: boolean;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -21,5 +21,6 @@ export type * from "./components/rating";
|
|
|
21
21
|
export type * from "./components/select";
|
|
22
22
|
export type * from "./components/separator";
|
|
23
23
|
export type * from "./components/sheet";
|
|
24
|
+
export type * from "./components/stack";
|
|
24
25
|
export type * from "./components/switch";
|
|
25
26
|
export type * from "./components/upload";
|
package/dist/module/index.js
CHANGED
|
@@ -28,7 +28,7 @@ var module_default = defineModule({
|
|
|
28
28
|
name: "ui",
|
|
29
29
|
modules: [client, icons_default],
|
|
30
30
|
config: () => ({
|
|
31
|
-
radius: "rounded-
|
|
31
|
+
radius: "rounded-none",
|
|
32
32
|
light: {
|
|
33
33
|
background: "white",
|
|
34
34
|
foreground: "zinc-950",
|
|
@@ -36,15 +36,15 @@ var module_default = defineModule({
|
|
|
36
36
|
cardForeground: "zinc-950",
|
|
37
37
|
popover: "white",
|
|
38
38
|
popoverForeground: "zinc-950",
|
|
39
|
-
primary: "
|
|
40
|
-
primaryForeground: "
|
|
39
|
+
primary: "zinc-950",
|
|
40
|
+
primaryForeground: "white",
|
|
41
41
|
secondary: "zinc-100",
|
|
42
42
|
secondaryForeground: "zinc-900",
|
|
43
43
|
muted: "zinc-100",
|
|
44
44
|
mutedForeground: "zinc-500",
|
|
45
45
|
accent: "zinc-100",
|
|
46
46
|
accentForeground: "zinc-900",
|
|
47
|
-
destructive: "red-
|
|
47
|
+
destructive: "red-500",
|
|
48
48
|
destructiveForeground: "zinc-50",
|
|
49
49
|
border: "zinc-200",
|
|
50
50
|
input: "zinc-200",
|
|
@@ -53,23 +53,23 @@ var module_default = defineModule({
|
|
|
53
53
|
dark: {
|
|
54
54
|
background: "zinc-950",
|
|
55
55
|
foreground: "zinc-50",
|
|
56
|
-
card: "zinc-
|
|
56
|
+
card: "zinc-950",
|
|
57
57
|
cardForeground: "zinc-50",
|
|
58
|
-
popover: "zinc-
|
|
58
|
+
popover: "zinc-950",
|
|
59
59
|
popoverForeground: "zinc-50",
|
|
60
|
-
primary: "
|
|
60
|
+
primary: "white",
|
|
61
61
|
primaryForeground: "zinc-950",
|
|
62
62
|
secondary: "zinc-900",
|
|
63
63
|
secondaryForeground: "zinc-50",
|
|
64
|
-
muted: "zinc-
|
|
64
|
+
muted: "zinc-900",
|
|
65
65
|
mutedForeground: "zinc-400",
|
|
66
66
|
accent: "zinc-900",
|
|
67
67
|
accentForeground: "zinc-50",
|
|
68
|
-
destructive: "red-
|
|
68
|
+
destructive: "red-500",
|
|
69
69
|
destructiveForeground: "zinc-50",
|
|
70
70
|
border: "zinc-700",
|
|
71
71
|
input: "zinc-700",
|
|
72
|
-
ring: "zinc-
|
|
72
|
+
ring: "zinc-700"
|
|
73
73
|
}
|
|
74
74
|
}),
|
|
75
75
|
setup(config) {
|
package/dist/style.css
CHANGED
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
--radius-ui: var(--ui-radius);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
@theme {
|
|
29
|
+
--shadow-sheet-top: 0 25px 50px -12px rgb(0 0 0 / 0.25);
|
|
30
|
+
--shadow-sheet-right: -25px 0 50px -12px rgb(0 0 0 / 0.25);
|
|
31
|
+
--shadow-sheet-bottom: 0 -25px 50px -12px rgb(0 0 0 / 0.25);
|
|
32
|
+
--shadow-sheet-left: 25px 0 50px -12px rgb(0 0 0 / 0.25);
|
|
33
|
+
}
|
|
34
|
+
|
|
28
35
|
@layer base {
|
|
29
36
|
* {
|
|
30
37
|
@apply border-border outline-ring;
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<nav aria-label="Breadcrumb">
|
|
3
|
-
<ol
|
|
4
|
-
<
|
|
3
|
+
<Stack is="ol" direction="horizontal" align="center" gap="3" wrap class="text-sm">
|
|
4
|
+
<Stack
|
|
5
|
+
is="li"
|
|
6
|
+
v-for="([label, href], index) in routes"
|
|
7
|
+
:key="href"
|
|
8
|
+
direction="horizontal"
|
|
9
|
+
align="center"
|
|
10
|
+
gap="3"
|
|
11
|
+
>
|
|
5
12
|
<span v-if="index" aria-hidden="true" class="opacity-40"> / </span>
|
|
6
13
|
|
|
7
14
|
<Link :href="href" :label="label" :variant="index === routes.length - 1 ? 'primary' : 'muted'" />
|
|
8
|
-
</
|
|
9
|
-
</
|
|
15
|
+
</Stack>
|
|
16
|
+
</Stack>
|
|
10
17
|
</nav>
|
|
11
18
|
</template>
|
|
12
19
|
|
|
13
20
|
<script setup lang="ts">
|
|
14
21
|
import type { BreadcrumbProps } from "../../components/breadcrumb";
|
|
15
22
|
import Link from "./link.vue";
|
|
23
|
+
import Stack from "./stack.vue";
|
|
16
24
|
|
|
17
25
|
defineProps<BreadcrumbProps>();
|
|
18
26
|
</script>
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<component
|
|
3
3
|
:is="is"
|
|
4
|
-
class="inline-flex cursor-pointer items-center justify-center gap-
|
|
4
|
+
class="inline-flex cursor-pointer items-center justify-center gap-1 rounded-ui border font-[inherit] font-medium leading-normal no-underline outline-ring transition-[background-color,border-color,color,opacity] duration-150 select-none focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:cursor-not-allowed aria-disabled:opacity-50"
|
|
5
5
|
:class="[fonts[font], layouts[layout], sizes[size], texts[text], variants[variant]]"
|
|
6
6
|
:type="attributes.type ?? 'button'"
|
|
7
7
|
:disabled="disabled"
|
|
8
8
|
>
|
|
9
|
-
<Icon v-if="iconPrefix" :is="iconPrefix" />
|
|
9
|
+
<Icon v-if="iconPrefix" :is="iconPrefix" :size="iconSize[text]" />
|
|
10
10
|
|
|
11
11
|
<slot>
|
|
12
12
|
<span v-if="label" class="truncate px-1" :class="labelLayouts[layout]">{{ label }}</span>
|
|
13
13
|
</slot>
|
|
14
14
|
|
|
15
|
-
<Icon v-if="iconSuffix" :is="iconSuffix" />
|
|
15
|
+
<Icon v-if="iconSuffix" :is="iconSuffix" :size="iconSize[text]" />
|
|
16
16
|
</component>
|
|
17
17
|
</template>
|
|
18
18
|
|
|
@@ -75,6 +75,11 @@ const texts: Record<ButtonText, string> = {
|
|
|
75
75
|
md: "text-base",
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
+
const iconSize: Record<ButtonText, number> = {
|
|
79
|
+
sm: 20,
|
|
80
|
+
md: 24,
|
|
81
|
+
};
|
|
82
|
+
|
|
78
83
|
const variants: Record<ButtonVariant, string> = {
|
|
79
84
|
primary: "border-transparent bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80",
|
|
80
85
|
secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/90 active:bg-secondary/80",
|
|
@@ -1,38 +1,41 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<section
|
|
3
|
-
|
|
4
|
-
>
|
|
5
|
-
<header
|
|
2
|
+
<Stack is="section" gap="0" class="rounded-ui border border-border bg-card text-card-foreground shadow-sm">
|
|
3
|
+
<Stack
|
|
6
4
|
v-if="label || title || description || $slots.header || $slots.actions"
|
|
7
|
-
|
|
5
|
+
is="header"
|
|
6
|
+
direction="horizontal"
|
|
7
|
+
align="center"
|
|
8
|
+
justify="between"
|
|
9
|
+
class="border-b border-border p-5"
|
|
8
10
|
>
|
|
9
11
|
<slot name="header">
|
|
10
|
-
<
|
|
12
|
+
<Stack>
|
|
11
13
|
<p v-if="label" class="text-xs font-semibold uppercase tracking-widest opacity-50">{{ label }}</p>
|
|
12
14
|
|
|
13
15
|
<h3 v-if="title" class="text-lg font-semibold leading-none">{{ title }}</h3>
|
|
14
16
|
|
|
15
17
|
<p v-if="description" class="text-sm opacity-70">{{ description }}</p>
|
|
16
|
-
</
|
|
18
|
+
</Stack>
|
|
17
19
|
</slot>
|
|
18
20
|
|
|
19
|
-
<
|
|
21
|
+
<Stack v-if="$slots.actions" direction="horizontal" align="center" class="shrink-0">
|
|
20
22
|
<slot name="actions" />
|
|
21
|
-
</
|
|
22
|
-
</
|
|
23
|
+
</Stack>
|
|
24
|
+
</Stack>
|
|
23
25
|
|
|
24
|
-
<
|
|
26
|
+
<Stack gap="5" padding="5" class="flex-1">
|
|
25
27
|
<slot />
|
|
26
|
-
</
|
|
28
|
+
</Stack>
|
|
27
29
|
|
|
28
|
-
<
|
|
30
|
+
<Stack v-if="$slots.footer" is="footer" direction="horizontal" align="center" gap="3" class="px-5 pb-5">
|
|
29
31
|
<slot name="footer" />
|
|
30
|
-
</
|
|
31
|
-
</
|
|
32
|
+
</Stack>
|
|
33
|
+
</Stack>
|
|
32
34
|
</template>
|
|
33
35
|
|
|
34
36
|
<script setup lang="ts">
|
|
35
37
|
import type { CardProps } from "../../components/card";
|
|
38
|
+
import Stack from "./stack.vue";
|
|
36
39
|
|
|
37
40
|
const { label, title, description } = defineProps<CardProps>();
|
|
38
41
|
</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="checkbox"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
<span
|
|
14
14
|
aria-hidden="true"
|
|
15
|
-
class="mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-
|
|
15
|
+
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"
|
|
16
16
|
:class="modelValue ? 'border-transparent bg-primary' : 'border-input bg-background'"
|
|
17
17
|
>
|
|
18
18
|
<span
|
|
@@ -21,19 +21,20 @@
|
|
|
21
21
|
/>
|
|
22
22
|
</span>
|
|
23
23
|
|
|
24
|
-
<span v-if="label || description || $slots.default"
|
|
24
|
+
<Stack is="span" v-if="label || description || $slots.default" gap="0">
|
|
25
25
|
<span class="text-sm font-medium"
|
|
26
26
|
><slot>{{ label }}</slot></span
|
|
27
27
|
>
|
|
28
28
|
<span v-if="description" class="text-sm opacity-60">{{ description }}</span>
|
|
29
|
-
</
|
|
30
|
-
</
|
|
29
|
+
</Stack>
|
|
30
|
+
</Stack>
|
|
31
31
|
</template>
|
|
32
32
|
|
|
33
33
|
<script setup lang="ts">
|
|
34
34
|
import { computed } from "vue";
|
|
35
35
|
import type { CheckboxProps } from "../../components/checkbox";
|
|
36
36
|
import type { ControlInteraction } from "../../components/control.ts";
|
|
37
|
+
import Stack from "./stack.vue";
|
|
37
38
|
|
|
38
39
|
const {
|
|
39
40
|
modelValue = false,
|
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<Stack
|
|
3
3
|
v-if="items"
|
|
4
|
-
|
|
4
|
+
gap="0"
|
|
5
|
+
class="w-full divide-y divide-border overflow-hidden rounded-ui border border-border bg-card"
|
|
5
6
|
>
|
|
6
7
|
<template v-if="items.length">
|
|
7
8
|
<div v-for="(item, index) in items" :key="index">
|
|
8
9
|
<slot :item="item" :index="index" />
|
|
9
10
|
</div>
|
|
10
11
|
</template>
|
|
11
|
-
<
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
<Stack v-else align="center" padding="5" class="text-sm opacity-60">
|
|
13
|
+
<Icon :is="SearchIcon" class="text-2xl" />
|
|
14
|
+
|
|
15
|
+
<span>{{ emptyLabel }}</span>
|
|
16
|
+
</Stack>
|
|
17
|
+
</Stack>
|
|
18
|
+
<div v-else class="grid gap-5" :class="columnsClass[columns]">
|
|
14
19
|
<slot />
|
|
15
20
|
</div>
|
|
16
21
|
</template>
|
|
17
22
|
|
|
18
23
|
<script setup generic="T" lang="ts">
|
|
24
|
+
import SearchIcon from "~icons/hugeicons/search-01";
|
|
19
25
|
import type { CollectionColumns, CollectionProps } from "../../components/collection";
|
|
26
|
+
import Icon from "./icon.vue";
|
|
27
|
+
import Stack from "./stack.vue";
|
|
20
28
|
|
|
21
29
|
const { items, columns = 1, emptyLabel = "Nothing found" } = defineProps<CollectionProps<T>>();
|
|
22
30
|
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<Stack gap="5" :padding="padding" :class="sizes[size]" class="mx-auto w-full">
|
|
3
3
|
<slot />
|
|
4
|
-
</
|
|
4
|
+
</Stack>
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<script setup lang="ts">
|
|
8
8
|
import type { ContainerProps, ContainerSize } from "../../components/container";
|
|
9
|
+
import Stack from "./stack.vue";
|
|
9
10
|
|
|
10
|
-
const { size = "5xl" } = defineProps<ContainerProps>();
|
|
11
|
+
const { size = "5xl", padding = "5" } = defineProps<ContainerProps>();
|
|
11
12
|
|
|
12
13
|
const sizes: Record<ContainerSize, string> = {
|
|
13
14
|
"3xl": "max-w-3xl",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<
|
|
2
|
+
<Stack>
|
|
3
3
|
<span v-if="label" class="text-sm font-medium">{{ label }}</span>
|
|
4
4
|
|
|
5
5
|
<div
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
@click="change(-step)"
|
|
16
16
|
/>
|
|
17
17
|
|
|
18
|
-
<output class="px-
|
|
18
|
+
<output class="px-1 text-center tabular-nums">{{ modelValue }}</output>
|
|
19
19
|
|
|
20
20
|
<Button
|
|
21
21
|
class="text-lg"
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
@click="change(step)"
|
|
28
28
|
/>
|
|
29
29
|
</div>
|
|
30
|
-
</
|
|
30
|
+
</Stack>
|
|
31
31
|
</template>
|
|
32
32
|
|
|
33
33
|
<script setup lang="ts">
|
|
@@ -35,6 +35,7 @@ import AddIcon from "~icons/hugeicons/add-01";
|
|
|
35
35
|
import MinusIcon from "~icons/hugeicons/minus-sign";
|
|
36
36
|
import type { CounterProps } from "../../components/counter";
|
|
37
37
|
import Button from "./button.vue";
|
|
38
|
+
import Stack from "./stack.vue";
|
|
38
39
|
|
|
39
40
|
const {
|
|
40
41
|
modelValue = 0,
|
|
@@ -1,7 +1,7 @@
|
|
|
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
|
>
|
|
@@ -24,6 +24,8 @@ import { useTemplateRef, watchEffect } from "vue";
|
|
|
24
24
|
import type { DialogProps } from "../../components/dialog";
|
|
25
25
|
import Button from "./button.vue";
|
|
26
26
|
import Card from "./card.vue";
|
|
27
|
+
import { useFocusTrap } from "../utilities/use-focus-trap";
|
|
28
|
+
import { useScrollLock } from "../utilities/use-scroll-lock";
|
|
27
29
|
|
|
28
30
|
const { modelValue = false, title, description, closeLabel = "Close" } = defineProps<DialogProps>();
|
|
29
31
|
|
|
@@ -33,6 +35,10 @@ const emit = defineEmits<{
|
|
|
33
35
|
|
|
34
36
|
const dialog = useTemplateRef("dialog");
|
|
35
37
|
|
|
38
|
+
useFocusTrap(dialog, () => modelValue);
|
|
39
|
+
|
|
40
|
+
useScrollLock(() => modelValue);
|
|
41
|
+
|
|
36
42
|
function open() {
|
|
37
43
|
emit("update:modelValue", true);
|
|
38
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,11 +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
19
|
import type { FieldProps } from "../../components/field";
|
|
20
|
+
import Stack from "./stack.vue";
|
|
20
21
|
|
|
21
22
|
const { label, description, error, required = false, disabled = false } = defineProps<FieldProps>();
|
|
22
23
|
</script>
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<span
|
|
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
|
+
>
|
|
3
7
|
<component :is="is" class="block size-full" />
|
|
4
8
|
</span>
|
|
5
9
|
</template>
|
|
@@ -7,5 +11,5 @@
|
|
|
7
11
|
<script setup lang="ts">
|
|
8
12
|
import type { IconProps } from "../../components/icon";
|
|
9
13
|
|
|
10
|
-
const { is } = defineProps<IconProps>();
|
|
14
|
+
const { is, size } = defineProps<IconProps>();
|
|
11
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" :is="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,10 +23,10 @@
|
|
|
20
23
|
@input="input"
|
|
21
24
|
/>
|
|
22
25
|
|
|
23
|
-
<Icon v-if="iconSuffix" :is="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">
|
|
@@ -31,6 +34,7 @@ import { computed } from "vue";
|
|
|
31
34
|
import type { ControlInteraction } from "../../components/control.ts";
|
|
32
35
|
import type { InputProps } from "../../components/input";
|
|
33
36
|
import Icon from "./icon.vue";
|
|
37
|
+
import Stack from "./stack.vue";
|
|
34
38
|
|
|
35
39
|
const {
|
|
36
40
|
modelValue,
|
|
@@ -45,6 +49,8 @@ const {
|
|
|
45
49
|
disabled = false,
|
|
46
50
|
} = defineProps<InputProps>();
|
|
47
51
|
|
|
52
|
+
const iconSize = 20;
|
|
53
|
+
|
|
48
54
|
const emit = defineEmits<{
|
|
49
55
|
"update:modelValue": [value: string | number];
|
|
50
56
|
}>();
|
|
@@ -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,16 +15,17 @@
|
|
|
25
15
|
</template>
|
|
26
16
|
|
|
27
17
|
<script setup lang="ts">
|
|
28
|
-
import {
|
|
18
|
+
import { onUnmounted, ref, useTemplateRef, watch } from "vue";
|
|
29
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
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
31
|
} = defineProps<PopoverProps>();
|
|
@@ -43,62 +34,69 @@ const emit = defineEmits<{
|
|
|
43
34
|
"update:modelValue": [value: boolean];
|
|
44
35
|
}>();
|
|
45
36
|
|
|
46
|
-
const id = `popover-${useId()}`;
|
|
47
|
-
|
|
48
37
|
const container = useTemplateRef("container");
|
|
49
38
|
|
|
50
|
-
const
|
|
39
|
+
const isOpen = ref(modelValue);
|
|
40
|
+
|
|
41
|
+
useFocusTrap(container, isOpen);
|
|
42
|
+
|
|
43
|
+
function setOpen(value: boolean) {
|
|
44
|
+
isOpen.value = value;
|
|
51
45
|
|
|
52
|
-
|
|
46
|
+
emit("update:modelValue", value);
|
|
47
|
+
}
|
|
53
48
|
|
|
54
49
|
function open() {
|
|
55
|
-
|
|
50
|
+
setOpen(true);
|
|
56
51
|
}
|
|
57
52
|
|
|
58
53
|
function close() {
|
|
59
|
-
|
|
54
|
+
setOpen(false);
|
|
60
55
|
}
|
|
61
56
|
|
|
62
57
|
function toggle() {
|
|
63
|
-
|
|
58
|
+
setOpen(!isOpen.value);
|
|
64
59
|
}
|
|
65
60
|
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (open) {
|
|
71
|
-
const trigger = container.value?.querySelector("button");
|
|
72
|
-
|
|
73
|
-
if (trigger) {
|
|
74
|
-
const bounds = trigger.getBoundingClientRect();
|
|
75
|
-
const width = matchTriggerWidth ? bounds.width : element.offsetWidth;
|
|
76
|
-
const left = Math.min(Math.max(8, bounds.left), window.innerWidth - width - 8);
|
|
77
|
-
const top =
|
|
78
|
-
bounds.bottom + element.offsetHeight + 8 <= window.innerHeight
|
|
79
|
-
? bounds.bottom + 8
|
|
80
|
-
: bounds.top - element.offsetHeight - 8;
|
|
81
|
-
|
|
82
|
-
position.value = {
|
|
83
|
-
bottom: "auto",
|
|
84
|
-
left: `${left}px`,
|
|
85
|
-
right: "auto",
|
|
86
|
-
top: `${Math.max(8, top)}px`,
|
|
87
|
-
position: "fixed",
|
|
88
|
-
width: matchTriggerWidth ? `${width}px` : "auto",
|
|
89
|
-
};
|
|
90
|
-
}
|
|
61
|
+
function handlePointerDown(event: PointerEvent) {
|
|
62
|
+
if (!container.value?.contains(event.target as Node)) {
|
|
63
|
+
close();
|
|
91
64
|
}
|
|
92
|
-
|
|
93
|
-
emit("update:modelValue", open);
|
|
94
65
|
}
|
|
95
66
|
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
|
|
99
|
-
} else if (!modelValue && popover.value?.matches(":popover-open")) {
|
|
100
|
-
popover.value.hidePopover();
|
|
67
|
+
function handleKeyDown(event: KeyboardEvent) {
|
|
68
|
+
if (event.key === "Escape") {
|
|
69
|
+
close();
|
|
101
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);
|
|
102
100
|
});
|
|
103
101
|
|
|
104
102
|
defineExpose({ open, close, toggle });
|