grain.social is a photo sharing platform built on atproto.
1import { cn } from "@bigmoves/bff/components";
2import type { FunctionalComponent, JSX } from "preact";
3import { Button, ButtonProps } from "./Button.tsx";
4
5type DialogProps = JSX.HTMLAttributes<HTMLDivElement> & { _?: string } & {
6 children: preact.ComponentChildren;
7};
8
9type DialogContentProps = JSX.HTMLAttributes<HTMLDivElement> & {
10 children: preact.ComponentChildren;
11};
12
13type DialogTitleProps = {
14 children: preact.ComponentChildren;
15};
16
17type DialogCloseProps = JSX.HTMLAttributes<HTMLButtonElement> & ButtonProps & {
18 children: preact.ComponentChildren;
19};
20
21type DialogXProps = JSX.HTMLAttributes<HTMLButtonElement>;
22
23const _closeOnClick = "on click trigger closeDialog";
24
25const Dialog: FunctionalComponent<DialogProps> & {
26 Content: FunctionalComponent<DialogContentProps>;
27 Title: FunctionalComponent<DialogTitleProps>;
28 Close: FunctionalComponent<DialogCloseProps>;
29 X: FunctionalComponent<DialogXProps>;
30 _closeOnClick: string;
31} = ({ children, class: classProp, _ = "", ...props }) => {
32 return (
33 <div
34 class={cn(
35 "fixed top-0 bottom-0 right-0 left-0 flex items-center justify-center z-100",
36 classProp,
37 )}
38 {...{
39 _: `on closeDialog
40 remove me
41 remove .pointer-events-none from document.body
42 remove [@data-scroll-locked] from document.body
43 on keyup[key is 'Escape'] from <body/> trigger closeDialog
44 init
45 add .pointer-events-none to document.body
46 add .pointer-events-auto to me
47 add [@data-scroll-locked=true] to document.body
48 ${_}`,
49 }}
50 {...props}
51 >
52 <div
53 class="absolute top-0 left-0 right-0 bottom-0 bg-black/80"
54 {...{
55 _: _closeOnClick,
56 }}
57 />
58 {children}
59 </div>
60 );
61};
62
63const DialogContent: FunctionalComponent<DialogContentProps> = (
64 { children, class: classProp, ...props },
65) => {
66 return (
67 <div
68 class={cn(
69 "w-[400px] bg-white dark:bg-zinc-900 rounded-md flex flex-col p-4 max-h-[calc(100vh-100px)] overflow-y-auto z-20 relative",
70 classProp,
71 )}
72 {...props}
73 >
74 {children}
75 </div>
76 );
77};
78
79const DialogTitle: FunctionalComponent<DialogTitleProps> = ({ children }) => {
80 return (
81 <h1 class="text-lg font-semibold text-center w-full mb-2">
82 {children}
83 </h1>
84 );
85};
86
87const DialogX: FunctionalComponent<DialogXProps> = ({
88 class: classProp,
89}) => {
90 return (
91 <button
92 type="button"
93 class={cn(
94 "absolute top-4 right-4 h-4 w-4 cursor-pointer z-30 fill-white",
95 classProp,
96 )}
97 {...{
98 _: _closeOnClick,
99 }}
100 >
101 <svg
102 xmlns="http://www.w3.org/2000/svg"
103 viewBox="0 0 384 512"
104 >
105 {/* <!--!Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc.-->*/}
106 <path d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z" />
107 </svg>
108 </button>
109 );
110};
111
112const DialogClose: FunctionalComponent<DialogCloseProps> = (
113 { children, ...props },
114) => {
115 return (
116 <Button
117 {...{
118 _: _closeOnClick,
119 }}
120 {...props}
121 >
122 {children}
123 </Button>
124 );
125};
126
127Dialog.Content = DialogContent;
128Dialog.Title = DialogTitle;
129Dialog.Close = DialogClose;
130Dialog.X = DialogX;
131Dialog._closeOnClick = _closeOnClick;
132
133export { Dialog };