summaryrefslogtreecommitdiffhomepage
path: root/cloud/web/src/ui/context-dialog.tsx
blob: f1bc932504f888ef9ebe6582399ae68fd25e9aae (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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import { createContext, JSX, ParentProps, useContext } from "solid-js"
import { StandardSchemaV1 } from "@standard-schema/spec"
import { createStore } from "solid-js/store"
import { Dialog } from "./dialog"

const Context = createContext<DialogControl>()

type DialogControl = {
  open<Schema extends StandardSchemaV1<object>>(
    component: DialogComponent<Schema>,
    input: StandardSchemaV1.InferInput<Schema>,
  ): void
  close(): void
  isOpen(input: any): boolean
  size: "sm" | "md"
  transition?: boolean
  input?: any
}

type DialogProps<Schema extends StandardSchemaV1<object>> = {
  input: StandardSchemaV1.InferInput<Schema>
  control: DialogControl
}

type DialogComponent<Schema extends StandardSchemaV1<object>> = ReturnType<
  typeof createDialog<Schema>
>

export function createDialog<Schema extends StandardSchemaV1<object>>(props: {
  schema: Schema
  size: "sm" | "md"
  render: (props: DialogProps<Schema>) => JSX.Element
}) {
  const result = () => {
    const dialog = useDialog()
    return (
      <Dialog
        size={dialog.size}
        transition={dialog.transition}
        open={dialog.isOpen(result)}
        onOpenChange={(val) => {
          if (!val) dialog.close()
        }}
      >
        {props.render({
          input: dialog.input,
          control: dialog,
        })}
      </Dialog>
    )
  }
  result.schema = props.schema
  result.size = props.size
  return result
}

export function DialogProvider(props: ParentProps) {
  const [store, setStore] = createStore<{
    dialog?: DialogComponent<any>
    input?: any
    transition?: boolean
    size: "sm" | "md"
  }>({
    size: "sm",
  })

  const control: DialogControl = {
    get input() {
      return store.input
    },
    get size() {
      return store.size
    },
    get transition() {
      return store.transition
    },
    isOpen(input) {
      return store.dialog === input
    },
    open(component, input) {
      setStore({
        dialog: component,
        input: input,
        size: store.dialog !== undefined ? store.size : component.size,
        transition: store.dialog !== undefined,
      })

      setTimeout(() => {
        setStore({
          size: component.size,
        })
      }, 0)

      setTimeout(() => {
        setStore({
          transition: false,
        })
      }, 150)
    },
    close() {
      setStore({
        dialog: undefined,
      })
    },
  }

  return (
    <>
      <Context.Provider value={control}>{props.children}</Context.Provider>
    </>
  )
}

export function useDialog() {
  const ctx = useContext(Context)
  if (!ctx) {
    throw new Error("useDialog must be used within a DialogProvider")
  }
  return ctx
}