Use of cJoin and cIf

This commit is contained in:
DrMint 2022-06-15 07:33:20 +02:00
parent 1510366bc8
commit c076ec06ad
44 changed files with 515 additions and 266 deletions

View File

@ -2,6 +2,7 @@ import { Button } from "components/Inputs/Button";
import { useAppLayout } from "contexts/AppLayoutContext";
import { UploadImageFragment } from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { cIf, cJoin } from "helpers/className";
import { prettyLanguage, prettySlug } from "helpers/formatters";
import { getOgImage, ImageQuality, OgImage } from "helpers/img";
// import { getClient, Indexes, search, SearchResult } from "helpers/search";
@ -181,19 +182,23 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
return (
<div
className={`${
appLayout.darkMode ? "set-theme-dark" : "set-theme-light"
} ${
appLayout.dyslexic
? "set-theme-font-dyslexic"
: "set-theme-font-standard"
}`}
className={cJoin(
cIf(appLayout.darkMode, "set-theme-dark", "set-theme-light"),
cIf(
appLayout.dyslexic,
"set-theme-font-dyslexic",
"set-theme-font-standard"
)
)}
>
<div
{...handlers}
className={`fixed inset-0 m-0 grid touch-pan-y bg-light p-0 text-black
[grid-template-areas:'main_sub_content'] ${gridCol} mobile:grid-cols-[1fr]
mobile:grid-rows-[1fr_5rem] mobile:[grid-template-areas:'content''navbar']`}
className={cJoin(
`fixed inset-0 m-0 grid touch-pan-y bg-light p-0 text-black
[grid-template-areas:'main_sub_content'] mobile:grid-cols-[1fr]
mobile:grid-rows-[1fr_5rem] mobile:[grid-template-areas:'content''navbar']`,
gridCol
)}
>
<Head>
<title>{metaTitle}</title>
@ -222,21 +227,25 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
{/* Background when navbar is opened */}
<div
className={`absolute inset-0 transition-[backdrop-filter]
duration-500 [grid-area:content] mobile:z-10 ${
(appLayout.mainPanelOpen || appLayout.subPanelOpen) && isMobile
? "[backdrop-filter:blur(2px)]"
: "pointer-events-none touch-none "
}`}
className={cJoin(
`absolute inset-0 transition-[backdrop-filter] duration-500 [grid-area:content]
mobile:z-10`,
cIf(
(appLayout.mainPanelOpen || appLayout.subPanelOpen) && isMobile,
"[backdrop-filter:blur(2px)]",
"pointer-events-none touch-none"
)
)}
>
<div
className={`absolute inset-0 bg-shade transition-opacity duration-500
${turnSubIntoContent ? "" : ""}
${
(appLayout.mainPanelOpen || appLayout.subPanelOpen) && isMobile
? "opacity-60"
: "opacity-0"
}`}
className={cJoin(
"absolute inset-0 bg-shade transition-opacity duration-500",
cIf(
(appLayout.mainPanelOpen || appLayout.subPanelOpen) && isMobile,
"opacity-60",
"opacity-0"
)
)}
onClick={() => {
appLayout.setMainPanelOpen(false);
appLayout.setSubPanelOpen(false);
@ -255,7 +264,7 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
<div className="grid h-full place-content-center">
<div
className="grid grid-flow-col place-items-center gap-9 rounded-2xl
border-2 border-dotted border-dark p-8 text-dark opacity-40"
border-2 border-dotted border-dark p-8 text-dark opacity-40"
>
<p className="text-4xl"></p>
<p className="w-64 text-2xl">{langui.select_option_sidebar}</p>
@ -267,16 +276,18 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
{/* Sub panel */}
{subPanel && (
<div
className={`texture-paper-dots overflow-y-scroll border-r-[1px] border-dotted
border-black bg-light transition-transform duration-300 [grid-area:sub]
[scrollbar-width:none] webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%]
mobile:justify-self-end mobile:border-r-0 mobile:border-l-[1px]
mobile:[grid-area:content]
${
turnSubIntoContent
? "mobile:w-full mobile:border-l-0"
: !appLayout.subPanelOpen && "mobile:translate-x-[100vw]"
}`}
className={cJoin(
`texture-paper-dots overflow-y-scroll border-r-[1px] border-dotted
border-black bg-light transition-transform duration-300 [grid-area:sub]
[scrollbar-width:none] webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%]
mobile:justify-self-end mobile:border-r-0 mobile:border-l-[1px]
mobile:[grid-area:content]`,
turnSubIntoContent
? "mobile:w-full mobile:border-l-0"
: appLayout.subPanelOpen
? ""
: "mobile:translate-x-[100vw]"
)}
>
{subPanel}
</div>
@ -284,12 +295,13 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
{/* Main panel */}
<div
className={`texture-paper-dots overflow-y-scroll border-r-[1px] border-dotted
border-black bg-light transition-transform duration-300 [grid-area:main]
[scrollbar-width:none] webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%]
mobile:justify-self-start mobile:[grid-area:content] ${
appLayout.mainPanelOpen ? "" : "mobile:-translate-x-full"
}`}
className={cJoin(
`texture-paper-dots overflow-y-scroll border-r-[1px] border-dotted
border-black bg-light transition-transform duration-300 [grid-area:main]
[scrollbar-width:none] webkit-scrollbar:w-0 mobile:z-10 mobile:w-[90%]
mobile:justify-self-start mobile:[grid-area:content]`,
cIf(!appLayout.mainPanelOpen, "mobile:-translate-x-full")
)}
>
<MainPanel langui={langui} />
</div>
@ -301,25 +313,28 @@ export function AppLayout(props: Immutable<Props>): JSX.Element {
>
<Ico
icon={appLayout.mainPanelOpen ? Icon.Close : Icon.Menu}
className="mt-[.1em] cursor-pointer"
className="mt-[.1em] cursor-pointer !text-2xl"
onClick={() => {
appLayout.setMainPanelOpen(!appLayout.mainPanelOpen);
appLayout.setSubPanelOpen(false);
}}
/>
<p
className={`overflow-hidden text-center font-headers font-black ${
ogTitle && ogTitle.length > 30
? "max-h-14 text-xl"
: "max-h-16 text-2xl"
}`}
className={cJoin(
"overflow-hidden text-center font-headers font-black",
cIf(
ogTitle && ogTitle.length > 30,
"max-h-14 text-xl",
"max-h-16 text-2xl"
)
)}
>
{ogTitle}
</p>
{subPanel && !turnSubIntoContent && (
<Ico
icon={appLayout.subPanelOpen ? Icon.Close : subPanelIcon}
className="mt-[.1em] cursor-pointer"
className="mt-[.1em] cursor-pointer !text-2xl"
onClick={() => {
appLayout.setSubPanelOpen(!appLayout.subPanelOpen);
appLayout.setMainPanelOpen(false);

View File

@ -1,3 +1,4 @@
import { cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
interface Props {
@ -8,9 +9,12 @@ interface Props {
export function Chip(props: Immutable<Props>): JSX.Element {
return (
<div
className={`grid place-content-center place-items-center whitespace-nowrap rounded-full
border-[1px] px-1.5 pb-[0.14rem] text-xs opacity-70
transition-[color,_opacity,_border-color] hover:opacity-100 ${props.className}`}
className={cJoin(
`grid place-content-center place-items-center whitespace-nowrap rounded-full
border-[1px] px-1.5 pb-[0.14rem] text-xs opacity-70
transition-[color,_opacity,_border-color] hover:opacity-100`,
props.className
)}
>
{props.children}
</div>

View File

@ -1,3 +1,4 @@
import { cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
interface Props {
@ -5,9 +6,13 @@ interface Props {
}
export function HorizontalLine(props: Immutable<Props>): JSX.Element {
const { className } = props;
return (
<div
className={`my-8 h-0 w-full border-t-[3px] border-dotted border-black ${props.className}`}
className={cJoin(
"my-8 h-0 w-full border-t-[3px] border-dotted border-black",
className
)}
></div>
);
}

View File

@ -1,3 +1,4 @@
import { cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
import { MouseEventHandler } from "react";
@ -10,7 +11,13 @@ interface Props {
export function Ico(props: Immutable<Props>): JSX.Element {
const { onClick, icon, className } = props;
return (
<span onClick={onClick} className={`material-icons ${className}`}>
<span
onClick={onClick}
className={cJoin(
"material-icons [font-size:inherit] [line-height:inherit]",
className
)}
>
{icon}
</span>
);

View File

@ -1,4 +1,5 @@
import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
import { useRouter } from "next/router";
import { MouseEventHandler } from "react";
@ -38,14 +39,18 @@ export function Button(props: Immutable<Props>): JSX.Element {
draggable={draggable}
id={id}
onClick={onClick}
className={`component-button group grid select-none grid-flow-col place-content-center
place-items-center gap-2 rounded-full border-[1px] border-dark px-4 pt-[0.4rem] pb-[0.5rem]
text-dark transition-all ${
active
? "!border-black bg-black text-light drop-shadow-black-lg"
: `cursor-pointer hover:bg-dark hover:text-light hover:drop-shadow-shade-lg
className={cJoin(
`component-button group grid select-none grid-flow-col place-content-center
place-items-center gap-2 rounded-full border-[1px] border-dark py-3 px-4 leading-none
text-dark transition-all`,
cIf(
active,
"!border-black bg-black text-light drop-shadow-black-lg",
`cursor-pointer hover:bg-dark hover:text-light hover:drop-shadow-shade-lg
active:border-black active:bg-black active:text-light active:drop-shadow-black-lg`
} ${className}`}
),
className
)}
>
{badgeNumber && (
<div
@ -55,7 +60,9 @@ export function Button(props: Immutable<Props>): JSX.Element {
<p className="-translate-y-[0.05em]">{badgeNumber}</p>
</div>
)}
{icon && <Ico className="translate-y-[0.04em] !text-base" icon={icon} />}
{icon && (
<Ico className="[font-size:150%] [line-height:0.66]" icon={icon} />
)}
{text && <p className="-translate-y-[0.05em] text-center">{text}</p>}
</div>
);

View File

@ -1,3 +1,4 @@
import { cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
import { useEffect, useRef } from "react";
@ -31,7 +32,7 @@ export function ButtonGroup(props: Immutable<Props>): JSX.Element {
}, [children]);
return (
<div ref={ref} className={`grid grid-flow-col ${className}`}>
<div ref={ref} className={cJoin("grid grid-flow-col", className)}>
{children}
</div>
);

View File

@ -1,5 +1,6 @@
import { Icon } from "components/Ico";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { cJoin } from "helpers/className";
import { prettyLanguage } from "helpers/formatters";
import { Immutable } from "helpers/types";
import { Dispatch, Fragment, SetStateAction } from "react";
@ -20,7 +21,7 @@ export function LanguageSwitcher(props: Immutable<Props>): JSX.Element {
return (
<ToolTip
content={
<div className={`flex flex-col gap-2 ${className}`}>
<div className={cJoin("flex flex-col gap-2", className)}>
{[...locales].map(([locale, value], index) => (
<Fragment key={index}>
{locale && (

View File

@ -1,4 +1,5 @@
import { Icon } from "components/Ico";
import { cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
import { Dispatch, SetStateAction } from "react";
import { Button } from "./Button";
@ -11,10 +12,10 @@ interface Props {
}
export function PageSelector(props: Immutable<Props>): JSX.Element {
const { page, setPage, maxPage } = props;
const { page, setPage, maxPage, className } = props;
return (
<div className={`flex flex-row place-content-center ${props.className}`}>
<div className={cJoin("flex flex-row place-content-center", className)}>
<Button
onClick={() => {
if (page > 0) setPage(page - 1);

View File

@ -1,4 +1,5 @@
import { Ico, Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
import { Dispatch, Fragment, SetStateAction, useState } from "react";
@ -17,16 +18,19 @@ export function Select(props: Immutable<Props>): JSX.Element {
return (
<div
className={`relative text-center transition-[filter] ${
opened && "z-10 drop-shadow-shade-lg"
} ${className}`}
className={cJoin(
"relative text-center transition-[filter]",
cIf(opened, "z-10 drop-shadow-shade-lg"),
className
)}
>
<div
className={`grid cursor-pointer grid-flow-col grid-cols-[1fr_auto_auto] place-items-center
rounded-[1em] bg-light p-1 outline outline-2 outline-offset-[-2px] outline-mid
transition-all hover:bg-mid hover:outline-[transparent] ${
opened && "rounded-b-none bg-highlight outline-[transparent]"
}`}
className={cJoin(
`grid cursor-pointer grid-flow-col grid-cols-[1fr_auto_auto] place-items-center
rounded-[1em] bg-light p-1 outline outline-2 outline-offset-[-2px] outline-mid
transition-all hover:bg-mid hover:outline-[transparent]`,
cIf(opened, "rounded-b-none bg-highlight outline-[transparent]")
)}
>
<p onClick={() => setOpened(!opened)} className="w-full">
{state === -1 ? "—" : options[state]}
@ -44,18 +48,19 @@ export function Select(props: Immutable<Props>): JSX.Element {
/>
</div>
<div
className={`left-0 right-0 rounded-b-[1em] ${
opened ? "absolute" : "hidden"
}`}
className={cJoin(
"left-0 right-0 rounded-b-[1em]",
cIf(opened, "absolute", "hidden")
)}
>
{options.map((option, index) => (
<Fragment key={index}>
{index !== state && (
<div
className={` ${
opened ? "bg-highlight" : "bg-light"
} cursor-pointer p-1
transition-colors last-of-type:rounded-b-[1em] hover:bg-mid`}
className={cJoin(
"cursor-pointer p-1 transition-colors last-of-type:rounded-b-[1em] hover:bg-mid",
cIf(opened, "bg-highlight", "bg-light")
)}
id={option}
onClick={() => {
setOpened(false);

View File

@ -1,3 +1,4 @@
import { cIf, cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
import { Dispatch, SetStateAction } from "react";
@ -12,23 +13,29 @@ export function Switch(props: Immutable<Props>): JSX.Element {
const { state, setState, className, disabled } = props;
return (
<div
className={`relative grid h-6 w-12 rounded-full border-2
border-mid transition-colors ${
disabled ? "cursor-not-allowed" : "cursor-pointer"
} ${className} ${
state ? "border-none bg-mid shadow-inner-sm shadow-shade" : "bg-light"
}`}
className={cJoin(
"relative grid h-6 w-12 rounded-full border-2 border-mid transition-colors",
cIf(disabled, "cursor-not-allowed", "cursor-pointer"),
cIf(
state,
"border-none bg-mid shadow-inner-sm shadow-shade",
"bg-light"
),
className
)}
onClick={() => {
if (!disabled) setState(!state);
}}
>
<div
className={`absolute ${
state ? "top-[2px] bottom-[2px] left-[2px]" : "top-0 bottom-0 left-0"
}
aspect-square rounded-full bg-dark transition-transform ${
state && "translate-x-[120%]"
}`}
className={cJoin(
"absolute aspect-square rounded-full bg-dark transition-transform",
cIf(
state,
"top-[2px] bottom-[2px] left-[2px] translate-x-[120%]",
"top-0 bottom-0 left-0"
)
)}
></div>
</div>
);

View File

@ -1,4 +1,5 @@
import { Ico, Icon } from "components/Ico";
import { cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
import { Dispatch, SetStateAction } from "react";
@ -16,7 +17,7 @@ export function TextInput(props: Immutable<Props>): JSX.Element {
const { state, setState, className, name, placeholder } = props;
return (
<div className={`relative ${className}`}>
<div className={cJoin("relative", className)}>
<input
className="w-full"
type="text"

View File

@ -1,3 +1,4 @@
import { cIf, cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
interface Props {
@ -10,12 +11,18 @@ export function WithLabel(props: Immutable<Props>): JSX.Element {
const { label, input, disabled } = props;
return (
<div
className={`flex flex-row place-content-between place-items-center gap-2 ${
disabled ? "text-dark brightness-150 contrast-75 grayscale" : ""
}`}
className={cJoin(
"flex flex-row place-content-between place-items-center gap-2",
cIf(disabled, "text-dark brightness-150 contrast-75 grayscale")
)}
>
{label && (
<p className={`text-left ${label.length < 10 ? "flex-shrink-0" : ""}`}>
<p
className={cJoin(
"text-left",
cIf(label.length < 10, "flex-shrink-0")
)}
>
{label}:
</p>
)}

View File

@ -1,3 +1,4 @@
import { cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
interface Props {
@ -10,7 +11,10 @@ export function InsetBox(props: Immutable<Props>): JSX.Element {
return (
<div
id={props.id}
className={`w-full rounded-xl bg-mid p-8 shadow-inner-sm shadow-shade ${props.className}`}
className={cJoin(
"w-full rounded-xl bg-mid p-8 shadow-inner-sm shadow-shade",
props.className
)}
>
{props.children}
</div>

View File

@ -4,6 +4,7 @@ import { Img } from "components/Img";
import { InsetBox } from "components/InsetBox";
import { ToolTip } from "components/ToolTip";
import { useAppLayout } from "contexts/AppLayoutContext";
import { cJoin } from "helpers/className";
import { slugify } from "helpers/formatters";
import { getAssetURL, ImageQuality } from "helpers/img";
import { Immutable } from "helpers/types";
@ -32,7 +33,7 @@ export function Markdawn(props: Immutable<Props>): JSX.Element {
<>
<LightBox />
<Markdown
className={`formatted ${props.className}`}
className={cJoin("formatted", props.className)}
options={{
slugify: slugify,
overrides: {

View File

@ -19,7 +19,7 @@ export function TOC(props: Immutable<Props>): JSX.Element {
<h3 className="text-xl">Table of content</h3>
<div className="max-w-[14.5rem] text-left">
<p className="relative my-2 overflow-x-hidden text-ellipsis whitespace-nowrap text-left">
<a className="" onClick={async () => router.replace(`#${toc.slug}`)}>
<a onClick={async () => router.replace(`#${toc.slug}`)}>
{<abbr title={toc.title}>{toc.title}</abbr>}
</a>
</p>

View File

@ -1,5 +1,6 @@
import { Ico, Icon } from "components/Ico";
import { ToolTip } from "components/ToolTip";
import { cJoin, cIf } from "helpers/className";
import { Immutable } from "helpers/types";
import { useRouter } from "next/router";
import { MouseEventHandler } from "react";
@ -17,16 +18,6 @@ interface Props {
export function NavOption(props: Immutable<Props>): JSX.Element {
const router = useRouter();
const isActive = router.asPath.startsWith(props.url);
const divActive = "bg-mid shadow-inner-sm shadow-shade";
const border =
"outline outline-mid outline-2 outline-offset-[-2px] hover:outline-[transparent]";
const divCommon = `gap-x-5 w-full rounded-2xl cursor-pointer p-4 hover:bg-mid
hover:shadow-inner-sm hover:shadow-shade hover:active:shadow-inner
hover:active:shadow-shade transition-all ${props.border ? border : ""} ${
isActive ? divActive : ""
}`;
return (
<ToolTip
@ -51,11 +42,21 @@ export function NavOption(props: Immutable<Props>): JSX.Element {
}
}
}}
className={`relative grid auto-cols-fr grid-flow-col grid-cols-[auto] justify-center ${
props.icon ? "text-left" : "text-center"
} ${divCommon}`}
className={cJoin(
`relative grid w-full cursor-pointer auto-cols-fr grid-flow-col grid-cols-[auto]
justify-center gap-x-5 rounded-2xl p-4 transition-all hover:bg-mid hover:shadow-inner-sm
hover:shadow-shade hover:active:shadow-inner hover:active:shadow-shade`,
cIf(props.icon, "text-left", "text-center"),
cIf(
props.border,
"outline outline-2 outline-offset-[-2px] outline-mid hover:outline-[transparent]"
),
cIf(isActive, "bg-mid shadow-inner-sm shadow-shade")
)}
>
{props.icon && <Ico icon={props.icon} className="mt-[.1em]" />}
{props.icon && (
<Ico icon={props.icon} className="mt-[-.1em] !text-2xl" />
)}
{!props.reduced && (
<div>

View File

@ -3,6 +3,7 @@ import { Icon } from "components/Ico";
import { Button } from "components/Inputs/Button";
import { useAppLayout } from "contexts/AppLayoutContext";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
interface Props {
@ -25,13 +26,14 @@ export function ReturnButton(props: Immutable<Props>): JSX.Element {
return (
<div
className={`${
className={cJoin(
props.displayOn === ReturnButtonType.Mobile
? "desktop:hidden"
: props.displayOn === ReturnButtonType.Desktop
? "mobile:hidden"
: ""
} ${props.className}`}
: "",
props.className
)}
>
<Button
onClick={() => appLayout.setSubPanelOpen(false)}

View File

@ -1,29 +1,33 @@
import { cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
interface Props {
children: React.ReactNode;
autoformat?: boolean;
width?: ContentPanelWidthSizes;
}
export enum ContentPanelWidthSizes {
Default = "default",
Large = "large",
Full = "full",
}
export function ContentPanel(props: Immutable<Props>): JSX.Element {
const width = props.width ? props.width : ContentPanelWidthSizes.Default;
const widthCSS =
width === ContentPanelWidthSizes.Default ? "max-w-2xl" : "w-full";
const { width = ContentPanelWidthSizes.Default, children } = props;
return (
<div className={`grid px-4 pt-10 pb-20 desktop:py-20 desktop:px-10`}>
<main
className={`${
props.autoformat && "formatted"
} ${widthCSS} place-self-center`}
className={cJoin(
"place-self-center",
width === ContentPanelWidthSizes.Default
? "max-w-2xl"
: width === ContentPanelWidthSizes.Large
? "max-w-4xl"
: "w-full"
)}
>
{props.children}
{children}
</main>
</div>
);

View File

@ -9,6 +9,7 @@ import { useMediaDesktop } from "hooks/useMediaQuery";
import Markdown from "markdown-to-jsx";
import Link from "next/link";
import { Icon } from "components/Ico";
import { cIf, cJoin } from "helpers/className";
interface Props {
langui: AppStaticProps["langui"];
@ -21,15 +22,17 @@ export function MainPanel(props: Immutable<Props>): JSX.Element {
return (
<div
className={`grid content-start justify-center gap-y-2 p-8 text-center ${
appLayout.mainPanelReduced && isDesktop && "px-4"
}`}
className={cJoin(
"grid content-start justify-center gap-y-2 p-8 text-center",
cIf(appLayout.mainPanelReduced && isDesktop, "px-4")
)}
>
{/* Reduce/expand main menu */}
<div
className={`fixed top-1/2 mobile:hidden ${
appLayout.mainPanelReduced ? "left-[4.65rem]" : "left-[18.65rem]"
}`}
className={cJoin(
"fixed top-1/2 mobile:hidden",
cIf(appLayout.mainPanelReduced, "left-[4.65rem]", "left-[18.65rem]")
)}
onClick={() =>
appLayout.setMainPanelReduced(!appLayout.mainPanelReduced)
}
@ -46,26 +49,28 @@ export function MainPanel(props: Immutable<Props>): JSX.Element {
<div className="grid place-items-center">
<Link href="/" passHref>
<div
className={`${
appLayout.mainPanelReduced && isDesktop ? "w-12" : "w-1/2"
} mb-4 aspect-square cursor-pointer bg-black
transition-colors [mask:url('/icons/accords.svg')]
![mask-size:contain] ![mask-repeat:no-repeat] ![mask-position:center] hover:bg-dark`}
className={cJoin(
`mb-4 aspect-square cursor-pointer bg-black transition-colors
[mask:url('/icons/accords.svg')] ![mask-size:contain] ![mask-repeat:no-repeat]
![mask-position:center] hover:bg-dark`,
cIf(appLayout.mainPanelReduced && isDesktop, "w-12", "w-1/2")
)}
></div>
</Link>
{appLayout.mainPanelReduced && isDesktop ? (
""
) : (
{(!appLayout.mainPanelReduced || !isDesktop) && (
<h2 className="text-3xl">Accord&rsquo;s Library</h2>
)}
<div
className={`flex ${
appLayout.mainPanelReduced && isDesktop
? "flex-col gap-3"
: "flex-row"
} flex-wrap gap-2`}
className={cJoin(
"flex flex-wrap gap-2",
cIf(
appLayout.mainPanelReduced && isDesktop,
"flex-col gap-3",
"flex-row"
)
)}
>
<ToolTip
content={<h3 className="text-2xl">{langui.open_settings}</h3>}
@ -176,9 +181,10 @@ export function MainPanel(props: Immutable<Props>): JSX.Element {
{appLayout.mainPanelReduced && isDesktop ? "" : <HorizontalLine />}
<div
className={`text-center ${
appLayout.mainPanelReduced && isDesktop ? "hidden" : ""
}`}
className={cJoin(
"text-center",
cIf(appLayout.mainPanelReduced && isDesktop, "hidden")
)}
>
<p>
{langui.licensing_notice && (

View File

@ -1,4 +1,5 @@
import { useAppLayout } from "contexts/AppLayoutContext";
import { cIf, cJoin } from "helpers/className";
import { Immutable } from "helpers/types";
import { Dispatch, SetStateAction, useEffect } from "react";
import Hotkeys from "react-hot-keys";
@ -40,32 +41,37 @@ export function Popup(props: Immutable<Props>): JSX.Element {
}}
>
<div
className={`fixed inset-0 z-50 grid place-content-center
transition-[backdrop-filter] duration-500 ${
state ? "[backdrop-filter:blur(2px)]" : "pointer-events-none touch-none"
}`}
className={cJoin(
"fixed inset-0 z-50 grid place-content-center transition-[backdrop-filter] duration-500",
cIf(
state,
"[backdrop-filter:blur(2px)]",
"pointer-events-none touch-none"
)
)}
>
<div
className={`fixed inset-0 bg-shade transition-all duration-500 ${
state ? "bg-opacity-50" : "bg-opacity-0"
}`}
className={cJoin(
"fixed inset-0 bg-shade transition-all duration-500",
cIf(state, "bg-opacity-50", "bg-opacity-0")
)}
onClick={() => {
setState(false);
}}
/>
<div
className={`${
padding && "p-10 mobile:p-6"
} grid place-items-center gap-4 transition-transform ${
state ? "scale-100" : "scale-0"
} ${
fillViewport
? "absolute inset-10"
: "relative max-h-[80vh] overflow-y-auto mobile:w-[85vw]"
} ${
hideBackground ? "" : "rounded-lg bg-light shadow-2xl shadow-shade"
}`}
className={cJoin(
"grid place-items-center gap-4 transition-transform",
cIf(padding, "p-10 mobile:p-6"),
cIf(state, "scale-100", "scale-0"),
cIf(
fillViewport,
"absolute inset-10",
"relative max-h-[80vh] overflow-y-auto mobile:w-[85vw]"
),
cIf(!hideBackground, "rounded-lg bg-light shadow-2xl shadow-shade")
)}
>
{children}
</div>

View File

@ -5,6 +5,7 @@ import {
UploadImageFragment,
} from "graphql/generated";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { cIf, cJoin } from "helpers/className";
import {
prettyDate,
prettyDuration,
@ -130,14 +131,15 @@ export function PreviewCard(props: Immutable<Props>): JSX.Element {
{stackNumber > 0 && (
<>
<div
className={`absolute inset-0 scale-[.85] overflow-hidden bg-light brightness-[0.8]
sepia-[0.5] transition-[top_transform] group-hover:-top-8 ${
thumbnailRounded && "rounded-md"
}`}
className={cJoin(
`absolute inset-0 scale-[.85] overflow-hidden bg-light brightness-[0.8] sepia-[0.5]
transition-[top_transform] group-hover:-top-8`,
cIf(thumbnailRounded, "rounded-md")
)}
>
{thumbnail && (
<Img
className="opacity-30 "
className="opacity-30"
image={thumbnail}
quality={ImageQuality.Medium}
/>
@ -145,10 +147,11 @@ export function PreviewCard(props: Immutable<Props>): JSX.Element {
</div>
<div
className={`absolute inset-0 overflow-hidden bg-light brightness-[0.9] sepia-[0.2]
transition-[top_transform] group-hover:-top-4 group-hover:scale-[.94] ${
thumbnailRounded && "rounded-md"
}`}
className={cJoin(
`absolute inset-0 overflow-hidden bg-light brightness-[0.9] sepia-[0.2]
transition-[top_transform] group-hover:-top-4 group-hover:scale-[.94]`,
cIf(thumbnailRounded, "rounded-md")
)}
>
{thumbnail && (
<Img
@ -171,19 +174,24 @@ export function PreviewCard(props: Immutable<Props>): JSX.Element {
}}
>
<Img
className={`${
thumbnailRounded &&
(keepInfoVisible
? "rounded-t-md"
: "rounded-md notHoverable:rounded-b-none")
} ${thumbnailForceAspectRatio && "h-full w-full object-cover"}`}
className={cJoin(
cIf(
thumbnailRounded,
cIf(
keepInfoVisible,
"rounded-t-md",
"rounded-md notHoverable:rounded-b-none"
)
),
cIf(thumbnailForceAspectRatio, "h-full w-full object-cover")
)}
image={thumbnail}
quality={ImageQuality.Medium}
/>
{stackNumber > 0 && (
<div
className="absolute right-2 top-2 rounded-full bg-black
bg-opacity-60 px-2 text-light"
bg-opacity-60 px-2 text-light"
>
{stackNumber}
</div>
@ -200,8 +208,8 @@ export function PreviewCard(props: Immutable<Props>): JSX.Element {
/>
</div>
<div
className="absolute right-2 bottom-2 rounded-full bg-black
bg-opacity-60 px-2 text-light"
className="absolute right-2 bottom-2 rounded-full bg-black bg-opacity-60 px-2
text-light"
>
{prettyDuration(hoverlay.duration)}
</div>
@ -211,11 +219,14 @@ export function PreviewCard(props: Immutable<Props>): JSX.Element {
) : (
<div
style={{ aspectRatio: thumbnailAspectRatio }}
className={`relative w-full bg-light ${
keepInfoVisible
? "rounded-t-md"
: "rounded-md notHoverable:rounded-b-none"
}`}
className={cJoin(
"relative w-full bg-light",
cIf(
keepInfoVisible,
"rounded-t-md",
"rounded-md notHoverable:rounded-b-none"
)
)}
>
{stackNumber > 0 && (
<div
@ -228,11 +239,14 @@ export function PreviewCard(props: Immutable<Props>): JSX.Element {
</div>
)}
<div
className={`z-20 grid gap-2 p-4 transition-opacity linearbg-obi ${
!keepInfoVisible &&
`-inset-x-0.5 bottom-2 opacity-0 group-hover:opacity-100 hoverable:absolute
hoverable:drop-shadow-shade-lg notHoverable:rounded-b-md`
}`}
className={cJoin(
"z-20 grid gap-2 p-4 transition-opacity linearbg-obi",
cIf(
!keepInfoVisible,
`-inset-x-0.5 bottom-2 opacity-0 group-hover:opacity-100 hoverable:absolute
hoverable:drop-shadow-shade-lg notHoverable:rounded-b-md`
)
)}
>
{metadata?.position === "Top" && metadataJSX}
{topChips && topChips.length > 0 && (

View File

@ -34,9 +34,8 @@ export function PreviewLine(props: Immutable<Props>): JSX.Element {
return (
<Link href={href} passHref>
<div
className="flex h-36 w-full cursor-pointer
flex-row place-items-center gap-4 overflow-hidden rounded-md
bg-light pr-4 transition-transform drop-shadow-shade-xl hover:scale-[1.02]"
className="flex h-36 w-full cursor-pointer flex-row place-items-center gap-4 overflow-hidden
rounded-md bg-light pr-4 transition-transform drop-shadow-shade-xl hover:scale-[1.02]"
>
{thumbnail ? (
<div className="aspect-[3/2] h-full">

View File

@ -1,22 +0,0 @@
import { Immutable } from "helpers/types";
import Image from "next/image";
interface Props {
src: string;
alt: string;
className?: string;
}
export function SVG(props: Immutable<Props>): JSX.Element {
return (
<div className={props.className}>
<Image
src={props.src}
alt={props.src}
height={1000}
width={1000}
unoptimized
/>
</div>
);
}

View File

@ -1,10 +1,12 @@
import Tippy, { TippyProps } from "@tippyjs/react";
import { cJoin } from "helpers/className";
import "tippy.js/animations/scale-subtle.css";
interface Props extends TippyProps {}
export function ToolTip(props: Props): JSX.Element {
const newProps: Props = {
className: cJoin("text-[80%]", props.className),
delay: [150, 0],
interactive: true,
animation: "scale-subtle",
@ -12,7 +14,7 @@ export function ToolTip(props: Props): JSX.Element {
};
return (
<Tippy className={`text-[80%] ${newProps.className}`} {...newProps}>
<Tippy {...newProps}>
<div>{props.children}</div>
</Tippy>
);

View File

@ -0,0 +1,50 @@
import { Chip } from "components/Chip";
import { ToolTip } from "components/ToolTip";
import { AppStaticProps } from "graphql/getAppStaticProps";
import { getStatusDescription } from "helpers/others";
import { useSmartLanguage } from "hooks/useSmartLanguage";
interface Props {
source?: string;
translations:
| {
language: string | undefined;
definition: string | null | undefined;
status: string | undefined;
}[]
| undefined;
languages: AppStaticProps["languages"];
langui: AppStaticProps["langui"];
index: number;
}
export default function DefinitionCard(props: Props): JSX.Element {
const { source, translations = [], languages, langui, index } = props;
const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({
items: translations,
languages: languages,
languageExtractor: (item) => item.language,
});
return (
<>
<div className="flex place-items-center gap-2">
{/* TODO: Langui */}
<p className="font-headers text-lg">{`Definition ${index}`}</p>
{selectedTranslation?.status && (
<ToolTip
content={getStatusDescription(selectedTranslation.status, langui)}
maxWidth={"20rem"}
>
<Chip>{selectedTranslation.status}</Chip>
</ToolTip>
)}
{translations.length > 1 && <LanguageSwitcher />}
</div>
<p className="italic">{`${langui.source}: ${source}`}</p>
<p>{selectedTranslation?.definition}</p>
</>
);
}

11
src/helpers/className.ts Normal file
View File

@ -0,0 +1,11 @@
export function cIf(
condition: boolean | null | undefined | string,
ifTrueCss: string,
ifFalseCss?: string
) {
return condition ? ifTrueCss : ifFalseCss ?? "";
}
export function cJoin(...args: (string | undefined)[]): string {
return args.filter((elem) => elem).join(" ");
}

View File

@ -1,4 +1,8 @@
import { GetContentTextQuery, GetPostQuery } from "graphql/generated";
import {
GetContentTextQuery,
GetPostQuery,
GetWikiPageQuery,
} from "graphql/generated";
import React from "react";
type Post = NonNullable<
@ -17,6 +21,15 @@ export interface ContentWithTranslations extends Omit<Content, "translations"> {
translations: NonNullable<Content["translations"]>;
}
type WikiPage = NonNullable<
NonNullable<GetWikiPageQuery["wikiPages"]>["data"][number]["attributes"]
>;
export interface WikiPageWithTranslations
extends Omit<WikiPage, "translations"> {
translations: NonNullable<WikiPage["translations"]>;
}
type ImmutableBlackList<T> = JSX.Element | React.ReactNode | Function;
export type Immutable<T> = {

View File

@ -4,6 +4,7 @@ import {
getPostStaticProps,
PostStaticProps,
} from "graphql/getPostStaticProps";
import { cIf, cJoin } from "helpers/className";
import { randomInt } from "helpers/numbers";
import { Immutable } from "helpers/types";
import { useRouter } from "next/router";
@ -27,10 +28,13 @@ export default function AboutUs(
const contactForm = (
<div className="flex flex-col gap-8 text-center">
<form
className={`grid gap-8 ${
formState !== "stale" &&
"pointer-events-none cursor-not-allowed touch-none opacity-60"
}`}
className={cJoin(
"grid gap-8",
cIf(
formState !== "stale",
"pointer-events-none cursor-not-allowed touch-none opacity-60"
)
)}
onSubmit={(event) => {
event.preventDefault();

View File

@ -64,7 +64,7 @@ export default function Channel(props: Props): JSX.Element {
);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<ContentPanel width={ContentPanelWidthSizes.Full}>
<div className="mb-8">
<h1 className="text-3xl">{channel?.title}</h1>
<p>{channel?.subscribers.toLocaleString()} subscribers</p>

View File

@ -82,7 +82,7 @@ export default function Videos(props: Props): JSX.Element {
);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<ContentPanel width={ContentPanelWidthSizes.Full}>
<PageSelector
maxPage={Math.floor(videos.length / itemPerPage)}
page={page}

View File

@ -72,7 +72,7 @@ export default function Video(props: Props): JSX.Element {
);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<ContentPanel width={ContentPanelWidthSizes.Full}>
<ReturnButton
href="/library/"
title={langui.library}

View File

@ -425,10 +425,7 @@ export async function getStaticProps(
language_code: context.locale ?? "en",
});
if (
!content.contents ||
!content.contents.data[0]?.attributes?.translations
) {
if (!content.contents?.data[0]?.attributes?.translations) {
return { notFound: true };
}
const props: Props = {

View File

@ -143,16 +143,15 @@ export default function Contents(props: Immutable<Props>): JSX.Element {
</SubPanel>
);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
{[...groups].map(([name, items]) => (
<Fragment key={name}>
{items.length > 0 && (
<Fragment>
<ContentPanel width={ContentPanelWidthSizes.Full}>
{[...groups].map(
([name, items], index) =>
items.length > 0 && (
<Fragment key={index}>
{name && (
<h2
key={`h2${name}`}
className="flex flex-row place-items-center gap-2
pb-2 pt-10 text-2xl first-of-type:pt-0"
className="flex flex-row place-items-center gap-2 pb-2 pt-10 text-2xl
first-of-type:pt-0"
>
{name}
<Chip>{`${items.reduce((currentSum, item) => {
@ -173,8 +172,8 @@ export default function Contents(props: Immutable<Props>): JSX.Element {
}`}</Chip>
</h2>
)}
<div
key={`items${name}`}
className="grid grid-cols-2 items-end gap-8
desktop:grid-cols-[repeat(auto-fill,_minmax(15rem,1fr))] mobile:gap-4"
>
@ -226,9 +225,8 @@ export default function Contents(props: Immutable<Props>): JSX.Element {
))}
</div>
</Fragment>
)}
</Fragment>
))}
)
)}
</ContentPanel>
);
return (

View File

@ -21,7 +21,7 @@ export default function CheckupContents(props: Immutable<Props>): JSX.Element {
const testReport = testingContent(contents);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<ContentPanel width={ContentPanelWidthSizes.Full}>
{<h2 className="text-2xl">{testReport.title}</h2>}
<div className="my-4 grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center gap-2">
@ -37,8 +37,8 @@ export default function CheckupContents(props: Immutable<Props>): JSX.Element {
{testReport.lines.map((line, index) => (
<div
key={index}
className="mb-2 grid
grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center justify-items-start gap-2"
className="mb-2 grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center
justify-items-start gap-2"
>
<Button
href={line.frontendUrl}

View File

@ -26,7 +26,7 @@ export default function CheckupLibraryItems(
const testReport = testingLibraryItem(libraryItems);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<ContentPanel width={ContentPanelWidthSizes.Full}>
{<h2 className="text-2xl">{testReport.title}</h2>}
<div className="my-4 grid grid-cols-[2em,3em,2fr,1fr,0.5fr,0.5fr,2fr] items-center gap-2">

View File

@ -146,7 +146,7 @@ export default function Editor(props: Immutable<Props>): JSX.Element {
}
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<ContentPanel width={ContentPanelWidthSizes.Full}>
<Popup setState={setConverterOpened} state={converterOpened}>
<div className="text-center">
<h2 className="mt-4">Convert HTML to markdown</h2>

View File

@ -16,9 +16,8 @@ export default function Home(props: Immutable<PostStaticProps>): JSX.Element {
prependBody={
<div className="grid w-full place-content-center place-items-center gap-5 text-center">
<div
className="aspect-square w-32
bg-black [mask:url('/icons/accords.svg')] [mask-size:contain] [mask-repeat:no-repeat]
[mask-position:center] mobile:w-[50vw]"
className="aspect-square w-32 bg-black [mask:url('/icons/accords.svg')]
[mask-size:contain] [mask-repeat:no-repeat] [mask-position:center] mobile:w-[50vw]"
/>
<h1 className="mb-0 text-5xl">Accord&rsquo;s Library</h1>
<h2 className="-mt-5 text-xl">

View File

@ -119,7 +119,7 @@ export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<ContentPanel width={ContentPanelWidthSizes.Full}>
<LightBox />
<ReturnButton
@ -444,6 +444,7 @@ export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
subtitle={subitem.attributes.subtitle}
thumbnail={subitem.attributes.thumbnail?.data?.attributes}
thumbnailAspectRatio="21/29.7"
thumbnailRounded={false}
keepInfoVisible={keepInfoVisible}
topChips={
subitem.attributes.metadata &&

View File

@ -69,7 +69,7 @@ export default function LibrarySlug(props: Immutable<Props>): JSX.Element {
);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<ContentPanel width={ContentPanelWidthSizes.Full}>
<LightBox />
<ReturnButton

View File

@ -250,7 +250,7 @@ export default function Library(props: Immutable<Props>): JSX.Element {
</SubPanel>
);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<ContentPanel width={ContentPanelWidthSizes.Full}>
{[...groups].map(([name, items]) => (
<Fragment key={name}>
{items.length > 0 && (

View File

@ -85,7 +85,7 @@ export default function News(props: Immutable<Props>): JSX.Element {
);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<ContentPanel width={ContentPanelWidthSizes.Full}>
<div
className="grid grid-cols-1 items-end gap-8
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))]"

View File

@ -1,10 +1,21 @@
import { AppLayout } from "components/AppLayout";
import { ContentPanel } from "components/Panels/ContentPanel";
import { Chip } from "components/Chip";
import { HorizontalLine } from "components/HorizontalLine";
import { Img } from "components/Img";
import {
ReturnButton,
ReturnButtonType,
} from "components/PanelComponents/ReturnButton";
import {
ContentPanel,
ContentPanelWidthSizes,
} from "components/Panels/ContentPanel";
import { SubPanel } from "components/Panels/SubPanel";
import { GetWikiPageQuery } from "graphql/generated";
import DefinitionCard from "components/Wiki/DefinitionCard";
import { AppStaticProps, getAppStaticProps } from "graphql/getAppStaticProps";
import { getReadySdk } from "graphql/sdk";
import { Immutable } from "helpers/types";
import { Immutable, WikiPageWithTranslations } from "helpers/types";
import { useSmartLanguage } from "hooks/useSmartLanguage";
import {
GetStaticPathsContext,
GetStaticPathsResult,
@ -12,16 +23,89 @@ import {
} from "next";
interface Props extends AppStaticProps {
page: NonNullable<
NonNullable<GetWikiPageQuery["wikiPages"]>["data"][number]["attributes"]
>;
page: WikiPageWithTranslations;
}
export default function WikiPage(props: Immutable<Props>): JSX.Element {
const { page, langui } = props;
const { page, langui, languages } = props;
const subPanel = <SubPanel>Hello</SubPanel>;
const contentPanel = <ContentPanel>{page.slug}</ContentPanel>;
const [selectedTranslation, LanguageSwitcher] = useSmartLanguage({
items: page.translations,
languages: languages,
languageExtractor: (item) => item.language?.data?.attributes?.code,
});
const subPanel = (
<SubPanel>
<ReturnButton
href={`/wiki`}
title={langui.wiki}
langui={langui}
displayOn={ReturnButtonType.Desktop}
horizontalLine
/>
</SubPanel>
);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<ReturnButton
href={`/wiki`}
title={langui.wiki}
langui={langui}
displayOn={ReturnButtonType.Mobile}
className="mb-10"
/>
<div className="flex place-content-center gap-4">
<h1 className="text-center text-3xl">{selectedTranslation?.title}</h1>
<LanguageSwitcher />
</div>
<HorizontalLine />
{selectedTranslation && (
<div className="text-justify">
<div
className="float-right ml-8 mb-8 w-[25rem] overflow-hidden rounded-lg bg-mid
text-center"
>
{page.thumbnail?.data?.attributes && (
<Img image={page.thumbnail.data.attributes} />
)}
<div className="my-4 grid gap-4 p-4">
<p className="font-headers text-xl">{langui.categories}</p>
<div className="flex flex-row flex-wrap place-content-center gap-2">
{page.categories?.data.map((category) => (
<Chip key={category.id}>{category.attributes?.name}</Chip>
))}
</div>
</div>
</div>
{selectedTranslation.summary && (
<div className="mb-6">
<p className="font-headers text-lg">{langui.summary}</p>
<p>{selectedTranslation.summary}</p>
</div>
)}
{page.definitions?.map((definition, index) => (
<DefinitionCard
key={index}
source={definition?.source?.data?.attributes?.name}
translations={definition?.translations?.map((translation) => ({
language: translation?.language?.data?.attributes?.code,
definition: translation?.definition,
status: translation?.status,
}))}
index={index + 1}
languages={languages}
langui={langui}
/>
))}
</div>
)}
</ContentPanel>
);
return (
<AppLayout
@ -42,10 +126,11 @@ export async function getStaticProps(
language_code: context.locale ?? "en",
slug: slug,
});
if (!page.wikiPages?.data[0].attributes) return { notFound: true };
if (!page.wikiPages?.data[0].attributes?.translations)
return { notFound: true };
const props: Props = {
...(await getAppStaticProps(context)),
page: page.wikiPages.data[0].attributes,
page: page.wikiPages.data[0].attributes as WikiPageWithTranslations,
};
return {
props: props,

View File

@ -92,7 +92,7 @@ export default function Wiki(props: Immutable<Props>): JSX.Element {
);
const contentPanel = (
<ContentPanel width={ContentPanelWidthSizes.Large}>
<ContentPanel width={ContentPanelWidthSizes.Full}>
<div
className="grid grid-cols-2 items-end gap-8
desktop:grid-cols-[repeat(auto-fill,_minmax(20rem,1fr))] mobile:gap-4"

View File

@ -172,6 +172,19 @@ input[type="submit"] {
[background-blend-mode:var(--theme-texture-dots-blend)];
}
/* DEBUGGING */
.false {
@apply border-2 border-[red] text-[red] outline-dotted outline-2 outline-[red];
}
.undefined {
@apply border-2 border-[green] text-[green] outline-dotted outline-2 outline-[green];
}
.null {
@apply border-2 border-[blue] text-[blue] outline-dotted outline-2 outline-[blue];
}
/* TIPPY */
.tippy-box[data-animation="fade"][data-state="hidden"] {