Skip to content

Commit 270b5a0

Browse files
author
TFX-98
committed
add useDraggable composable
1 parent 7616f43 commit 270b5a0

File tree

10 files changed

+882
-13
lines changed

10 files changed

+882
-13
lines changed

packages/docs/page-config/ui-elements/modal/api-description.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export default defineApiDescription({
2323
overlayOpacity: "Set the overlay's opacity",
2424
anchorClass: "Set class name to the `anchor` slot container",
2525
zIndex: "Set the modal's `z-index`",
26+
draggable: "Enable modal dragging",
27+
draggablePosition: "Set the initial position of the draggable modal",
2628
allowBodyScroll: "Allows the document scroll while modal is open.",
2729
blur: "Use `blur` filter to overlay. Root `css` variable `--va-modal-overlay-background-blur-radius` sets the blur radius",
2830
ariaCloseLabel: "The aria-label of the close button",
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<template>
2+
<div class="icons">
3+
<VaIcon
4+
v-for="({ name, flip, position }, index) in iconsList"
5+
:key="index"
6+
:name="name"
7+
:flip="flip"
8+
size="large"
9+
@click="() => handleIconClick(position)"
10+
/>
11+
</div>
12+
<VaModal
13+
v-model="showModal"
14+
draggable
15+
:draggable-position="initialPosition"
16+
:title="initialPosition"
17+
>
18+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ut lobortis
19+
dui.
20+
</VaModal>
21+
</template>
22+
23+
<script setup>
24+
import { ref } from "vue";
25+
26+
const iconsList = [
27+
{ name: 'arrow_outward', flip: 'vertical', position: { x: 0, y: 0 } },
28+
{ name: 'arrow_upward', position: { x: '50%', y: 0 } },
29+
{ name: 'arrow_outward', position: { x: '100%', y: 0 } },
30+
{ name: 'arrow_back', position: { x: 0, y: '50%' } },
31+
{ name: 'circle', position: { x: '50%', y: '50%' } },
32+
{ name: 'arrow_forward', position: { x: '100%', y: '50%' } },
33+
{ name: 'arrow_outward', flip: 'both', position: { x: 0, y: '100%' } },
34+
{ name: 'arrow_downward', position: { x: '50%', y: '100%' } },
35+
{ name: 'arrow_outward', flip: 'horizontal', position: { x: '100%', y: '100%' } },
36+
]
37+
38+
const showModal = ref(false)
39+
const initialPosition = ref(iconsList[0].position)
40+
41+
const handleIconClick = (position) => {
42+
initialPosition.value = position
43+
showModal.value = true
44+
}
45+
</script>
46+
47+
<style lang="scss" scoped>
48+
.icons {
49+
display: flex;
50+
flex-wrap: wrap;
51+
width: 80px;
52+
gap: 4px;
53+
align-items: center;
54+
55+
* {
56+
flex: 0 0 24px;
57+
transition: color 0.25s ease-in-out;
58+
59+
&:hover {
60+
color: var(--va-primary);
61+
}
62+
}
63+
}
64+
</style>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<template>
2+
<div class="buttons">
3+
<VaButton @click="showModalIconDraggable = true">
4+
Open modal with draggable icon
5+
</VaButton>
6+
<VaButton @click="showModalHeaderDraggable = true">
7+
Open modal with draggable header
8+
</VaButton>
9+
<VaButton @click="showModalFooterDraggable = true">
10+
Open modal with draggable footer
11+
</VaButton>
12+
</div>
13+
<VaModal
14+
v-model="showModalIconDraggable"
15+
draggable
16+
title="With draggable icon"
17+
>
18+
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
19+
<template #draggable="{startDrag}">
20+
<VaIcon name="drag_indicator" :style="{ cursor: 'move', position: 'absolute', top: '10px', right: '10px' }" @mousedown="startDrag" />
21+
</template>
22+
</VaModal>
23+
<VaModal
24+
v-model="showModalHeaderDraggable"
25+
draggable
26+
>
27+
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
28+
<template #header="{startDrag}">
29+
<h3 class="va-h3" :style="{ cursor: 'move' }" @mousedown="startDrag">
30+
<VaIcon name="drag_indicator" :style="{ cursor: 'move' }" @mousedown="startDrag" /> Draggable Header
31+
</h3>
32+
</template>
33+
</VaModal>
34+
<VaModal
35+
v-model="showModalFooterDraggable"
36+
draggable
37+
title="With draggable footer"
38+
>
39+
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
40+
<template #footer="{startDrag}">
41+
<VaIcon name="drag_indicator" :style="{ cursor: 'move', position: 'absolute' ,left: '10px', bottom: '10px' }" @mousedown="startDrag" />
42+
</template>
43+
</VaModal>
44+
</template>
45+
46+
<script setup>
47+
import { ref } from "vue";
48+
49+
const showModalIconDraggable = ref(false)
50+
const showModalHeaderDraggable = ref(false)
51+
const showModalFooterDraggable = ref(false)
52+
</script>
53+
54+
<style lang="scss" scoped>
55+
.buttons {
56+
display: flex;
57+
justify-content: space-between;
58+
}
59+
</style>

packages/docs/page-config/ui-elements/modal/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ export default definePageConfig({
7777
description: "Modals can be nested: you can open one modal from another."
7878
}),
7979

80+
block.example("DraggableModals", {
81+
title: "Draggable modals",
82+
description: "Modals can be draggable: you can open modal in 1 of 9 positions and drag it."
83+
}),
84+
85+
block.example("DraggableSlots", {
86+
title: "Draggable slots",
87+
description: "Draggable modal can be customize with any slots for drag interaction."
88+
}),
89+
8090
block.subtitle("API"),
8191
block.api("VaModal", apiDescription, apiOptions),
8292

packages/docs/page-config/ui-elements/modal/modal-api.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ const modalOptions: Record<keyof ModalOptions, string> = {
3838
overlay: "boolean",
3939
overlayOpacity: "number | string",
4040
zIndex: "number | string",
41+
draggable: "boolean",
42+
draggablePosition: "{ x?: number | string, y?: number | string }",
4143
onOk: "() => void",
4244
onCancel: "() => void",
4345
onClickOutside: "() => void",

packages/ui/src/components/va-modal/VaModal.stories.ts

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import { StoryFn } from '@storybook/vue3'
55
import { expect } from '@storybook/jest'
66
import { userEvent } from '../../../.storybook/interaction-utils/userEvent'
77

8+
import VaIcon from '../va-icon/VaIcon.vue'
9+
810
export default {
911
title: 'VaModal',
1012
component: VaModal,
13+
tags: ['autodocs'],
1114
}
1215

1316
export const OldDemos: StoryFn = () => ({
@@ -98,3 +101,201 @@ export const childProps: StoryFn = () => ({
98101
</VaModal>
99102
`,
100103
})
104+
105+
// TODO: add tests
106+
type PositionType = number | `${number}%`
107+
type initialPositionType = { x?: PositionType; y?: PositionType }
108+
export const DraggableAndAttachElement: StoryFn = () => ({
109+
components: { VaModal, VaIcon },
110+
setup () {
111+
const iconsList: {
112+
title: string;
113+
name: string;
114+
flip?: string;
115+
position: initialPositionType;
116+
}[] = [
117+
{ title: 'Top Left', name: 'arrow_outward', flip: 'vertical', position: { x: 0, y: 0 } },
118+
{ title: 'Top Center', name: 'arrow_upward', position: { x: '50%', y: 0 } },
119+
{ title: 'Top Right', name: 'arrow_outward', position: { x: '100%', y: 0 } },
120+
{ title: 'Center Left', name: 'arrow_back', position: { x: 0, y: '50%' } },
121+
{ title: 'Center Center', name: 'circle', position: { x: '50%', y: '50%' } },
122+
{ title: 'Center Right', name: 'arrow_forward', position: { x: '100%', y: '50%' } },
123+
{ title: 'Bottom Left', name: 'arrow_outward', flip: 'both', position: { x: 0, y: '100%' } },
124+
{ title: 'Bottom Center', name: 'arrow_downward', position: { x: '50%', y: '100%' } },
125+
{ title: 'Bottom Right', name: 'arrow_outward', flip: 'horizontal', position: { x: '100%', y: '100%' } },
126+
]
127+
128+
const showModal = ref(false)
129+
const title = ref<string | null>(null)
130+
const initialPosition = ref<initialPositionType | null>(null)
131+
132+
const handleIconClick = (currentTitle: string, position: initialPositionType) => {
133+
title.value = currentTitle
134+
initialPosition.value = position
135+
showModal.value = true
136+
}
137+
138+
const iconsStyle = {
139+
display: 'flex',
140+
flexWrap: 'wrap',
141+
width: '80px',
142+
gap: '4px',
143+
alignItems: 'Center',
144+
}
145+
146+
const attachElementStyle = {
147+
width: '300px',
148+
height: '300px',
149+
border: '2px solid black',
150+
}
151+
152+
return { iconsList, showModal, title, initialPosition, handleIconClick, iconsStyle, attachElementStyle }
153+
},
154+
template: `
155+
<div :style="iconsStyle">
156+
<VaIcon
157+
v-for="({ title, name, flip, position }, index) in iconsList"
158+
:key="index"
159+
:name="name"
160+
:flip="flip"
161+
size="large"
162+
@click="() => handleIconClick(title, position)"
163+
/>
164+
</div>
165+
<div class="modal-attach-element" :style="attachElementStyle"/>
166+
<VaModal
167+
v-model="showModal"
168+
draggable
169+
:draggable-position="initialPosition"
170+
:title="title"
171+
attach-element=".modal-attach-element"
172+
>
173+
Lorem ipsum dolor sit amet.
174+
</VaModal>
175+
`,
176+
})
177+
178+
export const DraggableSlots: StoryFn = () => ({
179+
components: { VaModal, VaIcon },
180+
template: `
181+
<VaModal
182+
model-value="true"
183+
draggable
184+
:draggable-position="{ x: 0, y: 0 }"
185+
title='Top Left position / "#Draggable" slot for drag'
186+
>
187+
<template #draggable={startDrag}>
188+
<span>
189+
<VaIcon
190+
name="drag_indicator"
191+
@mousedown="startDrag"
192+
:style="{cursor: 'move'}"
193+
/>
194+
</span>
195+
</template>
196+
</VaModal>
197+
<VaModal
198+
model-value="true"
199+
draggable
200+
:draggable-position="{ x: '50%', y: 0 }"
201+
>
202+
Top Center position / "#Header" slot for drag
203+
<template #header={startDrag}>
204+
<VaIcon
205+
name="drag_indicator"
206+
@mousedown="startDrag"
207+
:style="{cursor: 'move'}"
208+
/>
209+
</template>
210+
</VaModal>
211+
<VaModal
212+
model-value="true"
213+
draggable
214+
:draggable-position="{ x: '100%', y: 0 }"
215+
title="Top Right position / Full window drag"
216+
>
217+
<template #footer>
218+
Footer slot
219+
</template>
220+
</VaModal>
221+
<VaModal
222+
model-value="true"
223+
draggable
224+
:draggable-position="{ x: 0, y: '50%' }"
225+
>
226+
<template #header>
227+
Center Left position / Header slot / Full window drag
228+
</template>
229+
</VaModal>
230+
<VaModal
231+
model-value="true"
232+
draggable
233+
:draggable-position="{ x: '50%', y: '50%' }"
234+
title='Center Center position / "#Default" slot for drag'
235+
>
236+
<template #default={startDrag}>
237+
<VaIcon
238+
name="drag_indicator"
239+
@mousedown="startDrag"
240+
:style="{cursor: 'move'}"
241+
/>
242+
</template>
243+
</VaModal>
244+
<VaModal
245+
model-value="true"
246+
draggable
247+
:draggable-position="{ x: '100%', y: '50%' }"
248+
>
249+
<template #content={startDrag}>
250+
Center Right position / "#Content" slot for drag
251+
<VaIcon
252+
name="drag_indicator"
253+
@mousedown="startDrag"
254+
:style="{cursor: 'move'}"
255+
/>
256+
</template>
257+
</VaModal>
258+
<VaModal
259+
model-value="true"
260+
draggable
261+
:draggable-position="{ x: 0, y: '100%' }"
262+
title="Bottom Left position / Full window drag"
263+
>
264+
</VaModal>
265+
<VaModal
266+
model-value="true"
267+
draggable
268+
:draggable-position="{ x: '50%', y: '100%' }"
269+
>
270+
Bottom Center position / "#Header" & "#Footer" slots for drag
271+
<template #header={startDrag}>
272+
<VaIcon
273+
name="drag_indicator"
274+
@mousedown="startDrag"
275+
:style="{cursor: 'move'}"
276+
/>
277+
</template>
278+
<template #footer={startDrag}>
279+
<VaIcon
280+
name="drag_indicator"
281+
@mousedown="startDrag"
282+
:style="{cursor: 'move'}"
283+
/>
284+
</template>
285+
</VaModal>
286+
<VaModal
287+
model-value="true"
288+
draggable
289+
:draggable-position="{ x: '100%', y: '100%' }"
290+
title='Bottom Right position / "#Footer" slot for drag'
291+
>
292+
<template #footer={startDrag}>
293+
<VaIcon
294+
name="drag_indicator"
295+
@mousedown="startDrag"
296+
:style="{cursor: 'move'}"
297+
/>
298+
</template>
299+
</VaModal>
300+
`,
301+
})

0 commit comments

Comments
 (0)