toast.tsx 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import { createContext, useContext, type ParentProps, Show } from "solid-js"
  2. import { createStore } from "solid-js/store"
  3. import { useTheme } from "@tui/context/theme"
  4. import { SplitBorder } from "../component/border"
  5. import { TextAttributes } from "@opentui/core"
  6. import z from "zod"
  7. import { TuiEvent } from "../event"
  8. export type ToastOptions = z.infer<typeof TuiEvent.ToastShow.properties>
  9. export function Toast() {
  10. const toast = useToast()
  11. const { theme } = useTheme()
  12. return (
  13. <Show when={toast.currentToast}>
  14. {(current) => (
  15. <box
  16. position="absolute"
  17. justifyContent="center"
  18. alignItems="flex-start"
  19. top={2}
  20. right={2}
  21. paddingLeft={2}
  22. paddingRight={2}
  23. paddingTop={1}
  24. paddingBottom={1}
  25. backgroundColor={theme.backgroundPanel}
  26. borderColor={theme[current().variant]}
  27. border={["left", "right"]}
  28. customBorderChars={SplitBorder.customBorderChars}
  29. >
  30. <Show when={current().title}>
  31. <text attributes={TextAttributes.BOLD} marginBottom={1}>
  32. {current().title}
  33. </text>
  34. </Show>
  35. <text>{current().message}</text>
  36. </box>
  37. )}
  38. </Show>
  39. )
  40. }
  41. function init() {
  42. const [store, setStore] = createStore({
  43. currentToast: null as ToastOptions | null,
  44. })
  45. let timeoutHandle: NodeJS.Timeout | null = null
  46. const toast = {
  47. show(options: ToastOptions) {
  48. const parsedOptions = TuiEvent.ToastShow.properties.parse(options)
  49. const { duration, ...currentToast } = parsedOptions
  50. setStore("currentToast", currentToast)
  51. if (timeoutHandle) clearTimeout(timeoutHandle)
  52. timeoutHandle = setTimeout(() => {
  53. setStore("currentToast", null)
  54. }, duration).unref()
  55. },
  56. error: (err: any) => {
  57. if (err instanceof Error)
  58. return toast.show({
  59. variant: "error",
  60. message: err.message,
  61. })
  62. toast.show({
  63. variant: "error",
  64. message: "An unknown error has occurred",
  65. })
  66. },
  67. get currentToast(): ToastOptions | null {
  68. return store.currentToast
  69. },
  70. }
  71. return toast
  72. }
  73. export type ToastContext = ReturnType<typeof init>
  74. const ctx = createContext<ToastContext>()
  75. export function ToastProvider(props: ParentProps) {
  76. const value = init()
  77. return <ctx.Provider value={value}>{props.children}</ctx.Provider>
  78. }
  79. export function useToast() {
  80. const value = useContext(ctx)
  81. if (!value) {
  82. throw new Error("useToast must be used within a ToastProvider")
  83. }
  84. return value
  85. }