feat(app): improve servers UI (#30961)
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
||||
useContext,
|
||||
type JSX,
|
||||
startTransition,
|
||||
For,
|
||||
} from "solid-js"
|
||||
import { Dialog as Kobalte } from "@kobalte/core/dialog"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
@@ -29,7 +30,7 @@ type Active = {
|
||||
const Context = createContext<ReturnType<typeof init>>()
|
||||
|
||||
function init() {
|
||||
const [active, setActive] = createSignal<Active | undefined>()
|
||||
const [stack, setStack] = createSignal<Active[]>([])
|
||||
const timer = { current: undefined as ReturnType<typeof setTimeout> | undefined }
|
||||
const lock = { value: false }
|
||||
|
||||
@@ -39,14 +40,15 @@ function init() {
|
||||
timer.current = undefined
|
||||
})
|
||||
|
||||
const close = () => {
|
||||
const current = active()
|
||||
const close = (id?: string) => {
|
||||
const items = stack()
|
||||
const current = id ? items.find((item) => item.id === id) : items.at(-1)
|
||||
if (!current || lock.value) return
|
||||
lock.value = true
|
||||
current.onClose?.()
|
||||
current.setClosing(true)
|
||||
|
||||
const id = current.id
|
||||
const closed = current.id
|
||||
if (timer.current !== undefined) {
|
||||
clearTimeout(timer.current)
|
||||
timer.current = undefined
|
||||
@@ -55,13 +57,13 @@ function init() {
|
||||
timer.current = setTimeout(() => {
|
||||
timer.current = undefined
|
||||
current.dispose()
|
||||
if (active()?.id === id) setActive(undefined)
|
||||
setStack((items) => items.filter((item) => item.id !== closed))
|
||||
lock.value = false
|
||||
}, 100)
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
if (!active()) return
|
||||
if (stack().length === 0) return
|
||||
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key !== "Escape") return
|
||||
@@ -73,21 +75,9 @@ function init() {
|
||||
makeEventListener(window, "keydown", onKeyDown, { capture: true })
|
||||
})
|
||||
|
||||
const show = (element: DialogElement, owner: Owner, onClose?: () => void) => {
|
||||
// Immediately dispose any existing dialog when showing a new one
|
||||
const current = active()
|
||||
if (current) {
|
||||
current.dispose()
|
||||
setActive(undefined)
|
||||
}
|
||||
|
||||
if (timer.current !== undefined) {
|
||||
clearTimeout(timer.current)
|
||||
timer.current = undefined
|
||||
}
|
||||
lock.value = false
|
||||
|
||||
const mount = (element: DialogElement, owner: Owner, onClose: (() => void) | undefined, layer: number) => {
|
||||
const id = Math.random().toString(36).slice(2)
|
||||
const zIndex = 50 + layer * 10
|
||||
let dispose: (() => void) | undefined
|
||||
let setClosing: ((closing: boolean) => void) | undefined
|
||||
|
||||
@@ -102,12 +92,29 @@ function init() {
|
||||
open={!closing()}
|
||||
onOpenChange={(open: boolean) => {
|
||||
if (open) return
|
||||
close()
|
||||
close(id)
|
||||
}}
|
||||
>
|
||||
<Kobalte.Portal>
|
||||
<Kobalte.Overlay data-component="dialog-overlay" onClick={close} />
|
||||
{element()}
|
||||
<Kobalte.Overlay
|
||||
data-component="dialog-overlay"
|
||||
style={{ "z-index": String(zIndex) }}
|
||||
onClick={() => close(id)}
|
||||
/>
|
||||
<div
|
||||
data-dialog-layer={layer}
|
||||
style={{
|
||||
position: "fixed",
|
||||
inset: "0",
|
||||
"z-index": String(zIndex),
|
||||
display: "flex",
|
||||
"align-items": "center",
|
||||
"justify-content": "center",
|
||||
"pointer-events": "none",
|
||||
}}
|
||||
>
|
||||
{element()}
|
||||
</div>
|
||||
</Kobalte.Portal>
|
||||
</Kobalte>
|
||||
)
|
||||
@@ -116,15 +123,35 @@ function init() {
|
||||
|
||||
if (!dispose || !setClosing) return
|
||||
|
||||
setActive({ id, node, dispose, owner, onClose, setClosing })
|
||||
const active: Active = { id, node, dispose, owner, onClose, setClosing }
|
||||
setStack((items) => [...items, active])
|
||||
}
|
||||
|
||||
const push = (element: DialogElement, owner: Owner, onClose?: () => void) => {
|
||||
if (timer.current !== undefined) {
|
||||
clearTimeout(timer.current)
|
||||
timer.current = undefined
|
||||
}
|
||||
lock.value = false
|
||||
mount(element, owner, onClose, stack().length)
|
||||
}
|
||||
|
||||
const show = (element: DialogElement, owner: Owner, onClose?: () => void) => {
|
||||
for (const item of stack()) item.dispose()
|
||||
setStack([])
|
||||
if (timer.current !== undefined) {
|
||||
clearTimeout(timer.current)
|
||||
timer.current = undefined
|
||||
}
|
||||
lock.value = false
|
||||
mount(element, owner, onClose, 0)
|
||||
}
|
||||
|
||||
return {
|
||||
get active() {
|
||||
return active()
|
||||
},
|
||||
stack,
|
||||
close,
|
||||
show,
|
||||
push,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +160,9 @@ export function DialogProvider(props: ParentProps) {
|
||||
return (
|
||||
<Context.Provider value={ctx}>
|
||||
{props.children}
|
||||
<div data-component="dialog-stack">{ctx.active?.node}</div>
|
||||
<div data-component="dialog-stack">
|
||||
<For each={ctx.stack()}>{(item) => item.node}</For>
|
||||
</div>
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
@@ -151,12 +180,16 @@ export function useDialog() {
|
||||
|
||||
return {
|
||||
get active() {
|
||||
return ctx.active
|
||||
return ctx.stack().at(-1)
|
||||
},
|
||||
show(element: DialogElement, onClose?: () => void) {
|
||||
const base = ctx.active?.owner ?? owner
|
||||
const base = ctx.stack().at(-1)?.owner ?? owner
|
||||
return startTransition(() => ctx.show(element, base, onClose))
|
||||
},
|
||||
push(element: DialogElement, onClose?: () => void) {
|
||||
const base = ctx.stack().at(-1)?.owner ?? owner
|
||||
return startTransition(() => ctx.push(element, base, onClose))
|
||||
},
|
||||
close() {
|
||||
ctx.close()
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user