import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/solid"
import {
batch,
createContext,
createEffect,
Show,
useContext,
type JSX,
type ParentProps,
} from "solid-js"
import { useTheme } from "@tui/context/theme"
import { Renderable, RGBA } from "@opentui/core"
import { createStore } from "solid-js/store"
import { createEventBus } from "@solid-primitives/event-bus"
export function Dialog(
props: ParentProps<{
size?: "medium" | "large"
onClose: () => void
}>,
) {
const dimensions = useTerminalDimensions()
const { theme } = useTheme()
return (
{
props.onClose?.()
}}
width={dimensions().width}
height={dimensions().height}
alignItems="center"
position="absolute"
paddingTop={dimensions().height / 4}
left={0}
top={0}
backgroundColor={RGBA.fromInts(0, 0, 0, 150)}
>
{
e.stopPropagation()
}}
width={props.size === "large" ? 80 : 60}
maxWidth={dimensions().width - 2}
backgroundColor={theme.backgroundPanel}
paddingTop={1}
>
{props.children}
)
}
function init() {
const [store, setStore] = createStore({
stack: [] as {
element: JSX.Element
onClose?: () => void
}[],
size: "medium" as "medium" | "large",
})
const allClosedEvent = createEventBus()
useKeyboard((evt) => {
if (evt.name === "escape" && store.stack.length > 0) {
const current = store.stack.at(-1)!
current.onClose?.()
setStore("stack", store.stack.slice(0, -1))
evt.preventDefault()
refocus()
}
})
const renderer = useRenderer()
let focus: Renderable | null
function refocus() {
setTimeout(() => {
if (!focus) return
if (focus.isDestroyed) return
function find(item: Renderable) {
for (const child of item.getChildren()) {
if (child === focus) return true
if (find(child)) return true
}
return false
}
const found = find(renderer.root)
if (!found) return
focus.focus()
}, 1)
}
createEffect(() => {
if (store.stack.length === 0) {
allClosedEvent.emit()
}
})
return {
clear() {
for (const item of store.stack) {
if (item.onClose) item.onClose()
}
batch(() => {
setStore("size", "medium")
setStore("stack", [])
})
refocus()
},
replace(input: any, onClose?: () => void) {
if (store.stack.length === 0) focus = renderer.currentFocusedRenderable
for (const item of store.stack) {
if (item.onClose) item.onClose()
}
setStore("size", "medium")
setStore("stack", [
{
element: input,
onClose,
},
])
},
get stack() {
return store.stack
},
get size() {
return store.size
},
setSize(size: "medium" | "large") {
setStore("size", size)
},
get allClosedEvent() {
return allClosedEvent
},
}
}
export type DialogContext = ReturnType
const ctx = createContext()
export function DialogProvider(props: ParentProps) {
const value = init()
return (
{props.children}
)
}
export function useDialog() {
const value = useContext(ctx)
if (!value) {
throw new Error("useDialog must be used within a DialogProvider")
}
return value
}