@revojs/ui 0.2.7 → 0.2.8

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.
@@ -3,7 +3,7 @@ export type ButtonSize = "default" | "icon";
3
3
  export type ButtonFont = "normal" | "medium" | "semibold";
4
4
  export type ButtonLayout = "left" | "center" | "right";
5
5
  export type ButtonText = "sm" | "md";
6
- export type ButtonVariant = "primary" | "secondary" | "outline" | "ghost" | "destructive" | "danger" | "text";
6
+ export type ButtonVariant = "primary" | "secondary" | "outline" | "ghost" | "destructive";
7
7
  export interface ButtonProps {
8
8
  iconPrefix?: string;
9
9
  iconSuffix?: string;
@@ -31,45 +31,45 @@ var module_default = defineModule({
31
31
  radius: "rounded-md",
32
32
  light: {
33
33
  background: "white",
34
- foreground: "neutral-950",
34
+ foreground: "zinc-950",
35
35
  card: "white",
36
- cardForeground: "neutral-950",
36
+ cardForeground: "zinc-950",
37
37
  popover: "white",
38
- popoverForeground: "neutral-950",
39
- primary: "neutral-900",
40
- primaryForeground: "neutral-50",
41
- secondary: "neutral-100",
42
- secondaryForeground: "neutral-900",
43
- muted: "neutral-100",
44
- mutedForeground: "neutral-500",
45
- accent: "neutral-100",
46
- accentForeground: "neutral-900",
38
+ popoverForeground: "zinc-950",
39
+ primary: "green-600",
40
+ primaryForeground: "zinc-50",
41
+ secondary: "zinc-100",
42
+ secondaryForeground: "zinc-900",
43
+ muted: "zinc-100",
44
+ mutedForeground: "zinc-500",
45
+ accent: "zinc-100",
46
+ accentForeground: "zinc-900",
47
47
  destructive: "red-600",
48
- destructiveForeground: "white",
49
- border: "neutral-200",
50
- input: "neutral-200",
51
- ring: "neutral-400"
48
+ destructiveForeground: "zinc-50",
49
+ border: "zinc-200",
50
+ input: "zinc-200",
51
+ ring: "zinc-400"
52
52
  },
53
53
  dark: {
54
- background: "neutral-950",
55
- foreground: "neutral-50",
56
- card: "neutral-900",
57
- cardForeground: "neutral-50",
58
- popover: "neutral-900",
59
- popoverForeground: "neutral-50",
60
- primary: "neutral-200",
61
- primaryForeground: "neutral-900",
62
- secondary: "neutral-800",
63
- secondaryForeground: "neutral-50",
64
- muted: "neutral-800",
65
- mutedForeground: "neutral-400",
66
- accent: "neutral-800",
67
- accentForeground: "neutral-50",
68
- destructive: "red-400",
69
- destructiveForeground: "white",
70
- border: "white/10",
71
- input: "white/15",
72
- ring: "neutral-500"
54
+ background: "zinc-950",
55
+ foreground: "zinc-50",
56
+ card: "zinc-900",
57
+ cardForeground: "zinc-50",
58
+ popover: "zinc-900",
59
+ popoverForeground: "zinc-50",
60
+ primary: "green-500",
61
+ primaryForeground: "zinc-950",
62
+ secondary: "zinc-900",
63
+ secondaryForeground: "zinc-50",
64
+ muted: "zinc-800",
65
+ mutedForeground: "zinc-400",
66
+ accent: "zinc-900",
67
+ accentForeground: "zinc-50",
68
+ destructive: "red-600",
69
+ destructiveForeground: "zinc-50",
70
+ border: "zinc-700",
71
+ input: "zinc-700",
72
+ ring: "zinc-500"
73
73
  }
74
74
  }),
75
75
  setup(config) {
@@ -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-2 rounded-ui border border-transparent font-[inherit] font-medium leading-normal no-underline outline-ring 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"
4
+ class="inline-flex cursor-pointer items-center justify-center gap-2 rounded-ui border font-[inherit] font-medium leading-normal no-underline outline-ring transition-[background-color,border-color,color,opacity] duration-150 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"
@@ -76,12 +76,12 @@ const texts: Record<ButtonText, string> = {
76
76
  };
77
77
 
78
78
  const variants: Record<ButtonVariant, string> = {
79
- primary: "bg-primary text-primary-foreground",
80
- secondary: "bg-secondary text-secondary-foreground",
81
- outline: "border-border bg-background text-foreground",
82
- ghost: "text-foreground",
83
- destructive: "bg-destructive text-destructive-foreground",
84
- danger: "bg-destructive text-destructive-foreground",
85
- text: "text-foreground",
79
+ primary: "border-transparent bg-primary text-primary-foreground hover:bg-primary/90 active:bg-primary/80",
80
+ secondary: "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/90 active:bg-secondary/80",
81
+ outline:
82
+ "border-transparent bg-secondary text-secondary-foreground ring-1 ring-border ring-inset hover:bg-secondary/90 active:bg-secondary/80",
83
+ ghost: "border-transparent bg-transparent text-foreground hover:bg-accent active:bg-accent/80",
84
+ destructive:
85
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/90 active:bg-destructive/80",
86
86
  };
87
87
  </script>
@@ -8,7 +8,10 @@
8
8
  >
9
9
  <slot name="header">
10
10
  <div class="flex flex-col gap-1.5">
11
- <h3 v-if="label || title" class="text-lg font-semibold leading-none">{{ label ?? title }}</h3>
11
+ <p v-if="label" class="text-xs font-semibold uppercase tracking-widest opacity-50">{{ label }}</p>
12
+
13
+ <h3 v-if="title" class="text-lg font-semibold leading-none">{{ title }}</h3>
14
+
12
15
  <p v-if="description" class="text-sm opacity-70">{{ description }}</p>
13
16
  </div>
14
17
  </slot>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <label class="inline-flex items-start gap-3" :class="interactionVariants[interaction]">
3
3
  <input
4
- class="mt-0.5 size-5 shrink-0 cursor-inherit accent-current text-primary"
4
+ class="peer sr-only"
5
5
  type="checkbox"
6
6
  :checked="modelValue"
7
7
  :name="name"
@@ -10,6 +10,17 @@
10
10
  @change="$emit('update:modelValue', ($event.currentTarget as HTMLInputElement).checked)"
11
11
  />
12
12
 
13
+ <span
14
+ aria-hidden="true"
15
+ class="mt-0.5 flex size-5 shrink-0 items-center justify-center rounded-md 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'"
17
+ >
18
+ <span
19
+ v-if="modelValue"
20
+ class="mb-px h-2.5 w-1.5 rotate-45 border-b-2 border-r-2 border-primary-foreground"
21
+ />
22
+ </span>
23
+
13
24
  <span v-if="label || description || $slots.default" class="flex flex-col gap-0.5">
14
25
  <span class="text-sm font-medium"
15
26
  ><slot>{{ label }}</slot></span
@@ -4,7 +4,7 @@
4
4
  :href="href"
5
5
  font="normal"
6
6
  layout="left"
7
- :variant="active ? 'secondary' : 'text'"
7
+ :variant="active ? 'secondary' : 'ghost'"
8
8
  :icon-prefix="iconPrefix"
9
9
  :icon-suffix="iconSuffix"
10
10
  :label="label"
@@ -9,7 +9,7 @@
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-secondary"
12
+ class="h-2 w-full overflow-hidden rounded-full bg-input"
13
13
  role="progressbar"
14
14
  :aria-label="label"
15
15
  :aria-valuenow="value"
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <label class="inline-flex items-start gap-3" :class="interactionVariants[interaction]">
3
3
  <input
4
- class="mt-0.5 size-5 shrink-0 cursor-inherit accent-current text-primary"
4
+ class="peer sr-only"
5
5
  type="radio"
6
6
  :checked="modelValue === value"
7
7
  :value="value"
@@ -11,6 +11,14 @@
11
11
  @change="$emit('update:modelValue', value)"
12
12
  />
13
13
 
14
+ <span
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"
17
+ :class="modelValue === value ? 'border-primary bg-background' : 'border-input bg-background'"
18
+ >
19
+ <span v-if="modelValue === value" class="size-2 rounded-full bg-primary" />
20
+ </span>
21
+
14
22
  <span class="flex flex-col gap-0.5">
15
23
  <span class="text-sm font-medium">
16
24
  <slot> {{ label }}</slot>
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <label class="flex flex-col gap-2" :class="interactionVariants[interaction]">
2
+ <div class="flex flex-col gap-2" :class="interactionVariants[interaction]">
3
3
  <span v-if="label" class="flex items-center justify-between gap-4 text-sm">
4
4
  <span class="font-medium"> {{ label }} </span>
5
5
 
@@ -8,21 +8,35 @@
8
8
  </output>
9
9
  </span>
10
10
 
11
- <input
12
- class="h-2 w-full cursor-pointer accent-current text-primary disabled:cursor-not-allowed"
13
- type="range"
14
- :value="modelValue"
15
- :min="min"
16
- :max="max"
17
- :step="step"
18
- :disabled="disabled"
19
- @input="$emit('update:modelValue', Number(($event.currentTarget as HTMLInputElement).value))"
20
- />
21
- </label>
11
+ <div
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"
14
+ :class="disabled ? 'cursor-not-allowed opacity-50' : 'cursor-pointer'"
15
+ role="slider"
16
+ :tabindex="disabled ? -1 : 0"
17
+ :aria-valuemin="min"
18
+ :aria-valuemax="max"
19
+ :aria-valuenow="modelValue"
20
+ :aria-label="label"
21
+ :aria-disabled="disabled"
22
+ @pointerdown="onPointerDown"
23
+ @pointermove="onPointerMove"
24
+ @pointerup="onPointerUp"
25
+ @pointercancel="onPointerUp"
26
+ @keydown="onKeyDown"
27
+ >
28
+ <div class="absolute inset-y-0 left-0 rounded-full bg-primary" :style="{ width: `${percent}%` }" />
29
+
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"
32
+ :style="{ left: `${percent}%` }"
33
+ />
34
+ </div>
35
+ </div>
22
36
  </template>
23
37
 
24
38
  <script setup lang="ts">
25
- import { computed } from "vue";
39
+ import { computed, useTemplateRef } from "vue";
26
40
  import type { ControlInteraction } from "../../components/control.ts";
27
41
 
28
42
  const {
@@ -41,7 +55,7 @@ const {
41
55
  disabled?: boolean;
42
56
  }>();
43
57
 
44
- defineEmits<{
58
+ const emit = defineEmits<{
45
59
  "update:modelValue": [value: number];
46
60
  }>();
47
61
 
@@ -51,4 +65,73 @@ const interactionVariants: Record<ControlInteraction, string> = {
51
65
  disabled: "cursor-not-allowed opacity-50",
52
66
  enabled: "",
53
67
  };
68
+
69
+ const track = useTemplateRef("track");
70
+
71
+ const percent = computed(() => (max > min ? Math.min(100, Math.max(0, ((modelValue - min) / (max - min)) * 100)) : 0));
72
+
73
+ let dragging = false;
74
+
75
+ function commit(raw: number) {
76
+ const decimals = (String(step).split(".")[1] ?? "").length;
77
+ const snapped = min + Math.round((raw - min) / step) * step;
78
+ const rounded = Number(snapped.toFixed(decimals));
79
+ const value = Math.min(max, Math.max(min, rounded));
80
+
81
+ if (value !== modelValue) {
82
+ emit("update:modelValue", value);
83
+ }
84
+ }
85
+
86
+ function setValueFromClientX(clientX: number) {
87
+ if (!track.value) {
88
+ return;
89
+ }
90
+
91
+ const rect = track.value.getBoundingClientRect();
92
+ const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));
93
+
94
+ commit(min + ratio * (max - min));
95
+ }
96
+
97
+ function onPointerDown(event: PointerEvent) {
98
+ if (disabled || !track.value) {
99
+ return;
100
+ }
101
+
102
+ dragging = true;
103
+ track.value.setPointerCapture(event.pointerId);
104
+ track.value.focus({ preventScroll: true });
105
+ setValueFromClientX(event.clientX);
106
+ }
107
+
108
+ function onPointerMove(event: PointerEvent) {
109
+ if (dragging) {
110
+ setValueFromClientX(event.clientX);
111
+ }
112
+ }
113
+
114
+ function onPointerUp() {
115
+ dragging = false;
116
+ }
117
+
118
+ function onKeyDown(event: KeyboardEvent) {
119
+ if (disabled) {
120
+ return;
121
+ }
122
+
123
+ if (event.key === "ArrowRight" || event.key === "ArrowUp") {
124
+ commit(modelValue + step);
125
+ event.preventDefault();
126
+ } else if (event.key === "ArrowLeft" || event.key === "ArrowDown") {
127
+ commit(modelValue - step);
128
+ event.preventDefault();
129
+ } else if (event.key === "Home") {
130
+ commit(min);
131
+ event.preventDefault();
132
+ } else if (event.key === "End") {
133
+ commit(max);
134
+ event.preventDefault();
135
+ }
136
+ }
54
137
  </script>
@@ -50,7 +50,7 @@ function state(value: number): RatingState {
50
50
  }
51
51
 
52
52
  const variants: Record<RatingState, string> = {
53
- active: "text-amber-400",
54
- inactive: "text-neutral-300 dark:text-neutral-700",
53
+ active: "text-primary",
54
+ inactive: "text-input",
55
55
  };
56
56
  </script>
@@ -6,7 +6,7 @@
6
6
 
7
7
  <input v-if="name" type="hidden" :name="name" :value="modelValue" :required="required" />
8
8
 
9
- <div ref="container">
9
+ <div ref="container" class="relative">
10
10
  <div
11
11
  ref="trigger"
12
12
  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"
@@ -41,46 +41,47 @@
41
41
  <Icon name="arrow-down-01" />
42
42
  </button>
43
43
  </div>
44
- </div>
45
44
 
46
- <div
47
- ref="dropdown"
48
- popover="manual"
49
- :id="listboxId"
50
- role="listbox"
51
- :aria-label="label"
52
- class="m-0 max-h-[min(16rem,50vh)] overflow-y-auto rounded-ui border border-border bg-popover p-1 text-popover-foreground shadow-xl"
53
- :style="panel"
54
- >
55
- <Button
56
- v-for="option in filtered"
57
- :key="option.value"
58
- class="w-full"
59
- variant="ghost"
60
- layout="left"
61
- :disabled="option.disabled"
62
- role="option"
63
- :aria-selected="option.value === modelValue"
64
- @click="select(option)"
45
+ <div
46
+ ref="dropdown"
47
+ :popover="popoverSupported ? 'manual' : undefined"
48
+ :id="listboxId"
49
+ role="listbox"
50
+ :aria-label="label"
51
+ class="m-0 max-h-[min(16rem,50vh)] overflow-y-auto rounded-ui border border-border bg-popover p-1 text-popover-foreground shadow-xl"
52
+ :class="fallbackClass"
53
+ :style="popoverSupported ? panel : undefined"
65
54
  >
66
- <span class="flex w-full items-center justify-between gap-4 text-left">
67
- <slot name="option" :option="option" :selected="option.value === modelValue">
68
- {{ option.label }}
69
- </slot>
70
-
71
- <Icon v-if="option.value === modelValue" name="tick-02" />
72
- </span>
73
- </Button>
74
-
75
- <p v-if="filtered.length === 0" class="px-2 py-6 text-center text-sm opacity-60">
76
- {{ emptyLabel }}
77
- </p>
55
+ <Button
56
+ v-for="option in filtered"
57
+ :key="option.value"
58
+ class="w-full"
59
+ variant="ghost"
60
+ layout="left"
61
+ :disabled="option.disabled"
62
+ role="option"
63
+ :aria-selected="option.value === modelValue"
64
+ @click="select(option)"
65
+ >
66
+ <span class="flex w-full items-center justify-between gap-4 text-left">
67
+ <slot name="option" :option="option" :selected="option.value === modelValue">
68
+ {{ option.label }}
69
+ </slot>
70
+
71
+ <Icon v-if="option.value === modelValue" name="tick-02" />
72
+ </span>
73
+ </Button>
74
+
75
+ <p v-if="filtered.length === 0" class="px-2 py-6 text-center text-sm opacity-60">
76
+ {{ emptyLabel }}
77
+ </p>
78
+ </div>
78
79
  </div>
79
80
  </div>
80
81
  </template>
81
82
 
82
83
  <script setup lang="ts">
83
- import { computed, nextTick, onUnmounted, ref, useId, useTemplateRef, watch } from "vue";
84
+ import { computed, nextTick, onMounted, onUnmounted, ref, useId, useTemplateRef, watch } from "vue";
84
85
  import type { SelectOption, SelectValue } from "../../components/select.ts";
85
86
  import Button from "./button.vue";
86
87
  import Icon from "./icon.vue";
@@ -121,6 +122,8 @@ const dropdown = useTemplateRef("dropdown");
121
122
 
122
123
  const listboxId = `select-${useId()}`;
123
124
 
125
+ const popoverSupported = ref(true);
126
+
124
127
  const selected = computed(() => options.find((option) => option.value === modelValue));
125
128
 
126
129
  const query = ref(selected.value?.label ?? "");
@@ -133,6 +136,14 @@ const filtered = computed(() => {
133
136
  return term === "" ? options : options.filter((option) => option.label.toLowerCase().includes(term));
134
137
  });
135
138
 
139
+ const fallbackClass = computed(() => {
140
+ if (popoverSupported.value) {
141
+ return undefined;
142
+ }
143
+
144
+ return open.value ? "absolute inset-x-0 top-full z-50 mt-2" : "hidden";
145
+ });
146
+
136
147
  function handlePointerDown(event: PointerEvent) {
137
148
  const target = event.target as Node;
138
149
 
@@ -156,12 +167,13 @@ function updatePanel() {
156
167
  return;
157
168
  }
158
169
 
170
+ const height = window.visualViewport?.height ?? window.innerHeight;
171
+ const width = window.visualViewport?.width ?? window.innerWidth;
159
172
  const bounds = anchor.getBoundingClientRect();
160
- const height = list.offsetHeight;
161
- const width = bounds.width;
162
- const left = Math.min(Math.max(8, bounds.left), window.innerWidth - width - 8);
163
- const top =
164
- bounds.bottom + height + 8 <= window.innerHeight ? bounds.bottom + 8 : Math.max(8, bounds.top - height - 8);
173
+ const listHeight = list.offsetHeight;
174
+ const listWidth = bounds.width;
175
+ const left = Math.max(8, Math.min(Math.max(8, bounds.left), width - listWidth - 8));
176
+ const top = bounds.bottom + listHeight + 8 <= height ? bounds.bottom + 8 : Math.max(8, bounds.top - listHeight - 8);
165
177
 
166
178
  panel.value = {
167
179
  bottom: "auto",
@@ -169,7 +181,7 @@ function updatePanel() {
169
181
  position: "fixed",
170
182
  right: "auto",
171
183
  top: `${top}px`,
172
- width: `${width}px`,
184
+ width: `${listWidth}px`,
173
185
  };
174
186
  }
175
187
 
@@ -186,7 +198,7 @@ watch(open, async (value) => {
186
198
  return;
187
199
  }
188
200
 
189
- if (value && !list.matches(":popover-open")) {
201
+ if (value) {
190
202
  query.value = "";
191
203
 
192
204
  document.addEventListener("pointerdown", handlePointerDown);
@@ -195,31 +207,49 @@ watch(open, async (value) => {
195
207
 
196
208
  window.addEventListener("scroll", updatePanel, true);
197
209
 
198
- list.showPopover();
210
+ window.visualViewport?.addEventListener("resize", updatePanel);
211
+
212
+ if (popoverSupported.value && !list.matches(":popover-open")) {
213
+ list.showPopover();
214
+ }
199
215
 
200
216
  await nextTick();
201
217
 
202
218
  updatePanel();
203
219
 
204
- search.value?.focus();
205
- } else if (!value && list.matches(":popover-open")) {
220
+ requestAnimationFrame(updatePanel);
221
+
222
+ if (document.activeElement !== search.value) {
223
+ search.value?.focus();
224
+ }
225
+ } else {
206
226
  document.removeEventListener("pointerdown", handlePointerDown);
207
227
 
208
228
  window.removeEventListener("resize", updatePanel);
209
229
 
210
230
  window.removeEventListener("scroll", updatePanel, true);
211
231
 
212
- list.hidePopover();
232
+ window.visualViewport?.removeEventListener("resize", updatePanel);
233
+
234
+ if (popoverSupported.value && list.matches(":popover-open")) {
235
+ list.hidePopover();
236
+ }
213
237
 
214
238
  query.value = selected.value?.label ?? "";
215
239
  }
216
240
  });
217
241
 
242
+ onMounted(() => {
243
+ popoverSupported.value = typeof dropdown.value?.showPopover === "function";
244
+ });
245
+
218
246
  onUnmounted(() => {
219
247
  document.removeEventListener("pointerdown", handlePointerDown);
220
248
 
221
249
  window.removeEventListener("resize", updatePanel);
222
250
 
223
251
  window.removeEventListener("scroll", updatePanel, true);
252
+
253
+ window.visualViewport?.removeEventListener("resize", updatePanel);
224
254
  });
225
255
  </script>
@@ -1,12 +1,12 @@
1
1
  <template>
2
2
  <dialog
3
3
  ref="sheet"
4
- class="m-0 h-full max-h-none 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"
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="h-full w-full shadow-2xl">
9
+ <Card :title="title" :description="description" :class="cardClasses[side]">
10
10
  <template #actions>
11
11
  <Button
12
12
  size="icon"
@@ -55,10 +55,17 @@ defineEmits<{
55
55
  const sheet = useTemplateRef("sheet");
56
56
 
57
57
  const positions: Record<SheetSide, string> = {
58
- top: "inset-x-0 top-0 h-auto w-full max-h-[85vh]",
59
- right: "inset-y-0 right-0 w-[min(28rem,90vw)]",
60
- bottom: "inset-x-0 bottom-0 h-auto w-full max-h-[85vh]",
61
- left: "inset-y-0 left-0 w-[min(28rem,90vw)]",
58
+ top: "bottom-auto left-0 right-0 top-0 h-auto w-full",
59
+ right: "bottom-0 left-auto right-0 top-0 h-full max-h-full w-[min(28rem,90vw)]",
60
+ bottom: "bottom-0 left-0 right-0 top-auto h-auto w-full",
61
+ left: "bottom-0 left-0 right-auto top-0 h-full max-h-full w-[min(28rem,90vw)]",
62
+ };
63
+
64
+ const cardClasses: Record<SheetSide, string> = {
65
+ top: "max-h-[85vh] w-full shadow-2xl",
66
+ right: "h-full w-full shadow-2xl",
67
+ bottom: "max-h-[85vh] w-full shadow-2xl",
68
+ left: "h-full w-full shadow-2xl",
62
69
  };
63
70
 
64
71
  watchEffect(() => {
@@ -3,7 +3,7 @@
3
3
  <input v-if="name" type="hidden" :name="name" :value="modelValue ? 'on' : 'off'" />
4
4
 
5
5
  <button
6
- class="relative mt-0.5 h-6 w-11 shrink-0 rounded-full outline-ring transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed"
6
+ class="relative mt-0.5 flex h-6 w-11 shrink-0 items-center rounded-full outline-ring transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 disabled:cursor-not-allowed"
7
7
  :class="variants[state]"
8
8
  type="button"
9
9
  role="switch"
@@ -11,10 +11,7 @@
11
11
  :disabled="disabled"
12
12
  @click="$emit('update:modelValue', !modelValue)"
13
13
  >
14
- <span
15
- class="block size-5 translate-x-0.5 rounded-full bg-white shadow transition-transform"
16
- :class="thumbVariants[state]"
17
- />
14
+ <span class="block size-5 rounded-full shadow transition-transform" :class="thumbVariants[state]" />
18
15
  </button>
19
16
 
20
17
  <span v-if="label || description || $slots.default" class="flex flex-col gap-0.5">
@@ -60,8 +57,8 @@ const interactionVariants: Record<ControlInteraction, string> = {
60
57
  };
61
58
 
62
59
  const thumbVariants: Record<SwitchState, string> = {
63
- checked: "translate-x-5",
64
- unchecked: "",
60
+ checked: "translate-x-5 bg-primary-foreground",
61
+ unchecked: "translate-x-0.5 bg-white",
65
62
  };
66
63
 
67
64
  const variants: Record<SwitchState, string> = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revojs/ui",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",