summaryrefslogtreecommitdiffhomepage
path: root/packages/ui/src/context/dialog.tsx
blob: f85eb48df8015b246f571157c488f946deca772d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import {
  createContext,
  createSignal,
  getOwner,
  Owner,
  ParentProps,
  runWithOwner,
  Show,
  useContext,
  type JSX,
} from "solid-js"
import { Dialog as Kobalte } from "@kobalte/core/dialog"

type DialogElement = () => JSX.Element

const Context = createContext<ReturnType<typeof init>>()

function init() {
  const [active, setActive] = createSignal<
    | {
        id: string
        element: DialogElement
        onClose?: () => void
        owner: Owner
      }
    | undefined
  >()

  const result = {
    get active() {
      return active()
    },
    close() {
      active()?.onClose?.()
      setActive(undefined)
    },
    show(element: DialogElement, owner: Owner, onClose?: () => void) {
      active()?.onClose?.()
      const id = Math.random().toString(36).slice(2)
      setActive({
        id,
        element: () =>
          runWithOwner(owner, () => (
            <Show when={active()?.id === id}>
              <Kobalte
                modal
                open={true}
                onOpenChange={(open) => {
                  if (!open) {
                    result.close()
                  }
                }}
              >
                <Kobalte.Portal>
                  <Kobalte.Overlay data-component="dialog-overlay" />
                  {element()}
                </Kobalte.Portal>
              </Kobalte>
            </Show>
          )),
        onClose,
        owner,
      })
    },
  }

  return result
}

export function DialogProvider(props: ParentProps) {
  const ctx = init()
  return (
    <Context.Provider value={ctx}>
      {props.children}
      <div data-component="dialog-stack">{ctx.active?.element?.()}</div>
    </Context.Provider>
  )
}

export function useDialog() {
  const ctx = useContext(Context)
  const owner = getOwner()
  if (!owner) {
    throw new Error("useDialog must be used within a DialogProvider")
  }
  if (!ctx) {
    throw new Error("useDialog must be used within a DialogProvider")
  }
  return {
    get active() {
      return ctx.active
    },
    show(element: DialogElement, onClose?: () => void) {
      ctx.show(element, owner, onClose)
    },
    close() {
      ctx.close()
    },
  }
}