;
13 |
14 | /**
15 | * Returns a element wrapped in a tooltip. The tooltip will only be visible when the
element doesn't have enough
16 | * space (e.g. it has an ellipsis).
17 | */
18 | export function OverflowText(props: OverflowTextProps) {
19 | const {center, className, placement, style, title, ...rest} = props;
20 | const [isOpen, setIsOpen] = useState(false);
21 | const {ref} = useResizeObserver();
22 |
23 | const resizeCallback = useCallback((el: HTMLParagraphElement) => {
24 | if (!el) {
25 | return;
26 | }
27 | // scrollWidth doesn't include margin, so add margin to the child to see if it overflows the
28 | // parent.
29 | const {marginLeft, marginRight} = window.getComputedStyle(el);
30 | const outerChildWidth = el.scrollWidth + parseInt(marginLeft, 10) + parseInt(marginRight, 10);
31 | setIsOpen(!!el.parentElement && outerChildWidth >= el.parentElement.clientWidth);
32 | }, [setIsOpen]);
33 |
34 | // We wrap the Typography in a div to ensure that no other child interferes with the width of the
35 | // parent container.
36 | // TODO(acorn1010): Replace center prop with a better solution for centering text.
37 | const centerStyle: CSSProperties = center ? {display: 'flex', justifyContent: 'center'} : {};
38 | return (
39 |
40 |
41 |
{
45 | ref(e);
46 | resizeCallback(e);
47 | }}>
48 | {title}
49 |
50 |
51 |
52 | );
53 | }
54 |
--------------------------------------------------------------------------------
/src/client/theme/SiteTheme.tsx:
--------------------------------------------------------------------------------
1 | import {GlobalStyles, ThemeProvider} from "@mui/system";
2 | import {createTheme, CssBaseline} from "@mui/material";
3 | import type { PropsWithChildren } from "react";
4 | import {Work_Sans} from "@next/font/google";
5 |
6 | // Initialize font. This must be assigned to a variable in order to build.
7 | // noinspection JSUnusedLocalSymbols
8 | const font = Work_Sans({
9 | subsets: ['latin'],
10 | display: 'swap',
11 | variable: '--worksans-font',
12 | });
13 |
14 | const darkTheme = createTheme({
15 | palette: {
16 | mode: "dark",
17 | },
18 | typography: {
19 | fontFamily: 'var(--worksans-font)',
20 | },
21 | transitions: {
22 | create: () => 'none',
23 | },
24 | components: {
25 | MuiTooltip: {
26 | styleOverrides: {
27 | popper: {
28 | pointerEvents: 'none',
29 | },
30 | tooltip: {
31 | backgroundColor: 'rgba(39, 39, 42, .90)',
32 | fontSize: '.9rem',
33 | },
34 | },
35 | },
36 | },
37 | });
38 |
39 | export function SiteTheme(props: PropsWithChildren<{}>) {
40 | return (
41 |
42 |
43 |
49 | {props.children}
50 |
51 | );
52 | }
53 |
--------------------------------------------------------------------------------
/src/env/client.mjs:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | import { clientEnv, clientSchema } from "./schema.mjs";
3 |
4 | const _clientEnv = clientSchema.safeParse(clientEnv);
5 |
6 | export const formatErrors = (
7 | /** @type {import('zod').ZodFormattedError