@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.
Files changed (47) hide show
  1. package/dist/components/button.d.ts +3 -2
  2. package/dist/components/container.d.ts +2 -0
  3. package/dist/components/icon.d.ts +3 -1
  4. package/dist/components/input.d.ts +3 -2
  5. package/dist/components/navigate.d.ts +3 -2
  6. package/dist/components/separator.d.ts +1 -6
  7. package/dist/components/stack.d.ts +12 -0
  8. package/dist/components/stack.js +1 -0
  9. package/dist/index.d.ts +1 -0
  10. package/dist/module/icons.d.ts +2 -0
  11. package/dist/module/icons.js +12 -0
  12. package/dist/module/index.d.ts +1 -1
  13. package/dist/module/index.js +13 -23
  14. package/dist/style.css +7 -0
  15. package/dist/types/index.d.ts +0 -7
  16. package/dist/vue/components/breadcrumb.vue +14 -8
  17. package/dist/vue/components/button.vue +24 -19
  18. package/dist/vue/components/card.vue +21 -24
  19. package/dist/vue/components/checkbox.vue +11 -16
  20. package/dist/vue/components/collection.vue +15 -15
  21. package/dist/vue/components/container.vue +5 -6
  22. package/dist/vue/components/counter.vue +11 -14
  23. package/dist/vue/components/dialog.vue +11 -19
  24. package/dist/vue/components/field.vue +7 -16
  25. package/dist/vue/components/heading.vue +7 -8
  26. package/dist/vue/components/icon.vue +8 -42
  27. package/dist/vue/components/input.vue +18 -22
  28. package/dist/vue/components/link.vue +8 -9
  29. package/dist/vue/components/navigate.vue +2 -13
  30. package/dist/vue/components/popover.vue +57 -67
  31. package/dist/vue/components/progress.vue +9 -15
  32. package/dist/vue/components/radio.vue +9 -24
  33. package/dist/vue/components/range.vue +9 -21
  34. package/dist/vue/components/rating.vue +10 -20
  35. package/dist/vue/components/select.vue +86 -110
  36. package/dist/vue/components/separator.vue +2 -10
  37. package/dist/vue/components/sheet.vue +14 -19
  38. package/dist/vue/components/stack.vue +70 -0
  39. package/dist/vue/components/switch.vue +10 -21
  40. package/dist/vue/components/upload.vue +11 -23
  41. package/dist/vue/index.d.ts +3 -1
  42. package/dist/vue/index.js +5 -2
  43. package/dist/vue/utilities/use-focus-trap.d.ts +2 -0
  44. package/dist/vue/utilities/use-focus-trap.js +34 -0
  45. package/dist/vue/utilities/use-scroll-lock.d.ts +2 -0
  46. package/dist/vue/utilities/use-scroll-lock.js +33 -0
  47. package/package.json +5 -4
@@ -1,19 +1,13 @@
1
1
  <template>
2
2
  <dialog
3
3
  ref="dialog"
4
- class="m-auto w-[min(32rem,calc(100%-2rem))] border-0 bg-transparent p-0 backdrop:bg-black/50"
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
- <fieldset class="flex min-w-0 flex-col gap-2" :disabled="disabled">
3
- <legend v-if="label" class="mb-2 text-sm font-medium">
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
- </fieldset>
15
+ </Stack>
16
16
  </template>
17
17
 
18
18
  <script setup lang="ts">
