Slideover

화면의 어느 쪽에서나 슬라이드 인하는 대화 상자입니다.

Usage

Slideover의 기본 슬롯에 Button 또는 다른 구성 요소를 사용합니다.

그런 다음 #content 슬롯을 사용하여 Slideover가 열릴 때 표시되는 내용을 추가합니다.

<template>
  <USlideover>
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="h-full m-4" />
    </template>
  </USlideover>
</template>

또한 #header, #body 및 #footer 슬롯을 사용하여 Slideover 콘텐츠를 사용자 정의할 수 있습니다.

Title 파일

title prop을 사용하여 Slideover의 헤더 제목을 설정합니다.

<template>
  <USlideover title="Slideover with title">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>

설명

description prop을 사용하여 Slideover 헤더에 대한 설명을 설정합니다.

<template>
  <USlideover
    title="Slideover with description"
    description="Lorem ipsum dolor sit amet, consectetur adipiscing elit."
  >
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>

Close 닫기

close Prop을 사용하여 Slideover 헤더에 표시되는 닫기 버튼(false 값)을 사용자 정의하거나 숨깁니다.

Button 구성 요소의 모든 속성을 전달하여 사용자 정의할 수 있습니다.

<template>
  <USlideover
    title="Slideover with close button"
    :close="{
      color: 'primary',
      variant: 'outline',
      class: 'rounded-full'
    }"
  >
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>
#content 슬롯이 헤더의 일부로 사용되는 경우에는 닫기 버튼이 표시되지 않습니다.

닫기 아이콘

close-icon 소품을 사용하여 닫기 단추 Icon를 사용자 정의합니다. 기본값은 i-lucide-x입니다.

<template>
  <USlideover title="Slideover with close button" close-icon="i-lucide-arrow-right">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>
ui.icons.close 키 아래의 app.config.ts에서 이 아이콘을 전역적으로 사용자 정의할 수 있습니다.
ui.icons.close 키 아래의 vite.config.ts에서 이 아이콘을 전역적으로 사용자 지정할 수 있습니다.

Side 사이드

side 소품을 사용하여 Slideover가 에서 슬라이드로 들어갈 화면의 측면을 설정합니다. 기본값은 right입니다.

<template>
  <USlideover side="left" title="Slideover with side">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full min-h-48" />
    </template>
  </USlideover>
</template>

Inset: badge{label="4.3+" class="align-text-top"} 파일

inset Prop을 사용하여 가장자리에서 Slideover를 삽입합니다.

<template>
  <USlideover side="right" inset title="Slideover with inset">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="min-w-96 min-h-96 size-full" />
    </template>
  </USlideover>
</template>

Transition 변환

transition 소품을 사용하여 Slideover의 애니메이션 여부를 제어합니다. 기본값은 true입니다.

<template>
  <USlideover :transition="false" title="Slideover without transition">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>

Overlay 파일

overlay 소품을 사용하여 Slideover에 오버레이가 있는지 여부를 제어합니다. 기본값은 true입니다.

<template>
  <USlideover :overlay="false" title="Slideover without overlay">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>

Slideover가 외부 내용과의 상호 작용을 차단할지 여부를 제어하려면 modal 소품을 사용합니다. 기본값은 true입니다.

modal를 false로 설정하면 오버레이가 자동으로 비활성화되고 외부 내용은 대화형이 됩니다.
<template>
  <USlideover :modal="false" title="Slideover interactive">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>

사용 안 함

dismissible Prop을 사용하여 Slideover의 바깥쪽을 클릭하거나 Esc 키를 누를 때 Slideover가 허용되지 않는지 여부를 제어합니다. 기본값은 true입니다.

close:prevent 이벤트는 사용자가 종료하려고 할 때 내보내집니다.
modal: false와 dismissible: false를 결합하여 Slideover의 배경을 닫지 않고도 대화형으로 만들 수 있습니다.
<template>
  <USlideover :dismissible="false" title="Slideover non-dismissible">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>

Unmount: badge{label="4.10+" class="align-text-top"} 마운트 해제: badge{label="4.10+" class="align-text-top"}

unmount-on-hide Prop을 사용하여 Slideover가 닫힐 때 콘텐츠가 마운트 해제되지 않도록 합니다. 기본값은 true입니다.

