vueless 1.2.10-beta.2 → 1.2.10-beta.3
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 +1 -0
- package/constants.js +1 -0
- package/icons/storybook/arrow_forward_ios.svg +1 -0
- package/index.d.ts +1 -0
- package/index.ts +1 -0
- package/package.json +1 -1
- package/types.ts +2 -0
- package/ui.container-drawer/UDrawer.vue +365 -0
- package/ui.container-drawer/config.ts +128 -0
- package/ui.container-drawer/constants.ts +5 -0
- package/ui.container-drawer/storybook/docs.mdx +16 -0
- package/ui.container-drawer/storybook/stories.ts +255 -0
- package/ui.container-drawer/tests/UDrawer.test.ts +680 -0
- package/ui.container-drawer/types.ts +67 -0
package/constants.d.ts
CHANGED
package/constants.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "1.2.10-beta.3",
|
|
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>;
|
|
@@ -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,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 />
|