19
- const {
20
- label = undefined,
21
- description = undefined,
22
- error = undefined,
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.ts";
9
+ import type { HeadingLayout, HeadingProps, HeadingText } from "../../components/heading";
10
10
 
11
11
  const {
12
12
  is = "h2",
13
- label = undefined,
13
+ label,
14
14
  text = "xl",
15
15
  layout = "left",
16
- } = defineProps<{
17
- is?: string | Component;
18
- label?: string;
19
- text?: HeadingText;
20
- layout?: HeadingLayout;
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 aria-hidden="true" class="block size-[1em] shrink-0">
3
- <component :is="icon" class="block size-full" />
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 iconSets from "#ui/icons";
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 { 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
- });
14
+ const { is, size } = defineProps<IconProps>();
49
15
  </script>
@@ -1,11 +1,14 @@
1
1
  <template>
2
- <span
3
- class="flex min-h-10 w-full cursor-text items-center gap-2 rounded-ui border border-input px-3 text-sm ring-ring focus-within:ring-2"
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" :name="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" :name="iconSuffix" />
26
+ <Icon v-if="iconSuffix" :is="iconSuffix" :size="iconSize" />
24
27
 
25
28
  <slot name="trailing" />
26
- </span>
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 = undefined,
40
+ modelValue,
36
41
  type = "text",
37
- iconPrefix = undefined,
38
- iconSuffix = undefined,
39
- placeholder = undefined,
40
- name = undefined,
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
- modelValue?: string | number;
47
- type?: string;
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.ts";
14
+ import type { LinkProps, LinkVariant } from "../../components/link";
15
15
 
16
16
  const {
17
17
  is = "a",
18
- href = undefined,
19
- label = undefined,
18
+ href,
19
+ label,
20
20
  variant = "primary",
21
- } = defineProps<{
22
- is?: string | Component;
23
- href?: string;
24
- label?: string;
25
- variant?: LinkVariant;
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
- :id="id"
15
- ref="popover"
16
- popover
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 { ref, useId, useTemplateRef, watchEffect } from "vue";
29
- import type { ButtonLayout } from "../../components/button.ts";
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 = undefined,
26
+ triggerClass,
36
27
  triggerLayout = "center",
37
- contentClass = "p-4",
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 popover = useTemplateRef("popover");
39
+ const isOpen = ref(modelValue);
40
+
41
+ useFocusTrap(container, isOpen);
59
42
 
60
- const position = ref<Record<string, string>>({});
43
+ function setOpen(value: boolean) {
44
+ isOpen.value = value;
45
+
46
+ emit("update:modelValue", value);
47
+ }
61
48
 
62
49
  function open() {
63
- popover.value?.showPopover();
50
+ setOpen(true);
64
51
  }
65
52
 
66
53
  function close() {
67
- popover.value?.hidePopover();
54
+ setOpen(false);
68
55
  }
69
56
 
70
57
  function toggle() {
71
- popover.value?.togglePopover();
58
+ setOpen(!isOpen.value);
72
59
  }
73
60
 
74
- function handleToggle(event: Event) {
75
- const element = event.currentTarget as HTMLElement;
76
- const open = element.matches(":popover-open");
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
- watchEffect(() => {
105
- if (modelValue && !popover.value?.matches(":popover-open")) {
106
- popover.value?.showPopover();
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
- <div class="flex flex-col gap-2">
3
- <div v-if="label" class="flex items-center justify-between gap-4 text-sm">
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
- </div>
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-full bg-input"
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-full bg-primary transition-[width]" :style="{ width: `${percent}%` }" />
19
+ <div class="h-full rounded-ui bg-primary transition-[width]" :style="{ width: `${percent}%` }" />
20
20
  </div>
21
- </div>
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 class="inline-flex items-start gap-3" :class="interactionVariants[interaction]">
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-full border transition-colors peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-ring"
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-full bg-primary" />
19
+ <span v-if="modelValue === value" class="size-2 rounded-ui bg-primary" />
20
20
  </span>
21
21
 
22
- <span class="flex flex-col gap-0.5">
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
- </span>
29
- </label>
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.ts";
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
- <div class="flex flex-col gap-2" :class="interactionVariants[interaction]">
3
- <span v-if="label" class="flex items-center justify-between gap-4 text-sm">
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
- </span>
9
+ </Stack>
10
10
 
11
11
  <div
12
12
  ref="track"
13
- class="relative h-2 w-full touch-none rounded-full bg-input outline-ring focus-visible:outline-2 focus-visible:outline-offset-2"
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-full border-2 border-primary bg-background shadow"
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
- </div>
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];