<template>
  <USlideover :unmount-on-hide="false" title="Slideover">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>
  </USlideover>
</template>
DOM을 검사하여 Slideover가 닫혀 있는 동안에도 렌더링되는 내용을 볼 수 있습니다.You can inspect the DOM to see the Slideover's content being rendered even while it is closed.
portal Prop이 false로 설정되어 있으면 내용도 서버에 렌더링됩니다. SSR 중에 페이지 로드 시 플래시 없이 열린 Slideover를 렌더링하거나 SEO에 해당 콘텐츠를 노출하는 데 유용합니다.

examples 예제

Control 오픈 상태

default-open prop 또는 v-model:open 지시문을 사용하여 오픈 상태를 제어할 수 있습니다.

<script setup lang="ts">
const open = ref(false)

defineShortcuts({
  o: () => open.value = !open.value
})
</script>

<template>
  <USlideover v-model:open="open">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="h-full m-4" />
    </template>
  </USlideover>
</template>
<script setup lang="ts">
import { ref } from 'vue'

const open = ref(false)

defineShortcuts({
  o: () => open.value = !open.value
})
</script>

<template>
  <USlideover v-model:open="open">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #content>
      <Placeholder class="h-full m-4" />
    </template>
  </USlideover>
</template>
이 예제에서는 defineShortcuts를 사용하여 kbd{value="O"}를 눌러 Slideover를 토글할 수 있습니다.
이렇게 하면 트리거를 Slideover 밖으로 이동하거나 완전히 제거할 수 있습니다.This lets you move the trigger outside of the Slideover or remove it entirely.

Programmatic 사용법

useOverlay 컴포지션을 사용하여 프로그래밍 방식으로 Slideover를 열 수 있습니다.

앱을 OverlayProvider 구성 요소를 사용하는 App 구성 요소로 래핑해야 합니다.

먼저, 프로그래밍 방식으로 열 슬라이드오버 컴포넌트를 생성합니다.

SlideoverExample.vue
<script setup lang="ts">
defineProps<{
  count: number
}>()

const emit = defineEmits<{ close: [boolean] }>()
</script>

<template>
  <USlideover
    :close="{ onClick: () => emit('close', false) }"
    :description="`This slideover was opened programmatically ${count} times`"
  >
    <template #body>
      <Placeholder class="h-full" />
    </template>

    <template #footer>
      <div class="flex gap-2">
        <UButton color="neutral" label="Dismiss" @click="emit('close', false)" />
        <UButton label="Success" @click="emit('close', true)" />
      </div>
    </template>
  </USlideover>
</template>
슬라이드오버가 닫히거나 해제되면 close 이벤트를 실행합니다. close 이벤트를 통해 데이터를 내보낼 수 있으며 해당 데이터는 open()의 확인된 값이 됩니다. promise가 해결하려면 이벤트를 내보내야 합니다.

그런 다음 앱에서 사용하십시오.Use it in your app:

<script setup lang="ts">
import { LazySlideoverExample } from '#components'

const count = ref(0)

const toast = useToast()
const overlay = useOverlay()

const slideover = overlay.create(LazySlideoverExample)

async function open() {
  const instance = slideover.open({
    count: count.value
  })

  const shouldIncrement = await instance.result

  if (shouldIncrement) {
    count.value++

    toast.add({
      title: `Success: ${shouldIncrement}`,
      color: 'success',
      id: 'slideover-success'
    })

    // Update the count
    slideover.patch({
      count: count.value
    })
    return
  }

  toast.add({
    title: `Dismissed: ${shouldIncrement}`,
    color: 'error',
    id: 'slideover-dismiss'
  })
}
</script>

<template>
  <UButton label="Open" color="neutral" variant="subtle" @click="open" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import { LazySlideoverExample } from '#components'

const count = ref(0)

const toast = useToast()
const overlay = useOverlay()

const slideover = overlay.create(LazySlideoverExample)

