vueless 1.2.10-beta.2 → 1.2.10-beta.4

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/constants.d.ts CHANGED
@@ -186,6 +186,7 @@ export namespace COMPONENTS {
186
186
  let UModal: string;
187
187
  let UModalConfirm: string;
188
188
  let UPage: string;
189
+ let UDrawer: string;
189
190
  let UIcon: string;
190
191
  let UAvatar: string;
191
192
  let UTable: string;
package/constants.js CHANGED
@@ -296,6 +296,7 @@ export const COMPONENTS = {
296
296
  UModal: "ui.container-modal",
297
297
  UModalConfirm: "ui.container-modal-confirm",
298
298
  UPage: "ui.container-page",
299
+ UDrawer: "ui.container-drawer",
299
300
 
300
301
  /* Images and Icons */
301
302
  UIcon: "ui.image-icon",
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 -960 960 960"><path d="M304-76.26 242.26-139l343-343-343-343L304-887.74 709.74-482 304-76.26Z"/></svg>
package/index.d.ts CHANGED
@@ -101,6 +101,7 @@ export { default as UAccordionItem } from "./ui.container-accordion-item/UAccord
101
101
  export { default as UCard } from "./ui.container-card/UCard.vue";
102
102
  export { default as UModal } from "./ui.container-modal/UModal.vue";
103
103
  export { default as UModalConfirm } from "./ui.container-modal-confirm/UModalConfirm.vue";
104
+ export { default as UDrawer } from "./ui.container-drawer/UDrawer.vue";
104
105
  export { default as UPage } from "./ui.container-page/UPage.vue";
105
106
  /* Images and Icons */
106
107
  export { default as UIcon } from "./ui.image-icon/UIcon.vue";
package/index.ts CHANGED
@@ -107,6 +107,7 @@ export { default as UAccordionItem } from "./ui.container-accordion-item/UAccord
107
107
  export { default as UCard } from "./ui.container-card/UCard.vue";
108
108
  export { default as UModal } from "./ui.container-modal/UModal.vue";
109
109
  export { default as UModalConfirm } from "./ui.container-modal-confirm/UModalConfirm.vue";
110
+ export { default as UDrawer } from "./ui.container-drawer/UDrawer.vue";
110
111
  export { default as UPage } from "./ui.container-page/UPage.vue";
111
112
  /* Images and Icons */
112
113
  export { default as UIcon } from "./ui.image-icon/UIcon.vue";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vueless",
3
- "version": "1.2.10-beta.2",
3
+ "version": "1.2.10-beta.4",
4
4
  "description": "Vue Styleless UI Component Library, powered by Tailwind CSS.",
5
5
  "author": "Johnny Grid <hello@vueless.com> (https://vueless.com)",
6
6
  "homepage": "https://vueless.com",
package/types.ts CHANGED
@@ -27,6 +27,7 @@ import UDividerConfig from "./ui.container-divider/config";
27
27
  import UGroupConfig from "./ui.container-group/config";
28
28
  import UModalConfig from "./ui.container-modal/config";
29
29
  import UModalConfirmConfig from "./ui.container-modal-confirm/config";
30
+ import UDrawerConfig from "./ui.container-drawer/config";
30
31
  import UPageConfig from "./ui.container-page/config";
31
32
  import URowConfig from "./ui.container-row/config";
32
33
  import ULoaderConfig from "./ui.loader/config";
@@ -272,6 +273,7 @@ export interface Components {
272
273
  UModal: Partial<typeof UModalConfig>;
273
274
  UModalConfirm: Partial<typeof UModalConfirmConfig>;
274
275
  UPage: Partial<typeof UPageConfig>;
276
+ UDrawer: Partial<typeof UDrawerConfig>;
275
277
  URow: Partial<typeof URowConfig>;
276
278
  ULoader: Partial<typeof ULoaderConfig>;
277
279
  ULoaderOverlay: Partial<typeof ULoaderOverlayConfig>;
@@ -62,6 +62,7 @@ defineExpose({
62
62
  * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
63
63
  */
64
64
  const mutatedProps = computed(() => ({
65
+ icon: Boolean(props.icon) || hasSlotContent(slots["default"]),
65
66
  leftIcon: Boolean(props.leftIcon) || hasSlotContent(slots["left"]),
66
67
  rightIcon: Boolean(props.rightIcon) || hasSlotContent(slots["right"]),
67
68
  label: Boolean(props.label),
@@ -129,11 +130,6 @@ const {
129
130
  </template>
130
131
 
131
132
  <!-- This is needed to prevent changing button height -->
132
- <div
133
- v-if="(!label && !hasSlotContent(slots['default']) && !icon) || loading"
134
- tabindex="-1"
135
- v-bind="invisibleAttrs"
136
- v-text="'invisible'"
137
- />
133
+ <div v-if="icon || loading" tabindex="-1" v-bind="invisibleAttrs" v-text="'invisible'" />
138
134
  </component>
139
135
  </template>
@@ -54,6 +54,9 @@ export default /*tw*/ {
54
54
  loading: {
55
55
  true: "gap-0 pointer-events-none",
56
56
  },
57
+ icon: {
58
+ true: "gap-0",
59
+ },
57
60
  label: {
58
61
  false: "gap-0",
59
62
  },
@@ -62,6 +65,8 @@ export default /*tw*/ {
62
65
  },
63
66
  },
64
67
  compoundVariants: [
68
+ { label: false, leftIcon: true, class: "gap-0" },
69
+ { label: false, rightIcon: true, class: "gap-0" },
65
70
  { square: true, size: "2xs", class: "p-1" },
66
71
  { square: true, size: "xs", class: "p-1.5" },
67
72
  { square: true, size: "sm", class: "p-2" },
@@ -115,7 +115,7 @@ describe("UButton.vue", () => {
115
115
 
116
116
  const nestedUIconComponents = component.findAllComponents(UIcon);
117
117
 
118
- expect(component.text()).toBe("");
118
+ expect(component.text()).not.toBe(label);
119
119
  expect(nestedUIconComponents.length).toBe(1);
120
120
  expect(nestedUIconComponents[0].props("name")).toBe(icon);
121
121
  });
@@ -0,0 +1,365 @@
1
+ <script setup lang="ts">
2
+ import { computed, useSlots, watch, useId, useTemplateRef, nextTick, ref } from "vue";
3
+
4
+ import useUI from "../composables/useUI";
5
+ import { getDefaults } from "../utils/ui";
6
+ import { hasSlotContent } from "../utils/helper";
7
+
8
+ import UHeader from "../ui.text-header/UHeader.vue";
9
+
10
+ import defaultConfig from "./config";
11
+ import { COMPONENT_NAME } from "./constants";
12
+
13
+ import type { Props, Config } from "./types";
14
+
15
+ defineOptions({ inheritAttrs: false });
16
+
17
+ const props = withDefaults(defineProps<Props>(), {
18
+ ...getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME),
19
+ modelValue: false,
20
+ });
21
+
22
+ const emit = defineEmits([
23
+ /**
24
+ * Triggers when the drawer is toggled.
25
+ * @property {Boolean} value
26
+ */
27
+ "update:modelValue",
28
+
29
+ /**
30
+ * Triggers when the drawer is closed.
31
+ */
32
+ "close",
33
+ ]);
34
+
35
+ const elementId = props.id || useId();
36
+
37
+ const slots = useSlots();
38
+
39
+ const wrapperRef = useTemplateRef<HTMLDivElement>("wrapper");
40
+ const drawerRef = useTemplateRef<HTMLDivElement>("drawer");
41
+
42
+ const isDragging = ref(false);
43
+ const dragStartPosition = ref({ x: 0, y: 0 });
44
+ const dragCurrentPosition = ref({ x: 0, y: 0 });
45
+ const minDragDistance = 10;
46
+
47
+ const isShownDrawer = computed({
48
+ get: () => props.modelValue,
49
+ set: (value) => emit("update:modelValue", value),
50
+ });
51
+
52
+ const isExistHeader = computed(() => {
53
+ return (
54
+ props.title ||
55
+ hasSlotContent(slots["before-title"]) ||
56
+ hasSlotContent(slots["title"]) ||
57
+ hasSlotContent(slots["after-title"]) ||
58
+ hasSlotContent(slots["actions"])
59
+ );
60
+ });
61
+
62
+ const isExistFooter = computed(() => {
63
+ return hasSlotContent(slots["footer-left"]) || hasSlotContent(slots["footer-right"]);
64
+ });
65
+
66
+ const dragThreshold = computed(() => {
67
+ if (!drawerRef.value) return 0;
68
+
69
+ const rect = drawerRef.value.getBoundingClientRect();
70
+
71
+ const dragThresholdMap = {
72
+ top: rect.height / 2,
73
+ bottom: rect.height / 2,
74
+ left: rect.width / 2,
75
+ right: rect.width / 2,
76
+ };
77
+
78
+ return dragThresholdMap[props.position] || 0;
79
+ });
80
+
81
+ const dragDistance = computed(() => {
82
+ if (!isDragging.value) return 0;
83
+
84
+ const deltaX = dragCurrentPosition.value.x - dragStartPosition.value.x;
85
+ const deltaY = dragCurrentPosition.value.y - dragStartPosition.value.y;
86
+
87
+ const totalDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
88
+
89
+ if (totalDistance < minDragDistance) return 0;
90
+
91
+ const dragDistanceMap = {
92
+ top: Math.min(0, deltaY),
93
+ bottom: Math.max(0, deltaY),
94
+ left: Math.min(0, deltaX),
95
+ right: Math.max(0, deltaX),
96
+ };
97
+
98
+ return dragDistanceMap[props.position] || 0;
99
+ });
100
+
101
+ const shouldCloseDrawer = computed(() => {
102
+ return Math.abs(dragDistance.value) > dragThreshold.value;
103
+ });
104
+
105
+ const dragTransform = computed(() => {
106
+ if (!isDragging.value) return "";
107
+
108
+ const distance = dragDistance.value;
109
+
110
+ const dragTransformMap = {
111
+ top: `translateY(${distance}px)`,
112
+ bottom: `translateY(${distance}px)`,
113
+ left: `translateX(${distance}px)`,
114
+ right: `translateX(${distance}px)`,
115
+ };
116
+
117
+ return dragTransformMap[props.position] || "";
118
+ });
119
+
120
+ function getFocusableElements() {
121
+ if (!wrapperRef.value) return [];
122
+
123
+ return Array.from(
124
+ wrapperRef.value.querySelectorAll(
125
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])',
126
+ ),
127
+ );
128
+ }
129
+
130
+ function trapFocus(e: KeyboardEvent) {
131
+ if (e.key !== "Tab") return;
132
+
133
+ const focusableElements = getFocusableElements();
134
+
135
+ if (!focusableElements.length) return;
136
+
137
+ const firstElement = focusableElements.at(0) as HTMLElement;
138
+ const lastElement = focusableElements.at(-1) as HTMLElement;
139
+
140
+ // Shift+Tab - if focused on first element, move to last
141
+ if (e.shiftKey && document.activeElement === firstElement) {
142
+ e.preventDefault();
143
+ lastElement.focus();
144
+
145
+ return;
146
+ }
147
+
148
+ // Tab - if focused on last element, move to first
149
+ if (!e.shiftKey && document.activeElement === lastElement) {
150
+ e.preventDefault();
151
+ firstElement.focus();
152
+ }
153
+ }
154
+
155
+ watch(isShownDrawer, (newValue) => {
156
+ onChangeShownDrawer(newValue);
157
+
158
+ if (!newValue) emit("close");
159
+ });
160
+
161
+ function onChangeShownDrawer(newValue: boolean) {
162
+ toggleEventListeners();
163
+
164
+ if (newValue) {
165
+ nextTick(() => wrapperRef.value?.focus());
166
+ }
167
+ }
168
+
169
+ function toggleEventListeners() {
170
+ if (isShownDrawer.value) {
171
+ document.addEventListener("keydown", trapFocus);
172
+ document.addEventListener("keydown", onKeydownEsc);
173
+ } else {
174
+ document.removeEventListener("keydown", trapFocus);
175
+ document.removeEventListener("keydown", onKeydownEsc);
176
+ }
177
+ }
178
+
179
+ function onClickOutside() {
180
+ props.closeOnOverlay && closeDrawer();
181
+ }
182
+
183
+ function onKeydownEsc(e: KeyboardEvent) {
184
+ if (e.key !== "Escape" || !props.closeOnEsc) return;
185
+
186
+ closeDrawer();
187
+ }
188
+
189
+ function closeDrawer() {
190
+ isShownDrawer.value = false;
191
+
192
+ emit("close");
193
+ }
194
+
195
+ function onDragStart(e: MouseEvent | TouchEvent) {
196
+ e.preventDefault();
197
+
198
+ const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
199
+ const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
200
+
201
+ dragStartPosition.value = { x: clientX, y: clientY };
202
+ dragCurrentPosition.value = { x: clientX, y: clientY };
203
+
204
+ document.addEventListener("mousemove", onDragMove);
205
+ document.addEventListener("mouseup", onDragEnd);
206
+ document.addEventListener("touchmove", onDragMove, { passive: false });
207
+ document.addEventListener("touchend", onDragEnd);
208
+ }
209
+
210
+ function onDragMove(e: MouseEvent | TouchEvent) {
211
+ e.preventDefault();
212
+
213
+ const clientX = "touches" in e ? e.touches[0].clientX : e.clientX;
214
+ const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
215
+
216
+ dragCurrentPosition.value = { x: clientX, y: clientY };
217
+
218
+ const deltaX = clientX - dragStartPosition.value.x;
219
+ const deltaY = clientY - dragStartPosition.value.y;
220
+ const totalDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
221
+
222
+ if (totalDistance >= minDragDistance) {
223
+ isDragging.value = true;
224
+ }
225
+ }
226
+
227
+ function onDragEnd() {
228
+ document.removeEventListener("mousemove", onDragMove);
229
+ document.removeEventListener("mouseup", onDragEnd);
230
+ document.removeEventListener("touchmove", onDragMove);
231
+ document.removeEventListener("touchend", onDragEnd);
232
+
233
+ if (isDragging.value && shouldCloseDrawer.value) {
234
+ closeDrawer();
235
+ }
236
+
237
+ isDragging.value = false;
238
+ dragStartPosition.value = { x: 0, y: 0 };
239
+ dragCurrentPosition.value = { x: 0, y: 0 };
240
+ }
241
+
242
+ defineExpose({
243
+ /**
244
+ * A reference to the component's wrapper element for direct DOM manipulation.
245
+ * @property {HTMLDivElement}
246
+ */
247
+ wrapperRef,
248
+ });
249
+
250
+ const wrapperTransition = computed(() => {
251
+ const transitionMap = {
252
+ top: config.value.wrapperTransitionTop,
253
+ bottom: config.value.wrapperTransitionBottom,
254
+ left: config.value.wrapperTransitionLeft,
255
+ right: config.value.wrapperTransitionRight,
256
+ };
257
+
258
+ return transitionMap[props.position];
259
+ });
260
+
261
+ /**
262
+ * Get element / nested component attributes for each config token ✨
263
+ * Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
264
+ */
265
+
266
+ const {
267
+ getDataTest,
268
+ config,
269
+ handleAttrs,
270
+ handleWrapperAttrs,
271
+ drawerAttrs,
272
+ drawerWrapperAttrs,
273
+ titleAttrs,
274
+ overlayAttrs,
275
+ wrapperAttrs,
276
+ innerWrapperAttrs,
277
+ headerAttrs,
278
+ descriptionAttrs,
279
+ bodyAttrs,
280
+ footerLeftAttrs,
281
+ footerAttrs,
282
+ footerRightAttrs,
283
+ beforeTitleAttrs,
284
+ titleFallbackAttrs,
285
+ } = useUI<Config>(defaultConfig);
286
+ </script>
287
+
288
+ <template>
289
+ <Transition v-bind="config.overlayTransition">
290
+ <div v-if="isShownDrawer" v-bind="overlayAttrs" />
291
+ </Transition>
292
+
293
+ <Transition v-bind="wrapperTransition">
294
+ <div
295
+ v-if="isShownDrawer"
296
+ :id="elementId"
297
+ ref="wrapper"
298
+ tabindex="0"
299
+ v-bind="wrapperAttrs"
300
+ :data-test="getDataTest()"
301
+ @keydown.self.esc="onKeydownEsc"
302
+ >
303
+ <div v-bind="innerWrapperAttrs" @click.self="onClickOutside">
304
+ <div
305
+ ref="drawer"
306
+ :style="{ transform: dragTransform }"
307
+ v-bind="drawerWrapperAttrs"
308
+ @mousedown="onDragStart"
309
+ @touchstart="onDragStart"
310
+ >
311
+ <div v-bind="drawerAttrs">
312
+ <div v-if="isExistHeader" v-bind="headerAttrs">
313
+ <div v-bind="beforeTitleAttrs">
314
+ <!-- @slot Use it to add something before the header title. -->
315
+ <slot name="before-title" />
316
+ <!--
317
+ @slot Use it to add something to the left side of the header.
318
+ @binding {string} title
319
+ -->
320
+ <slot name="title" :title="title">
321
+ <div v-bind="titleFallbackAttrs">
322
+ <UHeader :label="title" size="sm" v-bind="titleAttrs" />
323
+ <div v-if="description" v-bind="descriptionAttrs" v-text="description" />
324
+ </div>
325
+ </slot>
326
+ <!-- @slot Use it to add something after the header title. -->
327
+ <slot name="after-title" />
328
+ </div>
329
+
330
+ <!--
331
+ @slot Use it to add something instead of the close button.
332
+ @binding {function} close
333
+ -->
334
+ <slot name="actions" :close="closeDrawer" />
335
+ </div>
336
+
337
+ <div v-bind="bodyAttrs">
338
+ <!-- @slot Use it to add something into the drawer body. -->
339
+ <slot />
340
+ </div>
341
+
342
+ <div v-if="isExistFooter" v-bind="footerAttrs">
343
+ <div v-if="hasSlotContent($slots['footer-left'])" v-bind="footerLeftAttrs">
344
+ <!-- @slot Use it to add something to the left side of the footer. -->
345
+ <slot name="footer-left" />
346
+ </div>
347
+
348
+ <div v-if="hasSlotContent($slots['footer-right'])" v-bind="footerRightAttrs">
349
+ <!-- @slot Use it to add something to the right side of the footer. -->
350
+ <slot name="footer-right" />
351
+ </div>
352
+ </div>
353
+ </div>
354
+
355
+ <div v-if="handle" v-bind="handleWrapperAttrs">
356
+ <!-- @slot Use it to add something instead of the default handle. -->
357
+ <slot name="handle">
358
+ <div v-bind="handleAttrs" />
359
+ </slot>
360
+ </div>
361
+ </div>
362
+ </div>
363
+ </div>
364
+ </Transition>
365
+ </template>
@@ -0,0 +1,128 @@
1
+ export default /*tw*/ {
2
+ wrapper: "fixed inset-0 z-50 outline-hidden",
3
+ wrapperTransitionTop: {
4
+ enterActiveClass: "ease-out duration-300",
5
+ leaveActiveClass: "ease-in duration-200",
6
+ enterFromClass: "opacity-0 -translate-y-full",
7
+ enterToClass: "opacity-100 translate-y-0",
8
+ leaveFromClass: "opacity-100 translate-y-0",
9
+ leaveToClass: "opacity-0 -translate-y-full",
10
+ },
11
+ wrapperTransitionBottom: {
12
+ enterActiveClass: "ease-out duration-300",
13
+ leaveActiveClass: "ease-in duration-200",
14
+ enterFromClass: "opacity-0 translate-y-full",
15
+ enterToClass: "opacity-100 translate-y-0",
16
+ leaveFromClass: "opacity-100 translate-y-0",
17
+ leaveToClass: "opacity-0 translate-y-full",
18
+ },
19
+ wrapperTransitionLeft: {
20
+ enterActiveClass: "ease-out duration-300",
21
+ leaveActiveClass: "ease-in duration-200",
22
+ enterFromClass: "opacity-0 -translate-x-full",
23
+ enterToClass: "opacity-100 translate-x-0",
24
+ leaveFromClass: "opacity-100 translate-x-0",
25
+ leaveToClass: "opacity-0 -translate-x-full",
26
+ },
27
+ wrapperTransitionRight: {
28
+ enterActiveClass: "ease-out duration-300",
29
+ leaveActiveClass: "ease-in duration-200",
30
+ enterFromClass: "opacity-0 translate-x-full",
31
+ enterToClass: "opacity-100 translate-x-0",
32
+ leaveFromClass: "opacity-100 translate-x-0",
33
+ leaveToClass: "opacity-0 translate-x-full",
34
+ },
35
+ overlay: "fixed inset-0 z-40 bg-inverted/15 dark:bg-lifted/75 backdrop-blur-md",
36
+ overlayTransition: {
37
+ enterActiveClass: "ease-out duration-300",
38
+ enterFromClass: "opacity-0",
39
+ enterToClass: "opacity-100",
40
+ leaveActiveClass: "ease-in duration-200",
41
+ leaveFromClass: "opacity-100",
42
+ leaveToClass: "opacity-0",
43
+ },
44
+ innerWrapper: {
45
+ base: "h-full relative", // add overflow-y
46
+ variants: {
47
+ inset: {
48
+ true: "m-4 h-[calc(100%-2rem)]",
49
+ },
50
+ },
51
+ },
52
+ header: {
53
+ base: "flex justify-between p-6",
54
+ compoundVariants: [
55
+ { handle: true, position: "left", class: "pr-0" },
56
+ { handle: true, position: "right", class: "pl-0" },
57
+ { handle: true, position: "bottom", class: "pt-0" },
58
+ ],
59
+ },
60
+ beforeTitle: "flex items-center gap-3",
61
+ titleFallback: "flex flex-col",
62
+ title: "{UHeader}",
63
+ description: "mt-1.5 text-medium font-normal text-lifted",
64
+ body: {
65
+ base: "px-6 pb-6 text-medium",
66
+ compoundVariants: [
67
+ { handle: true, position: "left", class: "pr-0" },
68
+ { handle: true, position: "right", class: "pl-0" },
69
+ { handle: true, position: "top", class: "pb-0" },
70
+ ],
71
+ },
72
+ footer: {
73
+ base: "flex justify-between p-6 max-md:flex-col max-md:gap-4 border-t border-muted",
74
+ compoundVariants: [
75
+ { handle: true, position: "left", class: "pr-0" },
76
+ { handle: true, position: "right", class: "pl-0" },
77
+ ],
78
+ },
79
+ footerLeft: "flex flex-col md:flex-row gap-4 w-full",
80
+ footerRight: "flex flex-col md:flex-row gap-4 w-full justify-end",
81
+ drawerWrapper: {
82
+ base: "flex border absolute select-none cursor-grab active:cursor-grabbing overflow-x-hidden overflow-y-auto",
83
+ variants: {
84
+ variant: {
85
+ solid: "bg-default border-transparent",
86
+ outlined: "bg-default border-muted",
87
+ subtle: "bg-muted border-default/50",
88
+ soft: "bg-muted border-transparent",
89
+ },
90
+ position: {
91
+ top: "top-0 flex-col rounded-b-large w-full h-auto",
92
+ bottom: "bottom-0 flex-col-reverse rounded-t-large w-full h-auto",
93
+ left: "left-0 flex-row rounded-r-large w-max h-full",
94
+ right: "right-0 flex-row-reverse rounded-l-large w-max h-full",
95
+ },
96
+ inset: {
97
+ true: "rounded-large",
98
+ },
99
+ },
100
+ },
101
+ drawer: "",
102
+ handleWrapper: {
103
+ base: "flex items-center justify-center bg-inherit",
104
+ variants: {
105
+ position: {
106
+ top: "w-full h-11",
107
+ bottom: "w-full h-11",
108
+ left: "w-11 h-auto",
109
+ right: "w-11 h-auto",
110
+ },
111
+ },
112
+ },
113
+ handle: {
114
+ base: "rounded-large cursor-pointer bg-lifted hover:bg-accented transition",
115
+ compoundVariants: [
116
+ { position: ["top", "bottom"], class: "w-11 h-1.5" },
117
+ { position: ["left", "right"], class: "w-1.5 h-11" },
118
+ ],
119
+ },
120
+ defaults: {
121
+ variant: "solid",
122
+ position: "left",
123
+ inset: false,
124
+ handle: true,
125
+ closeOnEsc: true,
126
+ closeOnOverlay: true,
127
+ },
128
+ };
@@ -0,0 +1,5 @@
1
+ /*
2
+ This const is needed to prevent the issue in script setup:
3
+ `defineProps` is referencing locally declared variables. (vue/valid-define-props)
4
+ */
5
+ export const COMPONENT_NAME = "UDrawer";
@@ -0,0 +1,16 @@
1
+ import { Meta, Title, Subtitle, Description, Primary, Controls, Stories, Source } from "@storybook/addon-docs/blocks";
2
+ import { getSource } from "../../utils/storybook";
3
+
4
+ import * as stories from "./stories";
5
+ import defaultConfig from "../config?raw"
6
+
7
+ <Meta of={stories} />
8
+ <Title of={stories} />
9
+ <Subtitle of={stories} />
10
+ <Description of={stories} />
11
+ <Primary of={stories} />
12
+ <Controls of={stories.Default} />
13
+ <Stories of={stories} />
14
+
15
+ ## Default config
16
+ <Source code={getSource(defaultConfig)} language="jsx" dark />