@revojs/ui 0.2.11 → 0.2.12

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.
@@ -1,5 +1,3 @@
1
1
  export interface CardProps {
2
2
  label?: string;
3
- title?: string;
4
- description?: string;
5
3
  }
@@ -1,6 +1,5 @@
1
1
  export interface DialogProps {
2
2
  modelValue?: boolean;
3
- title?: string;
4
- description?: string;
3
+ label?: string;
5
4
  closeLabel?: string;
6
5
  }
@@ -1,7 +1,9 @@
1
+ export type FieldText = "sm" | "md";
1
2
  export interface FieldProps {
2
3
  label?: string;
3
4
  description?: string;
4
5
  error?: string;
6
+ text?: FieldText;
5
7
  required?: boolean;
6
8
  disabled?: boolean;
7
9
  }
@@ -1,7 +1,9 @@
1
1
  import type { IconComponent } from "./icon";
2
+ export type InputText = "sm" | "md";
2
3
  export interface InputProps {
3
4
  modelValue?: string | number;
4
5
  type?: string;
6
+ text?: InputText;
5
7
  iconPrefix?: IconComponent;
6
8
  iconSuffix?: IconComponent;
7
9
  placeholder?: string;
@@ -2,7 +2,6 @@ export type SheetSide = "top" | "right" | "bottom" | "left";
2
2
  export interface SheetProps {
3
3
  modelValue?: boolean;
4
4
  side?: SheetSide;
5
- title?: string;
6
- description?: string;
5
+ label?: string;
7
6
  closeLabel?: string;
8
7
  }
@@ -1,8 +1,10 @@
1
1
  export type UploadState = "idle" | "over";
2
+ export type UploadText = "sm" | "md";
2
3
  export interface UploadProps {
3
4
  modelValue?: Blob | File;
4
5
  label?: string;
5
6
  description?: string;
7
+ text?: UploadText;
6
8
  accept?: string;
7
9
  name?: string;
8
10
  required?: boolean;
@@ -13,14 +13,22 @@ export interface UiTheme {
13
13
  popover: TailwindColor;
14
14
  popoverForeground: TailwindColor;
15
15
  primary: TailwindColor;
16
+ primaryHover: TailwindColor;
17
+ primaryActive: TailwindColor;
16
18
  primaryForeground: TailwindColor;
17
19
  secondary: TailwindColor;
20
+ secondaryHover: TailwindColor;
21
+ secondaryActive: TailwindColor;
18
22
  secondaryForeground: TailwindColor;
19
23
  muted: TailwindColor;
20
24
  mutedForeground: TailwindColor;
21
25
  accent: TailwindColor;
22
26
  accentForeground: TailwindColor;
27
+ ghostHover: TailwindColor;
28
+ ghostActive: TailwindColor;
23
29
  destructive: TailwindColor;
30
+ destructiveHover: TailwindColor;
31
+ destructiveActive: TailwindColor;
24
32
  destructiveForeground: TailwindColor;
25
33
  border: TailwindColor;
26
34
  input: TailwindColor;
@@ -37,14 +37,22 @@ var module_default = defineModule({
37
37
  popover: "white",
38
38
  popoverForeground: "zinc-950",
39
39
  primary: "zinc-950",
40
+ primaryHover: "zinc-900",
41
+ primaryActive: "zinc-800",
40
42
  primaryForeground: "white",
41
43
  secondary: "zinc-100",
44
+ secondaryHover: "zinc-200",
45
+ secondaryActive: "zinc-300",
42
46
  secondaryForeground: "zinc-900",
43
47
  muted: "zinc-100",
44
48
  mutedForeground: "zinc-500",
45
49
  accent: "zinc-100",
46
50
  accentForeground: "zinc-900",
51
+ ghostHover: "zinc-100",
52
+ ghostActive: "zinc-200",
47
53
  destructive: "red-500",
54
+ destructiveHover: "red-600",
55
+ destructiveActive: "red-700",
48
56
  destructiveForeground: "zinc-50",
49
57
  border: "zinc-200",
50
58
  input: "zinc-200",
@@ -58,14 +66,22 @@ var module_default = defineModule({
58
66
  popover: "zinc-950",
59
67
  popoverForeground: "zinc-50",
60
68
  primary: "white",
69
+ primaryHover: "zinc-100",
70
+ primaryActive: "zinc-200",
61
71
  primaryForeground: "zinc-950",
62
72
  secondary: "zinc-900",
73
+ secondaryHover: "zinc-800",
74
+ secondaryActive: "zinc-700",
63
75
  secondaryForeground: "zinc-50",
64
76
  muted: "zinc-900",
65
77
  mutedForeground: "zinc-400",
66
78
  accent: "zinc-900",
67
79
  accentForeground: "zinc-50",
80
+ ghostHover: "zinc-900",
81
+ ghostActive: "zinc-800",
68
82
  destructive: "red-500",
83
+ destructiveHover: "red-600",
84
+ destructiveActive: "red-700",
69
85
  destructiveForeground: "zinc-50",
70
86
  border: "zinc-700",
71
87
  input: "zinc-700",
package/dist/style.css CHANGED
@@ -10,14 +10,22 @@
10
10
  --color-popover: var(--ui-popover);
11
11
  --color-popover-foreground: var(--ui-popover-foreground);
12
12
  --color-primary: var(--ui-primary);
13
+ --color-primary-hover: var(--ui-primary-hover);
14
+ --color-primary-active: var(--ui-primary-active);
13
15
  --color-primary-foreground: var(--ui-primary-foreground);
14
16
  --color-secondary: var(--ui-secondary);
17
+ --color-secondary-hover: var(--ui-secondary-hover);
18
+ --color-secondary-active: var(--ui-secondary-active);
15
19
  --color-secondary-foreground: var(--ui-secondary-foreground);
16
20
  --color-muted: var(--ui-muted);
17
21
  --color-muted-foreground: var(--ui-muted-foreground);
18
22
  --color-accent: var(--ui-accent);
19
23
  --color-accent-foreground: var(--ui-accent-foreground);
24
+ --color-ghost-hover: var(--ui-ghost-hover);
25
+ --color-ghost-active: var(--ui-ghost-active);
20
26
  --color-destructive: var(--ui-destructive);
27
+ --color-destructive-hover: var(--ui-destructive-hover);
28
+ --color-destructive-active: var(--ui-destructive-active);
21
29
  --color-destructive-foreground: var(--ui-destructive-foreground);
22
30
  --color-border: var(--ui-border);
23
31
  --color-input: var(--ui-input);
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <component
3
3
  :is="is"
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"
4
+ class="inline-flex shrink-0 cursor-pointer items-center justify-center gap-1 truncate rounded-ui font-[inherit] no-underline outline-ring 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"
@@ -33,7 +33,7 @@ const {
33
33
  iconPrefix,
34
34
  iconSuffix,
35
35
  label,
36
- font = "medium",
36
+ font = "semibold",
37
37
  layout = "center",
38
38
  size = "default",
39
39
  text = "sm",
@@ -66,8 +66,8 @@ const labelLayouts: Record<ButtonLayout, string> = {
66
66
  };
67
67
 
68
68
  const sizes: Record<ButtonSize, string> = {
69
- default: "min-h-10 px-4 py-2",
70
- icon: "size-10 shrink-0 p-0",
69
+ default: "p-3",
70
+ icon: "p-3",
71
71
  };
72
72
 
73
73
  const texts: Record<ButtonText, string> = {
@@ -81,12 +81,11 @@ const iconSize: Record<ButtonText, number> = {
81
81
  };
82
82
 
83
83
  const variants: Record<ButtonVariant, string> = {
84
- primary: "border-transparent bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80",
85
- secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/90 active:bg-secondary/80",
84
+ primary: "bg-primary text-primary-foreground hover:bg-primary-hover active:bg-primary-active",
85
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary-hover active:bg-secondary-active",
86
86
  outline:
87
- "border-transparent bg-secondary text-secondary-foreground ring-1 ring-border ring-inset hover:bg-secondary/90 active:bg-secondary/80",
88
- ghost: "border-transparent bg-transparent text-foreground hover:bg-accent active:bg-accent/80",
89
- destructive:
90
- "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/90 active:bg-destructive/80",
87
+ "bg-secondary text-secondary-foreground ring-1 ring-border ring-inset hover:bg-secondary-hover active:bg-secondary-active",
88
+ ghost: "bg-transparent text-foreground hover:bg-ghost-hover active:bg-ghost-active",
89
+ destructive: "bg-destructive text-destructive-foreground hover:bg-destructive-hover active:bg-destructive-active",
91
90
  };
92
91
  </script>
@@ -1,33 +1,27 @@
1
1
  <template>
2
- <Stack is="section" gap="0" class="rounded-ui border border-border bg-card text-card-foreground shadow-sm">
2
+ <Stack is="section" gap="0" class="w-full rounded-ui border border-border bg-card text-card-foreground">
3
3
  <Stack
4
- v-if="label || title || description || $slots.header || $slots.actions"
4
+ v-if="label || $slots.header || $slots.actions"
5
5
  is="header"
6
6
  direction="horizontal"
7
7
  align="center"
8
8
  justify="between"
9
- class="border-b border-border p-5"
9
+ class="shrink-0 border-b border-border"
10
10
  >
11
11
  <slot name="header">
12
- <Stack>
13
- <p v-if="label" class="text-xs font-semibold uppercase tracking-widest opacity-50">{{ label }}</p>
14
-
15
- <h3 v-if="title" class="text-lg font-semibold leading-none">{{ title }}</h3>
16
-
17
- <p v-if="description" class="text-sm opacity-70">{{ description }}</p>
18
- </Stack>
12
+ <span v-if="label" class="min-w-0 flex-1 px-5 py-3 text-sm/5 font-normal"> {{ label }} </span>
19
13
  </slot>
20
14
 
21
- <Stack v-if="$slots.actions" direction="horizontal" align="center" class="shrink-0">
15
+ <Stack v-if="$slots.actions" direction="horizontal" align="center" class="ml-auto shrink-0">
22
16
  <slot name="actions" />
23
17
  </Stack>
24
18
  </Stack>
25
19
 
26
- <Stack gap="5" padding="5" class="flex-1">
20
+ <Stack gap="5" padding="5" class="min-h-0 flex-1">
27
21
  <slot />
28
22
  </Stack>
29
23
 
30
- <Stack v-if="$slots.footer" is="footer" direction="horizontal" align="center" gap="3" class="px-5 pb-5">
24
+ <Stack v-if="$slots.footer" is="footer" direction="horizontal" align="center" class="shrink-0 px-5 pb-5">
31
25
  <slot name="footer" />
32
26
  </Stack>
33
27
  </Stack>
@@ -37,5 +31,5 @@
37
31
  import type { CardProps } from "../../components/card";
38
32
  import Stack from "./stack.vue";
39
33
 
40
- const { label, title, description } = defineProps<CardProps>();
34
+ const { label } = defineProps<CardProps>();
41
35
  </script>
@@ -1,5 +1,12 @@
1
1
  <template>
2
- <Stack is="label" direction="horizontal" align="start" gap="3" :class="interactionVariants[interaction]">
2
+ <Stack
3
+ is="label"
4
+ direction="horizontal"
5
+ align="start"
6
+ gap="3"
7
+ class="group"
8
+ :class="interactionVariants[interaction]"
9
+ >
3
10
  <input
4
11
  class="peer sr-only"
5
12
  type="checkbox"
@@ -12,8 +19,15 @@
12
19
 
13
20
  <span
14
21
  aria-hidden="true"
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
- :class="modelValue ? 'border-transparent bg-primary' : 'border-input bg-background'"
22
+ class="mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-ui border peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-ring"
23
+ :class="[
24
+ modelValue ? 'border-transparent bg-primary' : 'border-input bg-background',
25
+ disabled
26
+ ? ''
27
+ : modelValue
28
+ ? 'group-hover:bg-primary-hover group-active:bg-primary-active'
29
+ : 'group-hover:bg-secondary-hover group-active:bg-secondary-active',
30
+ ]"
17
31
  >
18
32
  <span
19
33
  v-if="modelValue"
@@ -22,7 +36,7 @@
22
36
  </span>
23
37
 
24
38
  <Stack is="span" v-if="label || description || $slots.default" gap="0">
25
- <span class="text-sm font-medium"
39
+ <span class="text-sm font-normal"
26
40
  ><slot>{{ label }}</slot></span
27
41
  >
28
42
  <span v-if="description" class="text-sm opacity-60">{{ description }}</span>
@@ -9,7 +9,7 @@
9
9
  <slot :item="item" :index="index" />
10
10
  </div>
11
11
  </template>
12
- <Stack v-else align="center" padding="5" class="text-sm opacity-60">
12
+ <Stack v-else align="center" padding="5">
13
13
  <Icon :is="SearchIcon" class="text-2xl" />
14
14
 
15
15
  <span>{{ emptyLabel }}</span>
@@ -1,5 +1,10 @@
1
1
  <template>
2
- <Stack gap="5" :padding="padding" :class="sizes[size]" class="mx-auto w-full">
2
+ <Stack
3
+ gap="5"
4
+ :padding="padding"
5
+ :class="sizes[size]"
6
+ class="mx-auto w-full overflow-y-scroll [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
7
+ >
3
8
  <slot />
4
9
  </Stack>
5
10
  </template>
@@ -1,10 +1,8 @@
1
1
  <template>
2
2
  <Stack>
3
- <span v-if="label" class="text-sm font-medium">{{ label }}</span>
3
+ <span v-if="label" class="text-sm font-normal">{{ label }}</span>
4
4
 
5
- <div
6
- class="grid h-10 grid-cols-[2.5rem_minmax(0,1fr)_2.5rem] items-center overflow-hidden rounded-ui border border-input"
7
- >
5
+ <div class="relative grid grid-cols-[auto_minmax(0,1fr)_auto] items-center overflow-hidden rounded-ui">
8
6
  <Button
9
7
  class="text-lg"
10
8
  size="icon"
@@ -26,6 +24,11 @@
26
24
  aria-label="Increase"
27
25
  @click="change(step)"
28
26
  />
27
+
28
+ <span
29
+ aria-hidden="true"
30
+ class="pointer-events-none absolute inset-0 rounded-ui ring-1 ring-input ring-inset"
31
+ />
29
32
  </div>
30
33
  </Stack>
31
34
  </template>
@@ -1,11 +1,11 @@
1
1
  <template>
2
2
  <dialog
3
3
  ref="dialog"
4
- class="m-auto w-full max-w-lg border-0 bg-transparent p-5 backdrop:bg-black/50"
4
+ class="fixed inset-0 m-0 h-full max-h-none w-full max-w-none flex-col items-center overflow-y-auto border-0 bg-transparent p-5 backdrop:bg-black/50 open:flex [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
5
5
  @close="$emit('update:modelValue', false)"
6
6
  @click="$event.target === $event.currentTarget && ($event.currentTarget as HTMLDialogElement).close()"
7
7
  >
8
- <Card :title="title" :description="description" class="shadow-2xl">
8
+ <Card :label="label" class="my-auto max-w-3xl shrink-0 shadow-2xl">
9
9
  <template #actions>
10
10
  <Button size="icon" variant="ghost" :icon-prefix="CloseIcon" :aria-label="closeLabel" @click="close" />
11
11
  </template>
@@ -27,7 +27,7 @@ import Card from "./card.vue";
27
27
  import { useFocusTrap } from "../utilities/use-focus-trap";
28
28
  import { useScrollLock } from "../utilities/use-scroll-lock";
29
29
 
30
- const { modelValue = false, title, description, closeLabel = "Close" } = defineProps<DialogProps>();
30
+ const { modelValue = false, label, closeLabel = "Close" } = defineProps<DialogProps>();
31
31
 
32
32
  const emit = defineEmits<{
33
33
  "update:modelValue": [value: boolean];
@@ -1,23 +1,28 @@
1
1
  <template>
2
- <Stack is="fieldset" :disabled="disabled">
3
- <legend v-if="label" class="mb-1 text-sm font-medium">
4
- {{ label }}<span v-if="required" aria-hidden="true"> *</span>
2
+ <Stack is="fieldset" :disabled="disabled" :class="texts[text]">
3
+ <legend v-if="label" class="mb-1 font-normal">
4
+ {{ label }}<span v-if="required" aria-hidden="true" class="text-destructive"> *</span>
5
5
  </legend>
6
6
 
7
7
  <slot />
8
8
 
9
- <p v-if="error" class="text-sm text-destructive">
9
+ <p v-if="error" class="text-destructive">
10
10
  {{ error }}
11
11
  </p>
12
- <p v-else-if="description" class="text-sm opacity-60">
12
+ <p v-else-if="description" class="opacity-60">
13
13
  {{ description }}
14
14
  </p>
15
15
  </Stack>
16
16
  </template>
17
17
 
18
18
  <script setup lang="ts">
19
- import type { FieldProps } from "../../components/field";
19
+ import type { FieldProps, FieldText } from "../../components/field";
20
20
  import Stack from "./stack.vue";
21
21
 
22
- const { label, description, error, required = false, disabled = false } = defineProps<FieldProps>();
22
+ const { label, description, error, text = "sm", required = false, disabled = false } = defineProps<FieldProps>();
23
+
24
+ const texts: Record<FieldText, string> = {
25
+ sm: "text-sm",
26
+ md: "text-base",
27
+ };
23
28
  </script>
@@ -3,15 +3,16 @@
3
3
  is="span"
4
4
  direction="horizontal"
5
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"
7
- :class="interactionVariants[interaction]"
6
+ gap="0"
7
+ class="w-full cursor-text rounded-ui bg-secondary p-3 ring-1 ring-input ring-inset focus-within:ring-2 focus-within:ring-ring"
8
+ :class="[texts[text], interactionVariants[interaction]]"
8
9
  >
9
10
  <slot name="leading" />
10
11
 
11
12
  <Icon v-if="iconPrefix" :is="iconPrefix" :size="iconSize" />
12
13
 
13
14
  <input
14
- class="w-full min-w-0 appearance-none bg-transparent py-2 outline-none placeholder:opacity-50"
15
+ class="w-full min-w-0 appearance-none bg-transparent px-1 outline-none placeholder:opacity-50"
15
16
  :value="modelValue"
16
17
  :type="type"
17
18
  :name="name"
@@ -32,13 +33,14 @@
32
33
  <script setup lang="ts">
33
34
  import { computed } from "vue";
34
35
  import type { ControlInteraction } from "../../components/control.ts";
35
- import type { InputProps } from "../../components/input";
36
+ import type { InputProps, InputText } from "../../components/input";
36
37
  import Icon from "./icon.vue";
37
38
  import Stack from "./stack.vue";
38
39
 
39
40
  const {
40
41
  modelValue,
41
42
  type = "text",
43
+ text = "sm",
42
44
  iconPrefix,
43
45
  iconSuffix,
44
46
  placeholder,
@@ -49,7 +51,12 @@ const {
49
51
  disabled = false,
50
52
  } = defineProps<InputProps>();
51
53
 
52
- const iconSize = 20;
54
+ const iconSize = computed(() => (text === "sm" ? 20 : 24));
55
+
56
+ const texts: Record<InputText, string> = {
57
+ sm: "text-sm",
58
+ md: "text-base",
59
+ };
53
60
 
54
61
  const emit = defineEmits<{
55
62
  "update:modelValue": [value: string | number];
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <component
3
3
  :is="is"
4
- class="cursor-pointer underline-offset-4 transition-colors hover:underline"
4
+ class="cursor-pointer underline-offset-4 hover:underline"
5
5
  :class="variants[variant]"
6
6
  :href="href"
7
7
  >
@@ -1,12 +1,16 @@
1
1
  <template>
2
- <div ref="container" class="relative inline-flex">
2
+ <div ref="container" class="relative w-full">
3
3
  <Button variant="outline" :class="triggerClass" :layout="triggerLayout" :disabled="disabled" @click="toggle">
4
- <slot name="trigger" :open="open" :close="close" :toggle="toggle">{{ triggerLabel }}</slot>
4
+ <slot name="trigger" :open="open" :close="close" :toggle="toggle">
5
+ <span class="truncate px-1" :class="triggerLayout === 'center' ? '' : 'flex-1'">{{
6
+ triggerLabel
7
+ }}</span>
8
+ </slot>
5
9
  </Button>
6
10
 
7
11
  <div
8
12
  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"
13
+ class="absolute left-0 top-full z-10 mt-1 max-w-xs rounded-ui border border-border bg-popover text-popover-foreground shadow-xl"
10
14
  :class="[contentClass, matchTriggerWidth ? 'w-full' : '']"
11
15
  >
12
16
  <slot :open="open" :close="close" :toggle="toggle" />
@@ -26,7 +30,7 @@ const {
26
30
  triggerClass,
27
31
  triggerLayout = "center",
28
32
  contentClass = "flex flex-col gap-1 p-1",
29
- matchTriggerWidth = false,
33
+ matchTriggerWidth = true,
30
34
  disabled = false,
31
35
  } = defineProps<PopoverProps>();
32
36
 
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <Stack>
3
3
  <Stack v-if="label" direction="horizontal" align="center" justify="between" gap="1" class="text-sm">
4
- <span class="font-medium">{{ label }}</span>
4
+ <span class="font-normal">{{ label }}</span>
5
5
 
6
6
  <span class="tabular-nums opacity-60">{{ Math.round(percent) }}%</span>
7
7
  </Stack>
@@ -16,7 +16,7 @@
16
16
  aria-valuemin="0"
17
17
  :aria-valuemax="max"
18
18
  >
19
- <div class="h-full rounded-ui bg-primary transition-[width]" :style="{ width: `${percent}%` }" />
19
+ <div class="h-full rounded-ui bg-primary" :style="{ width: `${percent}%` }" />
20
20
  </div>
21
21
  </Stack>
22
22
  </template>
@@ -1,5 +1,12 @@
1
1
  <template>
2
- <Stack is="label" direction="horizontal" align="start" gap="3" :class="interactionVariants[interaction]">
2
+ <Stack
3
+ is="label"
4
+ direction="horizontal"
5
+ align="start"
6
+ gap="3"
7
+ class="group"
8
+ :class="interactionVariants[interaction]"
9
+ >
3
10
  <input
4
11
  class="peer sr-only"
5
12
  type="radio"
@@ -13,14 +20,21 @@
13
20
 
14
21
  <span
15
22
  aria-hidden="true"
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
- :class="modelValue === value ? 'border-primary bg-background' : 'border-input bg-background'"
23
+ class="mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-ui border peer-focus-visible:outline-2 peer-focus-visible:outline-offset-2 peer-focus-visible:outline-ring"
24
+ :class="[
25
+ modelValue === value ? 'border-primary bg-background' : 'border-input bg-background',
26
+ disabled ? '' : 'group-hover:bg-secondary-hover group-active:bg-secondary-active',
27
+ ]"
18
28
  >
19
- <span v-if="modelValue === value" class="size-2 rounded-ui bg-primary" />
29
+ <span
30
+ v-if="modelValue === value"
31
+ class="size-2 rounded-ui bg-primary"
32
+ :class="disabled ? '' : 'group-hover:bg-secondary-foreground group-active:bg-secondary-foreground'"
33
+ />
20
34
  </span>
21
35
 
22
36
  <Stack is="span" gap="0">
23
- <span class="text-sm font-medium">
37
+ <span class="text-sm font-normal">
24
38
  <slot> {{ label }}</slot>
25
39
  </span>
26
40
 
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <Stack :class="interactionVariants[interaction]">
3
3
  <Stack v-if="label" direction="horizontal" align="center" justify="between" gap="1" class="text-sm">
4
- <span class="font-medium"> {{ label }} </span>
4
+ <span class="font-normal"> {{ label }} </span>
5
5
 
6
6
  <output class="tabular-nums opacity-60">
7
7
  {{ modelValue }}
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <Stack>
3
- <span v-if="label" class="text-sm font-medium">{{ label }}</span>
3
+ <span v-if="label" class="text-sm font-normal">{{ label }}</span>
4
4
 
5
5
  <Stack direction="horizontal" align="center" gap="0" role="radiogroup" :aria-label="label">
6
6
  <Button
@@ -16,7 +16,7 @@
16
16
  :disabled="readonly || disabled"
17
17
  @click="$emit('update:modelValue', value)"
18
18
  >
19
- <Icon :is="StarIcon" />
19
+ <Icon :is="StarIcon" :class="{ '[&_path]:fill-current': value <= modelValue }" />
20
20
  </Button>
21
21
  </Stack>
22
22
  </Stack>
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <Stack>
3
- <span v-if="label" class="text-sm font-medium">
3
+ <span v-if="label" class="text-sm font-normal">
4
4
  {{ label }}
5
5
  </span>
6
6
 
@@ -10,22 +10,26 @@
10
10
  <Stack
11
11
  direction="horizontal"
12
12
  align="center"
13
- class="min-h-10 w-full cursor-text rounded-ui border border-input px-3 text-sm ring-ring focus-within:ring-2"
13
+ class="w-full cursor-text rounded-ui bg-secondary p-3 text-sm font-semibold ring-1 ring-input ring-inset focus-within:ring-2 focus-within:ring-ring"
14
14
  :class="disabled ? 'cursor-not-allowed opacity-50' : ''"
15
- @click="search?.focus()"
15
+ @click="
16
+ if (!disabled) {
17
+ open = true;
18
+ search?.focus();
19
+ }
20
+ "
16
21
  >
17
22
  <input
18
23
  ref="search"
19
24
  v-model="query"
20
25
  role="combobox"
21
- class="w-full min-w-0 bg-transparent py-2 outline-none placeholder:opacity-50"
26
+ class="w-full min-w-0 bg-transparent px-1 outline-none placeholder:opacity-50"
22
27
  :placeholder="placeholder ?? 'Select'"
23
28
  :aria-expanded="open"
24
29
  :aria-controls="listboxId"
25
30
  :aria-activedescendant="activeId"
26
31
  aria-autocomplete="list"
27
32
  :disabled="disabled"
28
- @focus="open = true"
29
33
  @input="input"
30
34
  @keydown.enter.prevent="confirm"
31
35
  @keydown.escape="open = false"
@@ -33,24 +37,18 @@
33
37
  @keydown.arrow-up.prevent="stepHighlight(-1)"
34
38
  />
35
39
 
36
- <button
37
- type="button"
38
- tabindex="-1"
39
- class="shrink-0 opacity-60 transition-transform"
40
- :class="open ? 'rotate-180' : ''"
41
- :disabled="disabled"
42
- @click.stop="open = !open"
43
- >
44
- <Icon :is="ArrowDownIcon" :size="iconSize" />
40
+ <button type="button" tabindex="-1" class="shrink-0" :disabled="disabled" @click.stop="open = !open">
41
+ <Icon :is="open ? ArrowUpIcon : ArrowDownIcon" :size="iconSize" />
45
42
  </button>
46
43
  </Stack>
47
44
 
48
- <div
45
+ <Stack
49
46
  v-if="open"
50
47
  :id="listboxId"
51
48
  role="listbox"
52
49
  :aria-label="label"
53
- class="absolute inset-x-0 top-full z-10 mt-1 max-h-[min(16rem,50vh)] overflow-y-auto rounded-ui border border-border bg-popover p-1 text-popover-foreground shadow-xl"
50
+ padding="1"
51
+ class="absolute inset-x-0 top-full z-10 mt-1 max-h-[min(16rem,50vh)] overflow-y-auto rounded-ui border border-border bg-popover text-popover-foreground shadow-xl [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
54
52
  >
55
53
  <Button
56
54
  v-for="(option, index) in filtered"
@@ -66,7 +64,7 @@
66
64
  >
67
65
  <Stack direction="horizontal" align="center" justify="between" gap="1" class="w-full text-left">
68
66
  <slot name="option" :option="option" :selected="option.value === modelValue">
69
- {{ option.label }}
67
+ <span class="min-w-0 flex-1 truncate px-1">{{ option.label }}</span>
70
68
  </slot>
71
69
 
72
70
  <Icon v-if="option.value === modelValue" :is="TickIcon" :size="iconSize" />
@@ -76,13 +74,14 @@
76
74
  <p v-if="filtered.length === 0" class="px-1 py-5 text-center text-sm opacity-60">
77
75
  {{ emptyLabel }}
78
76
  </p>
79
- </div>
77
+ </Stack>
80
78
  </div>
81
79
  </Stack>
82
80
  </template>
83
81
 
84
82
  <script setup lang="ts">
85
83
  import ArrowDownIcon from "~icons/hugeicons/arrow-down-01";
84
+ import ArrowUpIcon from "~icons/hugeicons/arrow-up-01";
86
85
  import TickIcon from "~icons/hugeicons/tick-02";
87
86
  import { computed, onUnmounted, ref, useId, useTemplateRef, watch } from "vue";
88
87
  import type { SelectOption, SelectProps, SelectValue } from "../../components/select";
@@ -122,8 +121,10 @@ const selected = computed(() => options.find((option) => option.value === modelV
122
121
 
123
122
  const query = ref(selected.value?.label ?? "");
124
123
 
124
+ const searching = ref(false);
125
+
125
126
  const filtered = computed(() => {
126
- const term = query.value.trim().toLowerCase();
127
+ const term = searching.value ? query.value.trim().toLowerCase() : "";
127
128
 
128
129
  return term === "" ? options : options.filter((option) => option.label.toLowerCase().includes(term));
129
130
  });
@@ -194,6 +195,8 @@ function confirm() {
194
195
  }
195
196
 
196
197
  function input() {
198
+ searching.value = true;
199
+
197
200
  open.value = true;
198
201
 
199
202
  highlighted.value = -1;
@@ -207,8 +210,6 @@ watch(selected, (value) => {
207
210
 
208
211
  watch(open, (value) => {
209
212
  if (value) {
210
- query.value = "";
211
-
212
213
  highlighted.value = filtered.value.findIndex((option) => option.value === modelValue);
213
214
 
214
215
  document.addEventListener("pointerdown", handlePointerDown);
@@ -221,6 +222,8 @@ watch(open, (value) => {
221
222
 
222
223
  highlighted.value = -1;
223
224
 
225
+ searching.value = false;
226
+
224
227
  query.value = selected.value?.label ?? "";
225
228
  }
226
229
  });
@@ -1,6 +1,6 @@
1
1
  <template>
2
2
  <div
3
- class="shrink-0 bg-border"
3
+ class="shrink-0 self-center bg-border"
4
4
  :class="variants[selected]"
5
5
  :role="decorative ? 'none' : 'separator'"
6
6
  :aria-orientation="decorative ? undefined : selected"
@@ -17,6 +17,6 @@ const selected = computed(() => variant ?? orientation);
17
17
 
18
18
  const variants: Record<SeparatorOrientation, string> = {
19
19
  horizontal: "h-px w-full",
20
- vertical: "h-full min-h-5 w-px",
20
+ vertical: "h-5 w-px",
21
21
  };
22
22
  </script>
@@ -1,12 +1,12 @@
1
1
  <template>
2
2
  <dialog
3
3
  ref="sheet"
4
- class="m-0 max-w-none border-0 bg-transparent p-0 backdrop:bg-black/50"
4
+ class="m-0 max-w-none border-0 bg-transparent p-0 backdrop:bg-black/50 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
5
5
  :class="positions[side]"
6
6
  @close="$emit('update:modelValue', false)"
7
7
  @click="$event.target === $event.currentTarget && ($event.currentTarget as HTMLDialogElement).close()"
8
8
  >
9
- <Card :title="title" :description="description" :class="cardClasses[side]">
9
+ <Card :label="label" :class="cardClasses[side]">
10
10
  <template #actions>
11
11
  <Button
12
12
  size="icon"
@@ -17,7 +17,7 @@
17
17
  />
18
18
  </template>
19
19
 
20
- <div class="flex-1 overflow-auto">
20
+ <div class="min-h-0 flex-1 overflow-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
21
21
  <slot />
22
22
  </div>
23
23
 
@@ -37,7 +37,7 @@ import Card from "./card.vue";
37
37
  import { useFocusTrap } from "../utilities/use-focus-trap";
38
38
  import { useScrollLock } from "../utilities/use-scroll-lock";
39
39
 
40
- const { modelValue = false, side = "right", title, description, closeLabel = "Close" } = defineProps<SheetProps>();
40
+ const { modelValue = false, side = "right", label, closeLabel = "Close" } = defineProps<SheetProps>();
41
41
 
42
42
  defineEmits<{
43
43
  "update:modelValue": [value: boolean];
@@ -1,21 +1,35 @@
1
1
  <template>
2
- <Stack is="label" direction="horizontal" align="start" gap="3" :class="interactionVariants[interaction]">
2
+ <Stack
3
+ is="label"
4
+ direction="horizontal"
5
+ align="center"
6
+ gap="3"
7
+ class="group"
8
+ :class="interactionVariants[interaction]"
9
+ >
3
10
  <input v-if="name" type="hidden" :name="name" :value="modelValue ? 'on' : 'off'" />
4
11
 
5
12
  <button
6
- class="relative mt-0.5 flex h-6 w-11 shrink-0 items-center rounded-ui outline-ring transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed"
7
- :class="variants[state]"
13
+ class="relative flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-ui p-0.5 outline-ring focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed"
14
+ :class="[
15
+ variants[state],
16
+ disabled
17
+ ? ''
18
+ : modelValue
19
+ ? 'group-hover:bg-primary-hover group-active:bg-primary-active'
20
+ : 'group-hover:bg-secondary-hover group-active:bg-secondary-active',
21
+ ]"
8
22
  type="button"
9
23
  role="switch"
10
24
  :aria-checked="modelValue"
11
25
  :disabled="disabled"
12
26
  @click="$emit('update:modelValue', !modelValue)"
13
27
  >
14
- <span class="block size-5 rounded-ui shadow transition-transform" :class="thumbVariants[state]" />
28
+ <span class="block size-5 rounded-ui shadow" :class="thumbVariants[state]" />
15
29
  </button>
16
30
 
17
31
  <Stack is="span" v-if="label || description || $slots.default" gap="0">
18
- <span class="text-sm font-medium">
32
+ <span class="text-sm font-normal">
19
33
  <slot> {{ label }} </slot>
20
34
  </span>
21
35
 
@@ -47,11 +61,11 @@ const interactionVariants: Record<ControlInteraction, string> = {
47
61
 
48
62
  const thumbVariants: Record<SwitchState, string> = {
49
63
  checked: "translate-x-5 bg-primary-foreground",
50
- unchecked: "translate-x-1 bg-white",
64
+ unchecked: "translate-x-0 bg-foreground",
51
65
  };
52
66
 
53
67
  const variants: Record<SwitchState, string> = {
54
68
  checked: "bg-primary",
55
- unchecked: "bg-input",
69
+ unchecked: "bg-background ring-1 ring-input ring-inset",
56
70
  };
57
71
  </script>
@@ -1,32 +1,42 @@
1
1
  <template>
2
- <Stack>
3
- <span v-if="label" class="text-sm font-medium">
4
- {{ label }}<span v-if="required" aria-hidden="true"> *</span>
2
+ <Stack class="w-full" :class="texts[text]">
3
+ <span v-if="label" class="font-normal">
4
+ {{ label }}<span v-if="required" aria-hidden="true" class="text-destructive"> *</span>
5
5
  </span>
6
6
 
7
7
  <Stack
8
- align="center"
9
- justify="center"
10
- gap="3"
11
- class="relative min-h-32 rounded-ui border border-input border-dashed p-5 text-center transition-colors"
8
+ gap="0"
9
+ class="relative rounded-ui"
12
10
  :class="[variants[state], interactionVariants[interaction]]"
13
11
  @dragenter.prevent="isOver = true"
14
12
  @dragover.prevent="isOver = true"
15
13
  @dragleave="leave"
16
14
  @drop.prevent="drop"
17
15
  >
18
- <template v-if="modelValue">
19
- <span class="max-w-full truncate text-sm">
20
- {{ "name" in modelValue ? modelValue.name : "File selected" }}
16
+ <Stack v-if="modelValue" direction="horizontal" align="center" justify="between" gap="0">
17
+ <span class="min-w-0 flex-1 p-3">
18
+ <span class="block truncate px-1">{{
19
+ "name" in modelValue ? modelValue.name : "File selected"
20
+ }}</span>
21
21
  </span>
22
22
 
23
- <Button label="Remove" variant="ghost" :disabled="disabled" @click="remove" />
24
- </template>
25
- <template v-else>
23
+ <Button
24
+ size="icon"
25
+ variant="secondary"
26
+ :text="text"
27
+ :icon-prefix="CloseIcon"
28
+ aria-label="Remove file"
29
+ :disabled="disabled"
30
+ @click="remove"
31
+ />
32
+ </Stack>
33
+ <Stack v-else align="center" gap="3" padding="5" class="text-center">
34
+ <Icon :is="UploadIcon" :size="24" />
35
+
26
36
  <span class="text-sm opacity-60"> Drag and drop a file here </span>
27
37
 
28
38
  <Button label="Browse files" variant="outline" :disabled="disabled" @click="input?.click()" />
29
- </template>
39
+ </Stack>
30
40
 
31
41
  <input
32
42
  ref="input"
@@ -38,20 +48,37 @@
38
48
  :disabled="disabled"
39
49
  @change="change"
40
50
  />
51
+
52
+ <span
53
+ aria-hidden="true"
54
+ class="pointer-events-none absolute inset-0 rounded-ui ring-1 ring-input ring-inset"
55
+ />
41
56
  </Stack>
42
57
 
43
- <span v-if="description" class="text-sm opacity-60">{{ description }}</span>
58
+ <span v-if="description" class="opacity-60">{{ description }}</span>
44
59
  </Stack>
45
60
  </template>
46
61
 
47
62
  <script setup lang="ts">
63
+ import UploadIcon from "~icons/hugeicons/file-upload";
64
+ import CloseIcon from "~icons/hugeicons/multiplication-sign";
48
65
  import { computed, ref, useTemplateRef } from "vue";
49
66
  import type { ControlInteraction } from "../../components/control.ts";
50
- import type { UploadProps, UploadState } from "../../components/upload";
67
+ import type { UploadProps, UploadState, UploadText } from "../../components/upload";
51
68
  import Button from "./button.vue";
69
+ import Icon from "./icon.vue";
52
70
  import Stack from "./stack.vue";
53
71
 
54
- const { modelValue, label, description, accept, name, required = false, disabled = false } = defineProps<UploadProps>();
72
+ const {
73
+ modelValue,
74
+ label,
75
+ description,
76
+ text = "sm",
77
+ accept,
78
+ name,
79
+ required = false,
80
+ disabled = false,
81
+ } = defineProps<UploadProps>();
55
82
 
56
83
  const emit = defineEmits<{
57
84
  "update:modelValue": [value?: Blob | File];
@@ -71,10 +98,15 @@ const interactionVariants: Record<ControlInteraction, string> = {
71
98
  };
72
99
 
73
100
  const variants: Record<UploadState, string> = {
74
- idle: "bg-muted",
101
+ idle: "bg-secondary",
75
102
  over: "bg-accent",
76
103
  };
77
104
 
105
+ const texts: Record<UploadText, string> = {
106
+ sm: "text-sm",
107
+ md: "text-base",
108
+ };
109
+
78
110
  function change() {
79
111
  const file = input.value?.files?.item(0);
80
112
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revojs/ui",
3
- "version": "0.2.11",
3
+ "version": "0.2.12",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",