async function open() {
  const instance = slideover.open({
    count: count.value
  })

  const shouldIncrement = await instance.result

  if (shouldIncrement) {
    count.value++

    toast.add({
      title: `Success: ${shouldIncrement}`,
      color: 'success',
      id: 'slideover-success'
    })

    // Update the count
    slideover.patch({
      count: count.value
    })
    return
  }

  toast.add({
    title: `Dismissed: ${shouldIncrement}`,
    color: 'error',
    id: 'slideover-dismiss'
  })
}
</script>

<template>
  <UButton label="Open" color="neutral" variant="subtle" @click="open" />
</template>
emit('close')를 내보내면 슬라이드오버 컴포넌트 내에서 슬라이드오버를 닫을 수 있습니다.

Nested 슬라이드오버

슬라이드오버를 서로 내부에 중첩할 수 있습니다.

<script setup lang="ts">
const first = ref(false)
const second = ref(false)
</script>

<template>
  <USlideover v-model:open="first" title="First slideover" :ui="{ footer: 'justify-end' }">
    <UButton color="neutral" variant="subtle" label="Open" />

    <template #body>
      <Placeholder class="h-full" />
    </template>

    <template #footer>
      <UButton label="Close" color="neutral" variant="outline" @click="first = false" />

      <USlideover v-model:open="second" title="Second slideover" :ui="{ footer: 'justify-end' }">
        <UButton label="Open second" color="neutral" />

        <template #body>
          <Placeholder class="h-full" />
        </template>

        <template #footer>
          <UButton label="Close" color="neutral" variant="outline" @click="second = false" />
        </template>
      </USlideover>
    </template>
  </USlideover>
</template>
<script setup lang="ts">
import { ref } from 'vue'

const first = ref(false)
const second = ref(false)
</script>

<template>
  <USlideover v-model:open="first" title="First slideover" :ui="{ footer: 'justify-end' }">
    <UButton color="neutral" variant="subtle" label="Open" />

    <template #body>
      <Placeholder class="h-full" />
    </template>

    <template #footer>
      <UButton label="Close" color="neutral" variant="outline" @click="first = false" />

      <USlideover v-model:open="second" title="Second slideover" :ui="{ footer: 'justify-end' }">
        <UButton label="Open second" color="neutral" />

        <template #body>
          <Placeholder class="h-full" />
        </template>

        <template #footer>
          <UButton label="Close" color="neutral" variant="outline" @click="second = false" />
        </template>
      </USlideover>
    </template>
  </USlideover>
</template>

With 바닥글 슬롯

#footer 슬롯을 사용하여 Slideover 본문 뒤에 내용을 추가합니다.

<script setup lang="ts">
const open = ref(false)
</script>

<template>
  <USlideover v-model:open="open" title="Slideover with footer" description="This is useful when you want a form in a Slideover." :ui="{ footer: 'justify-end' }">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>

    <template #footer="{ close }">
      <UButton label="Cancel" color="neutral" variant="outline" @click="close" />
      <UButton label="Submit" color="neutral" />
    </template>
  </USlideover>
</template>
<script setup lang="ts">
import { ref } from 'vue'

const open = ref(false)
</script>

<template>
  <USlideover v-model:open="open" title="Slideover with footer" description="This is useful when you want a form in a Slideover." :ui="{ footer: 'justify-end' }">
    <UButton label="Open" color="neutral" variant="subtle" />

    <template #body>
      <Placeholder class="h-full" />
    </template>

    <template #footer="{ close }">
      <UButton label="Cancel" color="neutral" variant="outline" @click="close" />
      <UButton label="Submit" color="neutral" />
    </template>
  </USlideover>
</template>

API 파일

Props 코드

Prop Default Type
title
string
description
string
content
DialogContentProps & Partial<EmitsToProps<DialogContentImplEmits>>

The content of the slideover.

overlay
true
boolean

Render an overlay behind the slideover.

transition
true
boolean

Animate the slideover when opening or closing.

side
'right'
"right" | "top" | "bottom" | "left"

The side of the slideover.

inset
false
boolean

Whether to inset the slideover from the edges.

portal
true
string | false | true | HTMLElement

Render the slideover in a portal.

close
true
boolean | Omit<ButtonProps, LinkPropsKeys>

Display a close button to dismiss the slideover. { size: 'md', color: 'neutral', variant: 'ghost' }

closeIcon
appConfig.ui.icons.close
any

