diff --git a/README.md b/README.md index ca8bae7..296e4c6 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,13 @@ - Support for Arbitrary React Components and Component Props! - Autogenerated multi-level table of content and anchor links for the different headers - Styling: [Tailwind CSS](https://tailwindcss.com/) - - Manually added support for scrollbar styling to Tailwind CSS - Support for [Material Icons](https://fonts.google.com/icons) - Support for creating any arbitrary theming mode by swapping CSS variables - Support for Container Queries (media queries at the element level) - The website has a three-column layout, which turns into one-column + 2 toggleable side-menus if the screen is too narrow. + - Show out the Design System Showcase [here](https://accords-library.com/dev/showcase/design-system) - State Management: [React Context](https://reactjs.org/docs/context.html) - - Persistent app state using LocalStorage + - Persistent app state using LocalStorage and SessionStorage - Accessibility - Gestures using [react-swipeable](https://www.npmjs.com/package/react-swipeable) - Keyboard hotkeys using [react-hotkeys-hook](https://www.npmjs.com/package/react-hotkeys-hook) @@ -54,7 +54,7 @@ - Furthermore, the user can temporary select another language then the one that was automatically selected - SSG + ISR (Static Site Generation + Incremental Static Regeneration): - The website is built before running in production - - Performances are great, and possibility to deploy the app using a CDN + - Performances are great, and possibility to deploy the app on a CDN - On-Demand ISR to continuously update the website when new content is added or existing content is modified/deleted - SEO - Good defaults for the metadata and OpenGraph properties diff --git a/next.config.js b/next.config.js index 424c9e0..50cd568 100644 --- a/next.config.js +++ b/next.config.js @@ -8,6 +8,7 @@ const locales = ["en", "es", "fr", "pt-br", "ja"]; module.exports = { swcMinify: true, reactStrictMode: true, + poweredByHeader: false, i18n: { locales: locales, defaultLocale: "en", diff --git a/src/components/AppLayout.tsx b/src/components/AppLayout.tsx index 0770f35..cdcf37f 100644 --- a/src/components/AppLayout.tsx +++ b/src/components/AppLayout.tsx @@ -168,8 +168,7 @@ export const AppLayout = ({ id={Ids.SubPanel} className={cJoin( `texture-paper-dots z-20 overflow-y-scroll border-r border-dark/50 - bg-light transition-transform duration-300 [scrollbar-width:none] - webkit-scrollbar:w-0`, + bg-light transition-transform duration-300 scrollbar-none`, cIf( is1ColumnLayout, "justify-self-end border-r-0 [grid-area:content]", @@ -187,7 +186,7 @@ export const AppLayout = ({
( - ( + -
-

{date.year}

-

{prettyMonthDay(date.month, date.day)}

-
-

{title}

- + active={active} + border + disabled={disabled}> + {isDefined(date.year) && ( +
+

{date.year}

+

{prettyMonthDay(date.month, date.day)}

+
+ )} + +

+ {title} +

+
); /* diff --git a/src/components/Chronicles/ChroniclesList.tsx b/src/components/Chronicles/ChroniclesList.tsx index 80c563e..eb6a64c 100644 --- a/src/components/Chronicles/ChroniclesList.tsx +++ b/src/components/Chronicles/ChroniclesList.tsx @@ -54,7 +54,7 @@ const ChroniclesList = ({ chronicles, currentSlug, title }: Props): JSX.Element ] as const).map((content, index) => ( 0 && (
+ className="h-full overflow-scroll scroll-auto p-6 scrollbar-none"> {previousLines.map((previousLine, index) => (

{previousLine} @@ -311,7 +310,7 @@ export const Terminal = ({ {line[carretPosition] ?? " "} diff --git a/src/components/Panels/ContentPanel.tsx b/src/components/Containers/ContentPanel.tsx similarity index 100% rename from src/components/Panels/ContentPanel.tsx rename to src/components/Containers/ContentPanel.tsx diff --git a/src/components/Containers/DownPressable.tsx b/src/components/Containers/DownPressable.tsx new file mode 100644 index 0000000..7852ffc --- /dev/null +++ b/src/components/Containers/DownPressable.tsx @@ -0,0 +1,61 @@ +import { MouseEventHandler, useState } from "react"; +import { cJoin, cIf } from "helpers/className"; +import { Link } from "components/Inputs/Link"; +/* + * ╭─────────────╮ + * ───────────────────────────────────────╯ COMPONENT ╰─────────────────────────────────────────── + */ + +interface Props { + border?: boolean; + active?: boolean; + disabled?: boolean; + href: string; + children: React.ReactNode; + className?: string; + onFocusChanged?: (isFocused: boolean) => void; + onClick?: MouseEventHandler; +} + +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + +export const DownPressable = ({ + href, + border = false, + active = false, + disabled = false, + children, + className, + onFocusChanged, + onClick, +}: Props): JSX.Element => { + const [isFocused, setFocused] = useState(false); + + return ( + { + setFocused(focus); + onFocusChanged?.(focus); + }} + className={cJoin( + `rounded-2xl p-4 transition-all`, + cIf(border, "outline outline-2 -outline-offset-2 outline-mid"), + cIf(active, "!bg-mid shadow-inner-sm outline-transparent shadow-shade"), + cIf( + disabled, + "cursor-not-allowed select-none opacity-50 grayscale", + cJoin( + "cursor-pointer hover:bg-mid hover:shadow-inner-sm hover:shadow-shade", + cIf(isFocused, "!shadow-inner !shadow-shade"), + cIf(border, "hover:outline-transparent") + ) + ), + className + )} + disabled={disabled}> + {children} + + ); +}; diff --git a/src/components/InsetBox.tsx b/src/components/Containers/InsetBox.tsx similarity index 100% rename from src/components/InsetBox.tsx rename to src/components/Containers/InsetBox.tsx diff --git a/src/components/Popup.tsx b/src/components/Containers/Popup.tsx similarity index 100% rename from src/components/Popup.tsx rename to src/components/Containers/Popup.tsx diff --git a/src/components/Panels/SubPanel.tsx b/src/components/Containers/SubPanel.tsx similarity index 100% rename from src/components/Panels/SubPanel.tsx rename to src/components/Containers/SubPanel.tsx diff --git a/src/components/Containers/UpPressable.tsx b/src/components/Containers/UpPressable.tsx new file mode 100644 index 0000000..8c387e3 --- /dev/null +++ b/src/components/Containers/UpPressable.tsx @@ -0,0 +1,42 @@ +import { useState } from "react"; +import { Link } from "components/Inputs/Link"; +import { cIf, cJoin } from "helpers/className"; + +interface Props { + children: React.ReactNode; + href: string; + className?: string; + noBackground?: boolean; + disabled?: boolean; +} + +export const UpPressable = ({ + children, + href, + className, + disabled = false, + noBackground = false, +}: Props): JSX.Element => { + const [isFocused, setFocused] = useState(false); + return ( + + {children} + + ); +}; diff --git a/src/components/Contents/PreviewFolder.tsx b/src/components/Contents/PreviewFolder.tsx new file mode 100644 index 0000000..fe4f056 --- /dev/null +++ b/src/components/Contents/PreviewFolder.tsx @@ -0,0 +1,38 @@ +import { useCallback } from "react"; +import { useSmartLanguage } from "hooks/useSmartLanguage"; +import { TranslatedProps } from "types/TranslatedProps"; +import { UpPressable } from "components/Containers/UpPressable"; +import { cIf, cJoin } from "helpers/className"; + +interface PreviewFolderProps { + href: string; + title?: string | null; + disabled?: boolean; +} + +export const PreviewFolder = ({ href, title, disabled }: PreviewFolderProps): JSX.Element => ( + +

+ {title &&

{title}

} +
+ +); + +// ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ + +export const TranslatedPreviewFolder = ({ + translations, + fallback, + ...otherProps +}: TranslatedProps): JSX.Element => { + const [selectedTranslation] = useSmartLanguage({ + items: translations, + languageExtractor: useCallback((item: { language: string }): string => item.language, []), + }); + return ; +}; diff --git a/src/components/Inputs/Button.tsx b/src/components/Inputs/Button.tsx index 795189a..e24569e 100644 --- a/src/components/Inputs/Button.tsx +++ b/src/components/Inputs/Button.tsx @@ -46,28 +46,32 @@ export const Button = ({ size = "normal", }: Props): JSX.Element => (
!disabled && onClick?.(event)} onMouseUp={onMouseUp} onFocus={(event) => event.target.blur()} className={cJoin( `group grid cursor-pointer select-none grid-flow-col place-content-center place-items-center gap-2 rounded-full border border-dark py-3 px-4 leading-none text-dark transition-all`, - cIf( - active, - "!border-black bg-black !text-light drop-shadow-black-lg", - `hover:bg-dark hover:text-light hover:drop-shadow-shade-lg active:hover:!border-black - active:hover:bg-black active:hover:!text-light active:hover:drop-shadow-black-lg` - ), cIf(size === "small", "px-3 py-1 text-xs"), - cIf(disabled, "cursor-not-allowed"), + cIf(active, "!border-black bg-black !text-light drop-shadow-lg shadow-black"), + cIf( + disabled, + "cursor-not-allowed opacity-50 grayscale", + cIf( + !active, + `shadow-shade hover:bg-dark hover:text-light hover:drop-shadow-lg + active:hover:!border-black active:hover:bg-black active:hover:!text-light + active:hover:drop-shadow-lg active:hover:shadow-black` + ) + ), className )}> {isDefined(badgeNumber) && ( diff --git a/src/components/Inputs/Link.tsx b/src/components/Inputs/Link.tsx index 0df1860..8898229 100644 --- a/src/components/Inputs/Link.tsx +++ b/src/components/Inputs/Link.tsx @@ -1,5 +1,5 @@ import router from "next/router"; -import { MouseEventHandler, useState } from "react"; +import { PointerEventHandler, useState } from "react"; import { isDefined } from "helpers/others"; interface Props { @@ -8,43 +8,56 @@ interface Props { allowNewTab?: boolean; alwaysNewTab?: boolean; children: React.ReactNode; - onClick?: MouseEventHandler; + onClick?: PointerEventHandler; + onFocusChanged?: (isFocused: boolean) => void; + disabled?: boolean; } export const Link = ({ href, allowNewTab = true, alwaysNewTab = false, + disabled = false, children, className, onClick, + onFocusChanged, }: Props): JSX.Element => { const [isValidClick, setIsValidClick] = useState(false); return (
setIsValidClick(false)} - onContextMenu={(event) => event.preventDefault()} - onMouseDown={(event) => { - event.preventDefault(); - setIsValidClick(true); + onPointerLeave={() => { + setIsValidClick(false); + onFocusChanged?.(false); }} - onMouseUp={(event) => { - if (isDefined(onClick)) { - onClick(event); - } else if (isValidClick && href) { - if (event.button !== MouseButton.Right) { - if (alwaysNewTab) { - window.open(href, "_blank", "noopener"); - } else if (event.button === MouseButton.Left) { - if (href.startsWith("#")) { - router.replace(href); - } else { - router.push(href); + onContextMenu={(event) => event.preventDefault()} + onPointerDown={(event) => { + if (!disabled) { + event.preventDefault(); + onFocusChanged?.(true); + setIsValidClick(true); + } + }} + onPointerUp={(event) => { + onFocusChanged?.(false); + if (!disabled) { + if (isDefined(onClick)) { + onClick(event); + } else if (isValidClick && href) { + if (event.button !== MouseButton.Right) { + if (alwaysNewTab) { + window.open(href, "_blank", "noopener"); + } else if (event.button === MouseButton.Left) { + if (href.startsWith("#")) { + router.replace(href); + } else { + router.push(href); + } + } else if (allowNewTab) { + window.open(href, "_blank"); } - } else if (allowNewTab) { - window.open(href, "_blank"); } } } diff --git a/src/components/Inputs/OrderableList.tsx b/src/components/Inputs/OrderableList.tsx index cf9c7d4..f8e7f1e 100644 --- a/src/components/Inputs/OrderableList.tsx +++ b/src/components/Inputs/OrderableList.tsx @@ -58,7 +58,7 @@ export const OrderableList = ({ onChange, items, insertLabels }: Props): JSX.Ele }} className="grid cursor-grab select-none grid-cols-[auto_1fr] place-content-center gap-2 rounded-full border border-dark bg-light px-1 py-2 pr-4 text-dark transition-all - hover:bg-dark hover:text-light hover:drop-shadow-shade-lg" + hover:shadow-shade hover:bg-dark hover:text-light hover:shadow-lg" draggable>
{index > 0 && ( diff --git a/src/components/Inputs/Select.tsx b/src/components/Inputs/Select.tsx index 60a66f1..72c1c60 100644 --- a/src/components/Inputs/Select.tsx +++ b/src/components/Inputs/Select.tsx @@ -15,17 +15,34 @@ interface Props { allowEmpty?: boolean; className?: string; onChange: (value: number) => void; + disabled?: boolean; } // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ -export const Select = ({ className, value, options, allowEmpty, onChange }: Props): JSX.Element => { +export const Select = ({ + className, + value, + options, + allowEmpty, + disabled = false, + onChange, +}: Props): JSX.Element => { const { value: isOpened, setFalse: setClosed, toggle: toggleOpened } = useBoolean(false); const tryToggling = useCallback(() => { + if (disabled) return; const optionCount = options.length + (value === -1 ? 1 : 0); if (optionCount > 1) toggleOpened(); - }, [options.length, value, toggleOpened]); + }, [disabled, options.length, value, toggleOpened]); + + const onSelectionChanged = useCallback( + (newIndex: number) => { + setClosed(); + onChange(newIndex); + }, + [onChange, setClosed] + ); const ref = useRef(null); useOnClickOutside(ref, setClosed); @@ -35,27 +52,29 @@ export const Select = ({ className, value, options, allowEmpty, onChange }: Prop ref={ref} className={cJoin( "relative text-center transition-filter", - cIf(isOpened, "z-10 drop-shadow-shade-lg"), + cIf(isOpened, "z-10 drop-shadow-lg shadow-shade"), className )}>
-

+

{value === -1 ? "—" : options[value]}

{value >= 0 && allowEmpty && ( { - setClosed(); - onChange(-1); - }} + onClick={() => !disabled && onSelectionChanged(-1)} /> )} @@ -70,10 +89,7 @@ export const Select = ({ className, value, options, allowEmpty, onChange }: Prop cIf(isOpened, "bg-highlight", "bg-light") )} id={option} - onClick={() => { - setClosed(); - onChange(index); - }}> + onClick={() => onSelectionChanged(index)}> {option}
)} diff --git a/src/components/Inputs/Switch.tsx b/src/components/Inputs/Switch.tsx index c2cf389..6942767 100644 --- a/src/components/Inputs/Switch.tsx +++ b/src/components/Inputs/Switch.tsx @@ -22,24 +22,21 @@ export const Switch = ({ value, onClick, className, disabled = false }: Props): className={cJoin( `relative grid h-6 w-12 content-center rounded-full border-mid outline outline-1 -outline-offset-1 outline-mid transition-colors`, - cIf(disabled, "cursor-not-allowed", "cursor-pointer"), - cIf( - value, - "border-none bg-mid shadow-inner-sm shadow-shade outline-transparent", - "bg-light" - ), + cIf(value, "border-none bg-mid shadow-inner-sm shadow-shade outline-transparent"), + cIf(disabled, "cursor-not-allowed opacity-50 grayscale", "cursor-pointer"), + cIf(disabled, cIf(value, "bg-dark/40 outline-transparent", "outline-dark/60")), className )} onClick={() => { if (!disabled) onClick(); }} - onPointerDown={() => setIsFocused(true)} + onPointerDown={() => !disabled && setIsFocused(true)} onPointerOut={() => setIsFocused(false)} onPointerLeave={() => setIsFocused(false)} onPointerUp={() => setIsFocused(false)}>
(
{ onChange(event.target.value); @@ -38,11 +41,9 @@ export const TextInput = ({ {isDefinedAndNotEmpty(value) && (
{ - onChange(""); - }} + onClick={() => !disabled && onChange("")} />
)} diff --git a/src/components/Inputs/WithLabel.tsx b/src/components/Inputs/WithLabel.tsx index e2504f1..b91cb67 100644 --- a/src/components/Inputs/WithLabel.tsx +++ b/src/components/Inputs/WithLabel.tsx @@ -8,18 +8,13 @@ import { isDefinedAndNotEmpty } from "helpers/others"; interface Props { label: string | null | undefined; - disabled?: boolean; children: React.ReactNode; } // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ -export const WithLabel = ({ label, children, disabled }: Props): JSX.Element => ( -
+export const WithLabel = ({ label, children }: Props): JSX.Element => ( +
{isDefinedAndNotEmpty(label) && (

{label}:

)} diff --git a/src/components/LightBox.tsx b/src/components/LightBox.tsx index 9f8da88..eaf0bdd 100644 --- a/src/components/LightBox.tsx +++ b/src/components/LightBox.tsx @@ -10,15 +10,16 @@ import { Ids } from "types/ids"; import { UploadImageFragment } from "graphql/generated"; import { ImageQuality } from "helpers/img"; import { isDefined } from "helpers/others"; +import { useContainerQueries } from "contexts/ContainerQueriesContext"; interface Props { onCloseRequest: () => void; isVisible: boolean; image?: UploadImageFragment | string; - isNextImageAvailable?: boolean; - isPreviousImageAvailable?: boolean; - onPressNext?: () => void; - onPressPrevious?: () => void; + isNextImageAvailable: boolean; + isPreviousImageAvailable: boolean; + onPressNext: () => void; + onPressPrevious: () => void; } // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ @@ -37,18 +38,15 @@ export const LightBox = ({ Ids.LightBox ); - useHotkeys( - "left", - () => onPressPrevious?.(), - { enabled: isVisible && isPreviousImageAvailable }, - [onPressPrevious] - ); + useHotkeys("left", () => onPressPrevious(), { enabled: isVisible && isPreviousImageAvailable }, [ + onPressPrevious, + ]); useHotkeys("f", () => requestFullscreen(), { enabled: isVisible && !isFullscreen }, [ requestFullscreen, ]); - useHotkeys("right", () => onPressNext?.(), { enabled: isVisible && isNextImageAvailable }, [ + useHotkeys("right", () => onPressNext(), { enabled: isVisible && isNextImageAvailable }, [ onPressNext, ]); @@ -69,7 +67,7 @@ export const LightBox = ({ />
{isDefined(src) && ( )} - - {isPreviousImageAvailable && ( -
-
- )} - - {isNextImageAvailable && ( -
-
- )} - -
-
+ { + resetTransform(); + exitFullscreen(); + onCloseRequest(); + }} + onPressPrevious={() => { + resetTransform(); + onPressPrevious(); + }} + onPressNext={() => { + resetTransform(); + onPressNext(); + }} + toggleFullscreen={toggleFullscreen} + /> )}
@@ -134,3 +118,83 @@ export const LightBox = ({
); }; + +interface ControlButtonsProps { + isPreviousImageAvailable: boolean; + isNextImageAvailable: boolean; + isFullscreen: boolean; + onPressPrevious?: () => void; + onPressNext?: () => void; + onCloseRequest: () => void; + toggleFullscreen: () => void; +} + +const ControlButtons = ({ + isFullscreen, + isPreviousImageAvailable, + isNextImageAvailable, + onPressPrevious, + onPressNext, + onCloseRequest, + toggleFullscreen, +}: ControlButtonsProps): JSX.Element => { + const { is1ColumnLayout } = useContainerQueries(); + + const PreviousButton = () => ( +
), diff --git a/src/components/PanelComponents/NavOption.tsx b/src/components/PanelComponents/NavOption.tsx index 446c111..660db84 100644 --- a/src/components/PanelComponents/NavOption.tsx +++ b/src/components/PanelComponents/NavOption.tsx @@ -1,12 +1,12 @@ import { useRouter } from "next/router"; -import { MouseEventHandler, useCallback, useMemo } from "react"; +import { MouseEventHandler, useCallback, useMemo, useState } from "react"; import { Ico, Icon } from "components/Ico"; import { ToolTip } from "components/ToolTip"; -import { cJoin, cIf } from "helpers/className"; +import { cIf, cJoin } from "helpers/className"; import { isDefinedAndNotEmpty } from "helpers/others"; -import { Link } from "components/Inputs/Link"; import { TranslatedProps } from "types/TranslatedProps"; import { useSmartLanguage } from "hooks/useSmartLanguage"; +import { DownPressable } from "components/Containers/DownPressable"; /* * ╭─────────────╮ @@ -21,6 +21,7 @@ interface Props { border?: boolean; reduced?: boolean; active?: boolean; + disabled?: boolean; onClick?: MouseEventHandler; } @@ -34,6 +35,7 @@ export const NavOption = ({ border = false, reduced = false, active = false, + disabled = false, onClick, }: Props): JSX.Element => { const router = useRouter(); @@ -41,6 +43,7 @@ export const NavOption = ({ () => active || router.asPath.startsWith(url), [active, router.asPath, url] ); + const [isFocused, setFocused] = useState(false); return ( - + - {icon && } - + "grid w-full auto-cols-fr grid-flow-col grid-cols-[auto] justify-center gap-x-5", + cIf(icon, "text-left", "text-center") + )} + href={url} + border={border} + onClick={onClick} + active={isActive} + disabled={disabled} + onFocusChanged={setFocused}> + {icon && ( + + )} {!reduced && (

{title}

{isDefinedAndNotEmpty(subtitle) &&

{subtitle}

}
)} - +
); }; diff --git a/src/components/Panels/SafariPopup.tsx b/src/components/Panels/SafariPopup.tsx index 3d83572..a208c6c 100644 --- a/src/components/Panels/SafariPopup.tsx +++ b/src/components/Panels/SafariPopup.tsx @@ -2,7 +2,7 @@ import { useMemo } from "react"; import UAParser from "ua-parser-js"; import { useIsClient, useSessionStorage } from "usehooks-ts"; import { Button } from "components/Inputs/Button"; -import { Popup } from "components/Popup"; +import { Popup } from "components/Containers/Popup"; import { sendAnalytics } from "helpers/analytics"; export const SafariPopup = (): JSX.Element => { diff --git a/src/components/Panels/SettingsPopup.tsx b/src/components/Panels/SettingsPopup.tsx index 65b6d023..609924e 100644 --- a/src/components/Panels/SettingsPopup.tsx +++ b/src/components/Panels/SettingsPopup.tsx @@ -6,7 +6,7 @@ import { ButtonGroup } from "components/Inputs/ButtonGroup"; import { OrderableList } from "components/Inputs/OrderableList"; import { Select } from "components/Inputs/Select"; import { TextInput } from "components/Inputs/TextInput"; -import { Popup } from "components/Popup"; +import { Popup } from "components/Containers/Popup"; import { useAppLayout } from "contexts/AppLayoutContext"; import { useLocalData } from "contexts/LocalDataContext"; import { useUserSettings } from "contexts/UserSettingsContext"; diff --git a/src/components/PostPage.tsx b/src/components/PostPage.tsx index 05e7f45..5c13ff8 100644 --- a/src/components/PostPage.tsx +++ b/src/components/PostPage.tsx @@ -4,8 +4,8 @@ import { Chip } from "./Chip"; import { HorizontalLine } from "./HorizontalLine"; import { Markdawn, TableOfContents } from "./Markdown/Markdawn"; import { ReturnButton } from "./PanelComponents/ReturnButton"; -import { ContentPanel } from "./Panels/ContentPanel"; -import { SubPanel } from "./Panels/SubPanel"; +import { ContentPanel } from "./Containers/ContentPanel"; +import { SubPanel } from "./Containers/SubPanel"; import { RecorderChip } from "./RecorderChip"; import { ThumbnailHeader } from "./ThumbnailHeader"; import { ToolTip } from "./ToolTip"; diff --git a/src/components/PreviewCard.tsx b/src/components/PreviewCard.tsx index 1bd60d1..7971dfc 100644 --- a/src/components/PreviewCard.tsx +++ b/src/components/PreviewCard.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/router"; import { Chip } from "./Chip"; import { Ico, Icon } from "./Ico"; import { Img } from "./Img"; -import { Link } from "./Inputs/Link"; +import { UpPressable } from "./Containers/UpPressable"; import { DatePickerFragment, PricePickerFragment, UploadImageFragment } from "graphql/generated"; import { cIf, cJoin } from "helpers/className"; import { prettyDate, prettyDuration, prettyPrice, prettyShortenNumber } from "helpers/formatters"; @@ -47,6 +47,7 @@ interface Props { duration: number; } | { __typename: "anotherHoverlayName" }; + disabled?: boolean; } // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ @@ -67,6 +68,7 @@ export const PreviewCard = ({ metadata, hoverlay, infoAppend, + disabled = false, }: Props): JSX.Element => { const { currency } = useUserSettings(); const { currencies } = useLocalData(); @@ -110,97 +112,101 @@ export const PreviewCard = ({ ); return ( - - {thumbnail ? ( -
- - - {hoverlay && hoverlay.__typename === "Video" && ( - <> -
- -
-
- {prettyDuration(hoverlay.duration)} -
- - )} -
- ) : ( -
- )} -
- {metadata?.position === "Top" && metadataJSX} - {topChips && topChips.length > 0 && ( -
- {topChips.map((text, index) => ( - - ))} -
- )} -
- {pre_title &&

{pre_title}

} - {title && ( -

{title}

- )} - {subtitle &&

{subtitle}

} -
- {description &&

{description}

} - {bottomChips && bottomChips.length > 0 && ( + +
+ {thumbnail ? (
- {bottomChips.map((text, index) => ( - - ))} + className="relative" + style={{ + aspectRatio: thumbnailForceAspectRatio ? thumbnailAspectRatio : "unset", + }}> + + + {hoverlay && hoverlay.__typename === "Video" && ( + <> +
+ +
+
+ {prettyDuration(hoverlay.duration)} +
+ + )}
+ ) : ( +
)} +
+ {metadata?.position === "Top" && metadataJSX} + {topChips && topChips.length > 0 && ( +
+ {topChips.map((text, index) => ( + + ))} +
+ )} +
+ {pre_title &&

{pre_title}

} + {title && ( +

{title}

+ )} + {subtitle &&

{subtitle}

} +
+ {description &&

{description}

} + {bottomChips && bottomChips.length > 0 && ( +
+ {bottomChips.map((text, index) => ( + + ))} +
+ )} - {metadata?.position === "Bottom" && metadataJSX} + {metadata?.position === "Bottom" && metadataJSX} - {infoAppend} + {infoAppend} +
- + ); }; diff --git a/src/components/PreviewLine.tsx b/src/components/PreviewLine.tsx index a164675..313f82e 100644 --- a/src/components/PreviewLine.tsx +++ b/src/components/PreviewLine.tsx @@ -1,11 +1,13 @@ import { useCallback } from "react"; import { Chip } from "./Chip"; import { Img } from "./Img"; -import { Link } from "./Inputs/Link"; +import { UpPressable } from "./Containers/UpPressable"; import { UploadImageFragment } from "graphql/generated"; import { ImageQuality } from "helpers/img"; import { TranslatedProps } from "types/TranslatedProps"; import { useSmartLanguage } from "hooks/useSmartLanguage"; +import { cIf, cJoin } from "helpers/className"; +import { isDefined } from "helpers/others"; /* * ╭─────────────╮ @@ -14,60 +16,66 @@ import { useSmartLanguage } from "hooks/useSmartLanguage"; interface Props { thumbnail?: UploadImageFragment | string | null | undefined; - thumbnailAspectRatio?: string; href: string; pre_title?: string | null | undefined; title: string | null | undefined; subtitle?: string | null | undefined; topChips?: string[]; bottomChips?: string[]; + disabled?: boolean; } // ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ -const PreviewLine = ({ +export const PreviewLine = ({ href, thumbnail, pre_title, title, subtitle, topChips, + disabled, bottomChips, - thumbnailAspectRatio, }: Props): JSX.Element => ( - - {thumbnail ? ( -
- -
- ) : ( -
- )} -
- {topChips && topChips.length > 0 && ( -
- {topChips.map((text, index) => ( - - ))} + +
+ {thumbnail && ( +
+
)} -
- {pre_title &&

{pre_title}

} - {title &&

{title}

} - {subtitle &&

{subtitle}

} -
- {bottomChips && bottomChips.length > 0 && ( -
- {bottomChips.map((text, index) => ( - - ))} + +
+ {topChips && topChips.length > 0 && ( +
+ {topChips.map((text, index) => ( + + ))} +
+ )} +
+ {pre_title &&

{pre_title}

} + {title &&

{title}

} + {subtitle &&

{subtitle}

}
- )} + {bottomChips && bottomChips.length > 0 && ( +
+ {bottomChips.map((text, index) => ( + + ))} +
+ )} +
- + ); /* diff --git a/src/components/SmartList.tsx b/src/components/SmartList.tsx index 3f807d1..b067acf 100644 --- a/src/components/SmartList.tsx +++ b/src/components/SmartList.tsx @@ -9,6 +9,7 @@ import { useScrollTopOnChange } from "hooks/useScrollTopOnChange"; import { Ids } from "types/ids"; import { useLocalData } from "contexts/LocalDataContext"; import { useContainerQueries } from "contexts/ContainerQueriesContext"; +import { useHotkeys } from "react-hotkeys-hook"; interface Group { name: string; @@ -163,6 +164,11 @@ export const SmartList = ({ return memo; }, [groups, paginationItemPerPage]); + useHotkeys("left", () => setPage((current) => current - 1), { enabled: page > 0 }); + useHotkeys("right", () => setPage((current) => current + 1), { + enabled: page < pages.length - 1, + }); + return ( <> {pages.length > 1 && paginationSelectorTop && ( diff --git a/src/components/ThumbnailHeader.tsx b/src/components/ThumbnailHeader.tsx index 5d22e54..1396254 100644 --- a/src/components/ThumbnailHeader.tsx +++ b/src/components/ThumbnailHeader.tsx @@ -1,6 +1,6 @@ import { Chip } from "components/Chip"; import { Img } from "components/Img"; -import { InsetBox } from "components/InsetBox"; +import { InsetBox } from "components/Containers/InsetBox"; import { Markdawn } from "components/Markdown/Markdawn"; import { GetContentTextQuery, UploadImageFragment } from "graphql/generated"; import { prettyInlineTitle, prettySlug, slugify } from "helpers/formatters"; @@ -48,7 +48,7 @@ export const ThumbnailHeader = ({ return ( <>
-
+
{thumbnail ? ( (condition ? ifTrueCss : ifFalseCss ?? ""); +): string => removeWhitespace(condition ? ifTrueCss : ifFalseCss ?? ""); export const cJoin = (...args: (string | undefined)[]): string => - args.filter((elem) => elem).join(" "); + removeWhitespace(args.filter((elem) => elem).join(" ")); + +const removeWhitespace = (string: string): string => string.replaceAll(/\s+/gu, " "); diff --git a/src/pages/404.tsx b/src/pages/404.tsx index a7c3d4e..496222b 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -1,7 +1,7 @@ import { GetStaticProps } from "next"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { ReturnButton } from "components/PanelComponents/ReturnButton"; -import { ContentPanel } from "components/Panels/ContentPanel"; +import { ContentPanel } from "components/Containers/ContentPanel"; import { getOpenGraph } from "helpers/openGraph"; import { getLangui } from "graphql/fetchLocalData"; import { Img } from "components/Img"; @@ -20,7 +20,10 @@ const FourOhFour = ({ openGraph, ...otherProps }: Props): JSX.Element => { - +

{langui.page_not_found}

diff --git a/src/pages/500.tsx b/src/pages/500.tsx index 260cec8..d59dc01 100644 --- a/src/pages/500.tsx +++ b/src/pages/500.tsx @@ -1,7 +1,7 @@ import { GetStaticProps } from "next"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { ReturnButton } from "components/PanelComponents/ReturnButton"; -import { ContentPanel } from "components/Panels/ContentPanel"; +import { ContentPanel } from "components/Containers/ContentPanel"; import { getOpenGraph } from "helpers/openGraph"; import { getLangui } from "graphql/fetchLocalData"; import { Img } from "components/Img"; @@ -20,7 +20,10 @@ const FiveHundred = ({ openGraph, ...otherProps }: Props): JSX.Element => { - +

{langui.page_not_found}

diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 7e2dc72..bbc6e04 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -11,8 +11,6 @@ import type { AppProps } from "next/app"; import Script from "next/script"; import { AppContextProvider } from "contexts/AppLayoutContext"; -import "styles/animations.css"; -import "styles/custom-classes.css"; import "styles/debug.css"; import "styles/formatted.css"; import "styles/others.css"; diff --git a/src/pages/about-us/contact.tsx b/src/pages/about-us/contact.tsx index 5d9d1cd..5571c7a 100644 --- a/src/pages/about-us/contact.tsx +++ b/src/pages/about-us/contact.tsx @@ -1,6 +1,6 @@ import { useRouter } from "next/router"; import { useState } from "react"; -import { InsetBox } from "components/InsetBox"; +import { InsetBox } from "components/Containers/InsetBox"; import { PostPage } from "components/PostPage"; import { getPostStaticProps, PostStaticProps } from "graphql/getPostStaticProps"; import { cIf, cJoin } from "helpers/className"; diff --git a/src/pages/about-us/index.tsx b/src/pages/about-us/index.tsx index 98fd454..a43fdd9 100644 --- a/src/pages/about-us/index.tsx +++ b/src/pages/about-us/index.tsx @@ -3,7 +3,7 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { Icon } from "components/Ico"; import { NavOption } from "components/PanelComponents/NavOption"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; -import { SubPanel } from "components/Panels/SubPanel"; +import { SubPanel } from "components/Containers/SubPanel"; import { getOpenGraph } from "helpers/openGraph"; import { HorizontalLine } from "components/HorizontalLine"; import { getLangui } from "graphql/fetchLocalData"; diff --git a/src/pages/archives/index.tsx b/src/pages/archives/index.tsx index 4d57db5..005fa18 100644 --- a/src/pages/archives/index.tsx +++ b/src/pages/archives/index.tsx @@ -3,7 +3,7 @@ import { useMemo } from "react"; import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { NavOption } from "components/PanelComponents/NavOption"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; -import { SubPanel } from "components/Panels/SubPanel"; +import { SubPanel } from "components/Containers/SubPanel"; import { Icon } from "components/Ico"; import { getOpenGraph } from "helpers/openGraph"; import { HorizontalLine } from "components/HorizontalLine"; diff --git a/src/pages/archives/videos/c/[uid].tsx b/src/pages/archives/videos/c/[uid].tsx index 381d13a..8ffe8b4 100644 --- a/src/pages/archives/videos/c/[uid].tsx +++ b/src/pages/archives/videos/c/[uid].tsx @@ -5,8 +5,8 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { Switch } from "components/Inputs/Switch"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { ReturnButton } from "components/PanelComponents/ReturnButton"; -import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel"; -import { SubPanel } from "components/Panels/SubPanel"; +import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel"; +import { SubPanel } from "components/Containers/SubPanel"; import { PreviewCard } from "components/PreviewCard"; import { GetVideoChannelQuery } from "graphql/generated"; import { getReadySdk } from "graphql/sdk"; diff --git a/src/pages/archives/videos/index.tsx b/src/pages/archives/videos/index.tsx index 9bb2860..936a9bf 100644 --- a/src/pages/archives/videos/index.tsx +++ b/src/pages/archives/videos/index.tsx @@ -9,8 +9,8 @@ import { TextInput } from "components/Inputs/TextInput"; import { WithLabel } from "components/Inputs/WithLabel"; import { PanelHeader } from "components/PanelComponents/PanelHeader"; import { ReturnButton } from "components/PanelComponents/ReturnButton"; -import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel"; -import { SubPanel } from "components/Panels/SubPanel"; +import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel"; +import { SubPanel } from "components/Containers/SubPanel"; import { PreviewCard } from "components/PreviewCard"; import { GetVideosPreviewQuery } from "graphql/generated"; import { getReadySdk } from "graphql/sdk"; diff --git a/src/pages/archives/videos/v/[uid].tsx b/src/pages/archives/videos/v/[uid].tsx index db12a9a..095086a 100644 --- a/src/pages/archives/videos/v/[uid].tsx +++ b/src/pages/archives/videos/v/[uid].tsx @@ -5,11 +5,11 @@ import { AppLayout, AppLayoutRequired } from "components/AppLayout"; import { HorizontalLine } from "components/HorizontalLine"; import { Ico, Icon } from "components/Ico"; import { Button } from "components/Inputs/Button"; -import { InsetBox } from "components/InsetBox"; +import { InsetBox } from "components/Containers/InsetBox"; import { NavOption } from "components/PanelComponents/NavOption"; import { ReturnButton } from "components/PanelComponents/ReturnButton"; -import { ContentPanel, ContentPanelWidthSizes } from "components/Panels/ContentPanel"; -import { SubPanel } from "components/Panels/SubPanel"; +import { ContentPanel, ContentPanelWidthSizes } from "components/Containers/ContentPanel"; +import { SubPanel } from "components/Containers/SubPanel"; import { useAppLayout } from "contexts/AppLayoutContext"; import { GetVideoQuery } from "graphql/generated"; import { getReadySdk } from "graphql/sdk"; @@ -83,7 +83,7 @@ const Video = ({ video, ...otherProps }: Props): JSX.Element => { />
-
+
{video.gone ? (