Add Dialog element

This commit is contained in:
Kalle 2022-05-26 11:33:02 +03:00
parent 6a23c4f2bb
commit 7af4912453
5 changed files with 146 additions and 38 deletions

61
app/components/Dialog.tsx Normal file
View File

@ -0,0 +1,61 @@
import React from "react";
export function Dialog({
children,
isOpen,
close,
}: {
children: React.ReactNode;
isOpen: boolean;
close: () => void;
}) {
const ref = useDOMSync(isOpen);
// https://stackoverflow.com/a/26984690
const closeOnOutsideClick = (
event: React.MouseEvent<HTMLDialogElement, MouseEvent>
) => {
const rect: DOMRect = ref.current.getBoundingClientRect();
const isInDialog =
rect.top <= event.clientY &&
event.clientY <= rect.top + rect.height &&
rect.left <= event.clientX &&
event.clientX <= rect.left + rect.width;
if (!isInDialog) {
close();
}
};
return (
<dialog ref={ref} onClick={closeOnOutsideClick}>
<button onClick={close}>x</button>
{children}
</dialog>
);
}
function useDOMSync(isOpen: boolean) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ref = React.useRef<any>(null);
React.useEffect(() => {
if (ref.current.open && isOpen) return;
if (!ref.current.open && !isOpen) return;
const html = document.getElementsByTagName("html")[0];
if (isOpen) {
ref.current.showModal();
html.classList.add("lock-scroll");
} else {
ref.current.close();
html.classList.remove("lock-scroll");
}
return () => {
html.classList.remove("lock-scroll");
};
}, [isOpen]);
return ref;
}

View File

@ -0,0 +1,11 @@
import { useNavigate } from "@remix-run/react";
import { Dialog } from "~/components/Dialog";
export default function PlusCommentModalPage() {
const navigate = useNavigate();
return (
<Dialog isOpen close={() => navigate("/plus")}>
hello world
</Dialog>
);
}

View File

@ -4,21 +4,21 @@ import type {
MetaFunction,
} from "@remix-run/node";
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { Outlet, useLoaderData } from "@remix-run/react";
import * as React from "react";
import invariant from "tiny-invariant";
import { Avatar } from "~/components/Avatar";
import { Button } from "~/components/Button";
import { Catcher } from "~/components/Catcher";
import { upcomingVoting } from "~/core/plus";
import { db } from "~/db";
import type * as plusSuggestions from "~/db/models/plusSuggestions.server";
import { useUser } from "~/hooks/useUser";
import { makeTitle, requireUser } from "~/utils/remix";
import styles from "~/styles/plus.css";
import { Catcher } from "~/components/Catcher";
import * as React from "react";
import invariant from "tiny-invariant";
import type { Unpacked } from "~/utils/types";
import { Avatar } from "~/components/Avatar";
import { Button } from "~/components/Button";
import { discordFullName } from "~/utils/strings";
import { databaseTimestampToDate } from "~/utils/dates";
import { makeTitle, requireUser } from "~/utils/remix";
import { discordFullName } from "~/utils/strings";
import type { Unpacked } from "~/utils/types";
export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: styles }];
@ -67,36 +67,39 @@ export default function PlusPage() {
invariant(visibleSuggestions);
return (
<div className="plus__container">
<SuggestedForInfo />
<div className="stack md">
<div className="plus__radios">
{data.suggestions.map(({ tier, users }) => {
const id = String(tier);
return (
<div key={id} className="plus__radio-container">
<label htmlFor={id} className="plus__radio-label">
+{tier}{" "}
<span className="plus__users-count">({users.length})</span>
</label>
<input
id={id}
name="tier"
type="radio"
checked={tierVisible === tier}
onChange={() => setTierVisible(tier)}
/>
</div>
);
})}
</div>
<div className="stack lg">
{visibleSuggestions.users.map((u) => (
<SuggestedUser key={`${u.info.id}-${tierVisible}`} user={u} />
))}
<>
<Outlet />
<div className="plus__container">
<SuggestedForInfo />
<div className="stack md">
<div className="plus__radios">
{data.suggestions.map(({ tier, users }) => {
const id = String(tier);
return (
<div key={id} className="plus__radio-container">
<label htmlFor={id} className="plus__radio-label">
+{tier}{" "}
<span className="plus__users-count">({users.length})</span>
</label>
<input
id={id}
name="tier"
type="radio"
checked={tierVisible === tier}
onChange={() => setTierVisible(tier)}
/>
</div>
);
})}
</div>
<div className="stack lg">
{visibleSuggestions.users.map((u) => (
<SuggestedUser key={`${u.info.id}-${tierVisible}`} user={u} />
))}
</div>
</div>
</div>
</div>
</>
);
}

View File

@ -83,6 +83,10 @@
gap: var(--s-8);
}
.lock-scroll {
overflow: hidden;
}
/*
*
* Utility classes

View File

@ -7,7 +7,6 @@
--bg-lighter-transparent: hsla(237.3deg 42.3% 35.6% / 50%);
--bg-darker-very-transparent: hsla(237.3deg 42.3% 26.6% / 50%);
--bg-darker-transparent: hsla(237.3deg 42.3% 26.6% / 90%);
--bg-modal-backdrop: hsla(237deg 98% 1% / 70%);
--border: hsl(237.3deg 42.3% 45.6%);
--button-text: rgb(0 0 0 / 85%);
--button-text-transparent: rgb(0 0 0 / 65%);
@ -318,3 +317,33 @@ select::selection {
hr {
border-color: var(--theme-transparent);
}
dialog {
border: 0;
margin: auto;
background-color: var(--bg);
border-radius: var(--rounded);
}
dialog::backdrop {
background: hsla(237deg 98% 1% / 70%);
}
@supports ((-webkit-backdrop-filter: none) or (backdrop-filter: none)) {
dialog::backdrop {
-webkit-backdrop-filter: blur(7px) brightness(70%);
backdrop-filter: blur(7px) brightness(70%);
background-color: transparent;
}
}
dialog[open],
dialog::backdrop {
animation: show 500ms ease;
}
@keyframes show {
0% {
opacity: 0;
}
}