The icon displayed in the close button.

dismissible
true
boolean

When false, the slideover will not close when clicking outside or pressing escape.

open
boolean

The controlled open state of the dialog. Can be binded as v-model:open.

defaultOpen
boolean

The open state of the dialog when it is initially rendered. Use when you do not need to control its open state.

modal
true
boolean

The modality of the dialog When set to true,
interaction with outside elements will be disabled and only dialog content will be visible to screen readers.

unmountOnHide
true
boolean

When set to false, the dialog content will not be unmounted when closed, but instead hidden with CSS.
Useful for SEO or when you want to improve performance by not remounting the component on every open.

ui
{ overlay?: SlotClass; content?: SlotClass; header?: SlotClass; wrapper?: SlotClass; body?: SlotClass; footer?: SlotClass; title?: SlotClass; description?: SlotClass; close?: SlotClass; }

슬롯

Slot Type
default
{ open: boolean; }
content
{ close: () => void; }
header
{ close: () => void; }
title
{}
description
{}
actions
{}
close
{ ui: object; }
body
{ close: () => void; }
footer
{ close: () => void; }

Emits

Event Type
leave
[]
after:leave
[]
enter
[]
after:enter
[]
close:prevent
[]
update:open
[value: boolean]

Theme (## 테마)

app.config.ts
export default defineAppConfig({
  ui: {
    slideover: {
      slots: {
        overlay: 'fixed inset-0 bg-elevated/75',
        content: 'fixed bg-default divide-y divide-default sm:ring ring-default sm:shadow-lg flex flex-col focus:outline-none',
        header: 'flex items-center gap-1.5 p-4 sm:px-6 min-h-(--ui-header-height)',
        wrapper: '',
        body: 'flex-1 overflow-y-auto p-4 sm:p-6',
        footer: 'flex items-center gap-1.5 p-4 sm:px-6',
        title: 'text-highlighted font-semibold',
        description: 'mt-1 text-muted text-sm',
        close: 'absolute top-4 end-4'
      },
      variants: {
        side: {
          top: {
            content: ''
          },
          right: {
            content: 'max-w-md'
          },
          bottom: {
            content: ''
          },
          left: {
            content: 'max-w-md'
          }
        },
        inset: {
          true: {
            content: 'rounded-lg'
          }
        },
        transition: {
          true: {
            overlay: 'data-[state=open]:animate-[fade-in_200ms_var(--ease-out)] data-[state=closed]:animate-[fade-out_200ms_var(--ease-out)]'
          }
        }
      },
      compoundVariants: [
        {
          side: 'top',
          inset: true,
          class: {
            content: 'max-h-[calc(100%-2rem)] inset-x-4 top-4'
          }
        },
        {
          side: 'top',
          inset: false,
          class: {
            content: 'max-h-full inset-x-0 top-0'
          }
        },
        {
          side: 'right',
          inset: true,
          class: {
            content: 'w-[calc(100%-2rem)] inset-y-4 right-4'
          }
        },
        {
          side: 'right',
          inset: false,
          class: {
            content: 'w-full inset-y-0 right-0'
          }
        },
        {
          side: 'bottom',
          inset: true,
          class: {
            content: 'max-h-[calc(100%-2rem)] inset-x-4 bottom-4'
          }
        },
        {
          side: 'bottom',
          inset: false,
          class: {
            content: 'max-h-full inset-x-0 bottom-0'
          }
        },
        {
          side: 'left',
          inset: true,
          class: {
            content: 'w-[calc(100%-2rem)] inset-y-4 left-4'
          }
        },
        {
          side: 'left',
          inset: false,
          class: {
            content: 'w-full inset-y-0 left-0'
          }
        },
        {
          transition: true,
          side: 'top',
          class: {
            content: 'data-[state=open]:animate-[slide-in-from-top_200ms_var(--ease-out)] data-[state=closed]:animate-[slide-out-to-top_200ms_var(--ease-out)]'
          }
        },
        {
          transition: true,
          side: 'right',
          class: {
            content: 'data-[state=open]:animate-[slide-in-from-right_200ms_var(--ease-out)] data-[state=closed]:animate-[slide-out-to-right_200ms_var(--ease-out)]'
          }
        },
        {
          transition: true,
          side: 'bottom',
          class: {
            content: 'data-[state=open]:animate-[slide-in-from-bottom_200ms_var(--ease-out)] data-[state=closed]:animate-[slide-out-to-bottom_200ms_var(--ease-out)]'
          }
        },
        {
          transition: true,
          side: 'left',
          class: {
            content: 'data-[state=open]:animate-[slide-in-from-left_200ms_var(--ease-out)] data-[state=closed]:animate-[slide-out-to-left_200ms_var(--ease-out)]'
          }
        }
      ]
    }
  }
})
vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'

export default defineConfig({
  plugins: [
    vue(),
    ui({
      ui: {
        slideover: {
          slots: {
            overlay: 'fixed inset-0 bg-elevated/75',
            content: 'fixed bg-default divide-y divide-default sm:ring ring-default sm:shadow-lg flex flex-col focus:outline-none',
            header: 'flex items-center gap-1.5 p-4 sm:px-6 min-h-(--ui-header-height)',
            wrapper: '',
            body: 'flex-1 overflow-y-auto p-4 sm:p-6',
            footer: 'flex items-center gap-1.5 p-4 sm:px-6',
            title: 'text-highlighted font-semibold',
            description: 'mt-1 text-muted text-sm',
            close: 'absolute top-4 end-4'
          },
          variants: {
            side: {
              top: {
                content: ''
              },
              right: {
                content: 'max-w-md'
              },
              bottom: {
                content: ''
              },
              left: {
                content: 'max-w-md'
              }
            },
            inset: {
              true: {
                content: 'rounded-lg'
              }
            },
            transition: {
              true: {
                overlay: 'data-[state=open]:animate-[fade-in_200ms_var(--ease-out)] data-[state=closed]:animate-[fade-out_200ms_var(--ease-out)]'
              }
            }
          },
          compoundVariants: [
            {
              side: 'top',
              inset: true,
              class: {
                content: 'max-h-[calc(100%-2rem)] inset-x-4 top-4'
              }
            },
            {
              side: 'top',
              inset: false,
              class: {
                content: 'max-h-full inset-x-0 top-0'
              }
            },
            {
              side: 'right',
              inset: true,
              class: {
                content: 'w-[calc(100%-2rem)] inset-y-4 right-4'
              }
            },
            {
              side: 'right',
              inset: false,
              class: {
                content: 'w-full inset-y-0 right-0'
              }
            },
            {
              side: 'bottom',
              inset: true,
              class: {
                content: 'max-h-[calc(100%-2rem)] inset-x-4 bottom-4'
              }
            },
            {
              side: 'bottom',
              inset: false,
              class: {
                content: 'max-h-full inset-x-0 bottom-0'
              }
            },
            {
              side: 'left',
              inset: true,
              class: {
                content: 'w-[calc(100%-2rem)] inset-y-4 left-4'
              }
            },
            {
              side: 'left',
              inset: false,
              class: {
                content: 'w-full inset-y-0 left-0'
              }
            },
            {
              transition: true,
              side: 'top',
              class: {
                content: 'data-[state=open]:animate-[slide-in-from-top_200ms_var(--ease-out)] data-[state=closed]:animate-[slide-out-to-top_200ms_var(--ease-out)]'
              }
            },
            {
              transition: true,
              side: 'right',
              class: {
                content: 'data-[state=open]:animate-[slide-in-from-right_200ms_var(--ease-out)] data-[state=closed]:animate-[slide-out-to-right_200ms_var(--ease-out)]'
              }
            },
            {
              transition: true,
              side: 'bottom',
              class: {
                content: 'data-[state=open]:animate-[slide-in-from-bottom_200ms_var(--ease-out)] data-[state=closed]:animate-[slide-out-to-bottom_200ms_var(--ease-out)]'
              }
            },
            {
              transition: true,
              side: 'left',
              class: {
                content: 'data-[state=open]:animate-[slide-in-from-left_200ms_var(--ease-out)] data-[state=closed]:animate-[slide-out-to-left_200ms_var(--ease-out)]'
              }
            }
          ]
        }
      }
    })
  ]
})

변경 로그

No recent changes