Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add useDraggable composable #4486

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export default defineApiDescription({
overlayOpacity: "Set the overlay's opacity",
anchorClass: "Set class name to the `anchor` slot container",
zIndex: "Set the modal's `z-index`",
draggable: "Enable modal dragging",
draggablePosition: "Set the initial position of the draggable modal",
allowBodyScroll: "Allows the document scroll while modal is open.",
blur: "Use `blur` filter to overlay. Root `css` variable `--va-modal-overlay-background-blur-radius` sets the blur radius",
ariaCloseLabel: "The aria-label of the close button",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<template>
<div class="icons">
<VaIcon
v-for="({ name, flip, position }, index) in iconsList"
:key="index"
:name="name"
:flip="flip"
size="large"
@click="() => handleIconClick(position)"
/>
</div>
<VaModal
v-model="showModal"
draggable
:draggable-position="initialPosition"
:title="initialPosition"
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam ut lobortis
dui.
</VaModal>
</template>

<script setup>
import { ref } from "vue";

const iconsList = [
{ name: 'arrow_outward', flip: 'vertical', position: { x: 0, y: 0 } },
{ name: 'arrow_upward', position: { x: '50%', y: 0 } },
{ name: 'arrow_outward', position: { x: '100%', y: 0 } },
{ name: 'arrow_back', position: { x: 0, y: '50%' } },
{ name: 'circle', position: { x: '50%', y: '50%' } },
{ name: 'arrow_forward', position: { x: '100%', y: '50%' } },
{ name: 'arrow_outward', flip: 'both', position: { x: 0, y: '100%' } },
{ name: 'arrow_downward', position: { x: '50%', y: '100%' } },
{ name: 'arrow_outward', flip: 'horizontal', position: { x: '100%', y: '100%' } },
]

const showModal = ref(false)
const initialPosition = ref(iconsList[0].position)

const handleIconClick = (position) => {
initialPosition.value = position
showModal.value = true
}
</script>

<style lang="scss" scoped>
.icons {
display: flex;
flex-wrap: wrap;
width: 80px;
gap: 4px;
align-items: center;

* {
flex: 0 0 24px;
transition: color 0.25s ease-in-out;

&:hover {
color: var(--va-primary);
}
}
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<template>
<div class="buttons">
<VaButton @click="showModalIconDraggable = true">
Open modal with draggable icon
</VaButton>
<VaButton @click="showModalHeaderDraggable = true">
Open modal with draggable header
</VaButton>
<VaButton @click="showModalFooterDraggable = true">
Open modal with draggable footer
</VaButton>
</div>
<VaModal
v-model="showModalIconDraggable"
draggable
title="With draggable icon"
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<template #draggable="{startDrag}">
<VaIcon name="drag_indicator" :style="{ cursor: 'move', position: 'absolute', top: '10px', right: '10px' }" @mousedown="startDrag" />
</template>
</VaModal>
<VaModal
v-model="showModalHeaderDraggable"
draggable
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<template #header="{startDrag}">
<h3 class="va-h3" :style="{ cursor: 'move' }" @mousedown="startDrag">
<VaIcon name="drag_indicator" :style="{ cursor: 'move' }" @mousedown="startDrag" /> Draggable Header
</h3>
</template>
</VaModal>
<VaModal
v-model="showModalFooterDraggable"
draggable
title="With draggable footer"
>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
<template #footer="{startDrag}">
<VaIcon name="drag_indicator" :style="{ cursor: 'move', position: 'absolute' ,left: '10px', bottom: '10px' }" @mousedown="startDrag" />
</template>
</VaModal>
</template>

<script setup>
import { ref } from "vue";

const showModalIconDraggable = ref(false)
const showModalHeaderDraggable = ref(false)
const showModalFooterDraggable = ref(false)
</script>

<style lang="scss" scoped>
.buttons {
display: flex;
justify-content: space-between;
}
</style>
10 changes: 10 additions & 0 deletions packages/docs/page-config/ui-elements/modal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,16 @@ export default definePageConfig({
description: "Modals can be nested: you can open one modal from another."
}),

block.example("DraggableModals", {
title: "Draggable modals",
description: "Modals can be draggable: you can open modal in 1 of 9 positions and drag it."
}),

block.example("DraggableSlots", {
title: "Draggable slots",
description: "Draggable modal can be customize with any slots for drag interaction."
}),

block.subtitle("API"),
block.api("VaModal", apiDescription, apiOptions),

Expand Down
2 changes: 2 additions & 0 deletions packages/docs/page-config/ui-elements/modal/modal-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ const modalOptions: Record<keyof ModalOptions, string> = {
overlay: "boolean",
overlayOpacity: "number | string",
zIndex: "number | string",
draggable: "boolean",
draggablePosition: "{ x?: number | string, y?: number | string }",
Copy link
Collaborator Author

@TFX-98 TFX-98 Mar 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be better to set type like number | '${number}%'?

onOk: "() => void",
onCancel: "() => void",
onClickOutside: "() => void",
Expand Down
201 changes: 201 additions & 0 deletions packages/ui/src/components/va-modal/VaModal.stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ import { StoryFn } from '@storybook/vue3'
import { expect } from '@storybook/jest'
import { userEvent } from '../../../.storybook/interaction-utils/userEvent'

import VaIcon from '../va-icon/VaIcon.vue'

export default {
title: 'VaModal',
component: VaModal,
tags: ['autodocs'],
}

export const OldDemos: StoryFn = () => ({
Expand Down Expand Up @@ -98,3 +101,201 @@ export const childProps: StoryFn = () => ({
</VaModal>
`,
})

// TODO: add tests
type PositionType = number | `${number}%`
type initialPositionType = { x?: PositionType; y?: PositionType }
export const DraggableAndAttachElement: StoryFn = () => ({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const DraggableAndAttachElement: StoryFn = () => ({
export const DraggableAndAttachElement = defineStory({
  • tests

components: { VaModal, VaIcon },
setup () {
const iconsList: {
title: string;
name: string;
flip?: string;
position: initialPositionType;
}[] = [
{ title: 'Top Left', name: 'arrow_outward', flip: 'vertical', position: { x: 0, y: 0 } },
{ title: 'Top Center', name: 'arrow_upward', position: { x: '50%', y: 0 } },
{ title: 'Top Right', name: 'arrow_outward', position: { x: '100%', y: 0 } },
{ title: 'Center Left', name: 'arrow_back', position: { x: 0, y: '50%' } },
{ title: 'Center Center', name: 'circle', position: { x: '50%', y: '50%' } },
{ title: 'Center Right', name: 'arrow_forward', position: { x: '100%', y: '50%' } },
{ title: 'Bottom Left', name: 'arrow_outward', flip: 'both', position: { x: 0, y: '100%' } },
{ title: 'Bottom Center', name: 'arrow_downward', position: { x: '50%', y: '100%' } },
{ title: 'Bottom Right', name: 'arrow_outward', flip: 'horizontal', position: { x: '100%', y: '100%' } },
]

const showModal = ref(false)
const title = ref<string | null>(null)
const initialPosition = ref<initialPositionType | null>(null)

const handleIconClick = (currentTitle: string, position: initialPositionType) => {
title.value = currentTitle
initialPosition.value = position
showModal.value = true
}

const iconsStyle = {
display: 'flex',
flexWrap: 'wrap',
width: '80px',
gap: '4px',
alignItems: 'Center',
}

const attachElementStyle = {
width: '300px',
height: '300px',
border: '2px solid black',
}

return { iconsList, showModal, title, initialPosition, handleIconClick, iconsStyle, attachElementStyle }
},
template: `
<div :style="iconsStyle">
<VaIcon
v-for="({ title, name, flip, position }, index) in iconsList"
:key="index"
:name="name"
:flip="flip"
size="large"
@click="() => handleIconClick(title, position)"
/>
</div>
<div class="modal-attach-element" :style="attachElementStyle"/>
<VaModal
v-model="showModal"
draggable
:draggable-position="initialPosition"
:title="title"
attach-element=".modal-attach-element"
>
Lorem ipsum dolor sit amet.
</VaModal>
`,
})

export const DraggableSlots: StoryFn = () => ({
components: { VaModal, VaIcon },
template: `
<VaModal
model-value="true"
draggable
:draggable-position="{ x: 0, y: 0 }"
title='Top Left position / "#Draggable" slot for drag'
>
<template #draggable={startDrag}>
<span>
<VaIcon
name="drag_indicator"
@mousedown="startDrag"
:style="{cursor: 'move'}"
/>
</span>
</template>
</VaModal>
<VaModal
model-value="true"
draggable
:draggable-position="{ x: '50%', y: 0 }"
>
Top Center position / "#Header" slot for drag
<template #header={startDrag}>
<VaIcon
name="drag_indicator"
@mousedown="startDrag"
:style="{cursor: 'move'}"
/>
</template>
</VaModal>
<VaModal
model-value="true"
draggable
:draggable-position="{ x: '100%', y: 0 }"
title="Top Right position / Full window drag"
>
<template #footer>
Footer slot
</template>
</VaModal>
<VaModal
model-value="true"
draggable
:draggable-position="{ x: 0, y: '50%' }"
>
<template #header>
Center Left position / Header slot / Full window drag
</template>
</VaModal>
<VaModal
model-value="true"
draggable
:draggable-position="{ x: '50%', y: '50%' }"
title='Center Center position / "#Default" slot for drag'
>
<template #default={startDrag}>
<VaIcon
name="drag_indicator"
@mousedown="startDrag"
:style="{cursor: 'move'}"
/>
</template>
</VaModal>
<VaModal
model-value="true"
draggable
:draggable-position="{ x: '100%', y: '50%' }"
>
<template #content={startDrag}>
Center Right position / "#Content" slot for drag
<VaIcon
name="drag_indicator"
@mousedown="startDrag"
:style="{cursor: 'move'}"
/>
</template>
</VaModal>
<VaModal
model-value="true"
draggable
:draggable-position="{ x: 0, y: '100%' }"
title="Bottom Left position / Full window drag"
>
</VaModal>
<VaModal
model-value="true"
draggable
:draggable-position="{ x: '50%', y: '100%' }"
>
Bottom Center position / "#Header" & "#Footer" slots for drag
<template #header={startDrag}>
<VaIcon
name="drag_indicator"
@mousedown="startDrag"
:style="{cursor: 'move'}"
/>
</template>
<template #footer={startDrag}>
<VaIcon
name="drag_indicator"
@mousedown="startDrag"
:style="{cursor: 'move'}"
/>
</template>
</VaModal>
<VaModal
model-value="true"
draggable
:draggable-position="{ x: '100%', y: '100%' }"
title='Bottom Right position / "#Footer" slot for drag'
>
<template #footer={startDrag}>
<VaIcon
name="drag_indicator"
@mousedown="startDrag"
:style="{cursor: 'move'}"
/>
</template>
</VaModal>
`,
})
Loading
Loading