feat(ui): add optional transition animations to dialog

This commit is contained in:
David Hill
2026-01-26 16:39:01 +00:00
parent 0a572afd46
commit 92229b44f8
4 changed files with 30 additions and 21 deletions

View File

@@ -14,7 +14,7 @@ export const DialogSettings: Component = () => {
const platform = usePlatform() const platform = usePlatform()
return ( return (
<Dialog size="x-large"> <Dialog size="x-large" transition>
<Tabs orientation="vertical" variant="settings" defaultValue="general" class="h-full settings-dialog"> <Tabs orientation="vertical" variant="settings" defaultValue="general" class="h-full settings-dialog">
<Tabs.List> <Tabs.List>
<div class="flex flex-col justify-between h-full w-full"> <div class="flex flex-col justify-between h-full w-full">

View File

@@ -5,12 +5,6 @@
inset: 0; inset: 0;
z-index: 50; z-index: 50;
background-color: hsl(from var(--background-base) h s l / 0.2); background-color: hsl(from var(--background-base) h s l / 0.2);
/* animation: overlayHide 250ms ease 100ms forwards; */
/**/
/* &[data-expanded] { */
/* animation: overlayShow 250ms ease; */
/* } */
} }
[data-component="dialog"] { [data-component="dialog"] {
@@ -58,12 +52,6 @@
background-clip: padding-box; background-clip: padding-box;
box-shadow: var(--shadow-lg-border-base); box-shadow: var(--shadow-lg-border-base);
/* animation: contentHide 300ms ease-in forwards; */
/**/
/* &[data-expanded] { */
/* animation: contentShow 300ms ease-out; */
/* } */
[data-slot="dialog-header"] { [data-slot="dialog-header"] {
display: flex; display: flex;
padding: 20px; padding: 20px;
@@ -147,6 +135,14 @@
} }
} }
[data-component="dialog"][data-transition] [data-slot="dialog-content"] {
animation: contentHide 100ms ease-in forwards;
&[data-expanded] {
animation: contentShow 200ms ease-out;
}
}
@keyframes overlayShow { @keyframes overlayShow {
from { from {
opacity: 0; opacity: 0;
@@ -166,7 +162,7 @@
@keyframes contentShow { @keyframes contentShow {
from { from {
opacity: 0; opacity: 0;
transform: scale(0.96); transform: scale(0.98);
} }
to { to {
opacity: 1; opacity: 1;
@@ -180,6 +176,6 @@
} }
to { to {
opacity: 0; opacity: 0;
transform: scale(0.96); transform: scale(0.98);
} }
} }

View File

@@ -11,12 +11,18 @@ export interface DialogProps extends ParentProps {
class?: ComponentProps<"div">["class"] class?: ComponentProps<"div">["class"]
classList?: ComponentProps<"div">["classList"] classList?: ComponentProps<"div">["classList"]
fit?: boolean fit?: boolean
transition?: boolean
} }
export function Dialog(props: DialogProps) { export function Dialog(props: DialogProps) {
const i18n = useI18n() const i18n = useI18n()
return ( return (
<div data-component="dialog" data-fit={props.fit ? true : undefined} data-size={props.size || "normal"}> <div
data-component="dialog"
data-fit={props.fit ? true : undefined}
data-size={props.size || "normal"}
data-transition={props.transition ? true : undefined}
>
<div data-slot="dialog-container"> <div data-slot="dialog-container">
<Kobalte.Content <Kobalte.Content
data-slot="dialog-content" data-slot="dialog-content"

View File

@@ -21,6 +21,7 @@ type Active = {
dispose: () => void dispose: () => void
owner: Owner owner: Owner
onClose?: () => void onClose?: () => void
setClosing: (closing: boolean) => void
} }
const Context = createContext<ReturnType<typeof init>>() const Context = createContext<ReturnType<typeof init>>()
@@ -32,8 +33,11 @@ function init() {
const current = active() const current = active()
if (!current) return if (!current) return
current.onClose?.() current.onClose?.()
current.dispose() current.setClosing(true)
setActive(undefined) setTimeout(() => {
current.dispose()
setActive(undefined)
}, 100)
} }
createEffect(() => { createEffect(() => {
@@ -55,14 +59,17 @@ function init() {
const id = Math.random().toString(36).slice(2) const id = Math.random().toString(36).slice(2)
let dispose: (() => void) | undefined let dispose: (() => void) | undefined
let setClosing: ((closing: boolean) => void) | undefined
const node = runWithOwner(owner, () => const node = runWithOwner(owner, () =>
createRoot((d: () => void) => { createRoot((d: () => void) => {
dispose = d dispose = d
const [closing, setClosingSignal] = createSignal(false)
setClosing = setClosingSignal
return ( return (
<Kobalte <Kobalte
modal modal
open={true} open={!closing()}
onOpenChange={(open: boolean) => { onOpenChange={(open: boolean) => {
if (open) return if (open) return
close() close()
@@ -77,9 +84,9 @@ function init() {
}), }),
) )
if (!dispose) return if (!dispose || !setClosing) return
setActive({ id, node, dispose, owner, onClose }) setActive({ id, node, dispose, owner, onClose, setClosing })
} }
return { return {