chore: Bumped most of the dependencies to the latest version (#229)

* Bumped Next.js to 15.
* Bumped React to 19.
* Bumped GraphQL to 16.
* Bumped styled-components to 6.
* Bumped Radix components to latest version.
* Migrated Framer Motion to new package.
* Migrated react-query to new package.
* Lots of TypeScript fixes.
* Removed FontAwesome Pro icons.
* Started fading out SWR.
* Dev Server now uses Turbopack.
This commit is contained in:
Mani
2025-01-19 02:46:18 +01:00
committed by GitHub
parent 88238966fd
commit b0724e568a
105 changed files with 6600 additions and 15351 deletions
Vendored
+7
View File
@@ -0,0 +1,7 @@
import "react";
declare module "react" {
interface CSSProperties {
[key: `--${string}`]: string | number;
}
}
+5470 -14459
View File
File diff suppressed because it is too large Load Diff
+52 -43
View File
@@ -3,7 +3,7 @@
"version": "3.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev --turbopack",
"build": "next build",
"start": "next start",
"predev": "npm run compile-config",
@@ -13,49 +13,52 @@
"lint": "next lint",
"type-check": "tsc",
"graphql-codegen": "graphql-codegen --config codegen.ts",
"prepare": "husky"
"prepare": "husky",
"postinstall": "patch-package"
},
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-brands-svg-icons": "^6.5.2",
"@fortawesome/pro-solid-svg-icons": "^6.5.2",
"@graphql-tools/merge": "^8.2.1",
"@graphql-tools/schema": "^8.2.0",
"@graphql-tools/utils": "^8.6.5",
"@next/bundle-analyzer": "^12.0.3",
"@radix-ui/react-dialog": "^1.0.2",
"@radix-ui/react-dropdown-menu": "^2.0.4",
"@radix-ui/react-select": "^1.2.0",
"@radix-ui/react-slider": "^1.1.1",
"@radix-ui/react-switch": "^1.0.1",
"ahooks": "^3.7.6",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@graphql-tools/merge": "^9.0.16",
"@graphql-tools/schema": "^10.0.15",
"@graphql-tools/utils": "^10.7.1",
"@next/bundle-analyzer": "^15.1.3",
"@radix-ui/react-compose-refs": "^1.1.1",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-slider": "^1.2.2",
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-switch": "^1.1.2",
"@tanstack/react-query": "^5.62.15",
"ahooks": "^3.8.4",
"axios": "^1.2.2",
"common-tags": "^1.8.0",
"framer-motion": "^11.2.4",
"graphql": "^15.8.0",
"graphql-parse-resolve-info": "^4.12.0",
"graphql": "^16.10.0",
"graphql-parse-resolve-info": "^4.13.0",
"graphql-tag": "^2.12.6",
"hast-util-has-property": "^2.0.1",
"hast-util-heading-rank": "^2.1.1",
"hast-util-to-string": "^2.0.0",
"hast-util-has-property": "^3.0.0",
"hast-util-heading-rank": "^3.0.0",
"hast-util-to-string": "^3.0.1",
"lodash-es": "^4.17.21",
"md5": "^2.3.0",
"next": "^13.3.0",
"next-mdx-remote": "^4.3.0",
"motion": "^11.15.0",
"next": "^15.1.4",
"next-mdx-remote": "^5.0.0",
"p-limit": "^3.1.0",
"picocolors": "^1.0.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-query": "^3.29.0",
"rehype-pretty-code": "^0.9.4",
"rehype-slug": "^5.1.0",
"remark-gfm": "^3.0.1",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"rehype-pretty-code": "^0.14.0",
"rehype-slug": "^6.0.0",
"remark-gfm": "^4.0.0",
"sass": "^1.43.4",
"shiki": "^0.14.1",
"styled-components": "^5.3.3",
"swr": "^2.2.4",
"unist-util-visit": "^4.1.2",
"use-local-storage-state": "^19.3.1"
"shiki": "^1.26.2",
"styled-components": "^6.1.14",
"swr": "^2.3.0",
"unist-util-visit": "^5.0.0",
"use-local-storage-state": "^19.5.0"
},
"devDependencies": {
"@graphql-codegen/cli": "^5.0.2",
@@ -64,19 +67,25 @@
"@graphql-codegen/typescript-resolvers": "^4.1.0",
"@types/common-tags": "^1.8.1",
"@types/lodash-es": "^4.17.6",
"@types/md5": "^2.3.2",
"@types/node": "^17.0.41",
"@types/react": "^18.0.12",
"@types/styled-components": "^5.1.25",
"@typescript-eslint/eslint-plugin": "^6.21.0",
"esbuild": "^0.21.3",
"@types/md5": "^2.3.5",
"@types/node": "^22.10.5",
"@types/react": "^19.0.2",
"@typescript-eslint/eslint-plugin": "^8.19.0",
"esbuild": "^0.24.2",
"eslint": "^8.57.0",
"eslint-config-next": "^13.5.6",
"eslint-config-next": "^15.1.3",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-simple-import-sort": "^12.1.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"husky": "^9.0.11",
"prettier": "^3.2.5",
"ts-node": "^10.8.1",
"typescript": "^5.4.5"
"patch-package": "^8.0.0",
"prettier": "^3.4.2",
"ts-node": "^10.9.2",
"typescript": "^5.7.2"
},
"overrides": {
"ahooks": {
"react": "$react",
"react-dom": "$react-dom"
}
}
}
@@ -0,0 +1,26 @@
diff --git a/node_modules/graphql-parse-resolve-info/build-turbo/index.js b/node_modules/graphql-parse-resolve-info/build-turbo/index.js
index b10297b..6a56538 100644
--- a/node_modules/graphql-parse-resolve-info/build-turbo/index.js
+++ b/node_modules/graphql-parse-resolve-info/build-turbo/index.js
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.getAlias = exports.simplify = exports.parse = exports.simplifyParsedResolveInfoFragmentWithType = exports.parseResolveInfo = exports.getAliasFromResolveInfo = void 0;
const assert = require("assert");
const graphql_1 = require("graphql");
-const values_1 = require("graphql/execution/values");
+const values_1 = require("graphql");
const debugFactory = require("debug");
const debug = debugFactory("graphql-parse-resolve-info");
const DEBUG_ENABLED = debug.enabled;
diff --git a/node_modules/graphql-parse-resolve-info/node8plus/index.js b/node_modules/graphql-parse-resolve-info/node8plus/index.js
index 4c3d55d..0e2feb3 100644
--- a/node_modules/graphql-parse-resolve-info/node8plus/index.js
+++ b/node_modules/graphql-parse-resolve-info/node8plus/index.js
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
exports.getAlias = exports.simplify = exports.parse = exports.simplifyParsedResolveInfoFragmentWithType = exports.parseResolveInfo = exports.getAliasFromResolveInfo = void 0;
const assert = require("assert");
const graphql_1 = require("graphql");
-const values_1 = require("graphql/execution/values");
+const values_1 = require("graphql");
const debugFactory = require("debug");
const debug = debugFactory("graphql-parse-resolve-info");
const DEBUG_ENABLED = debug.enabled;
+5 -5
View File
@@ -2,8 +2,8 @@ import { memo, useCallback, useRef, useState } from "react";
import type { RefObject } from "react";
import styled from "styled-components";
import { faDiagramProject } from "@fortawesome/pro-solid-svg-icons";
import { m } from "framer-motion";
import { faDiagramProject } from "@fortawesome/free-solid-svg-icons";
import { m } from "motion/react";
import { BracketThemeSummaryCard } from "@/components/bracket/BracketThemeSummaryCard";
import { Button } from "@/components/button/Button";
@@ -66,11 +66,11 @@ const StyledBracketThemeSummaryCard = styled(BracketThemeSummaryCard)`
width: 384px;
`;
interface BracketChartProps extends RequiredNonNullable<BracketPageQuery> {}
type BracketChartProps = RequiredNonNullable<BracketPageQuery>;
export function BracketChart({ bracket }: BracketChartProps) {
const [showBracketChart, setShowBracketChart] = useState(false);
const bracketRef = useRef<HTMLDivElement>();
const bracketRef = useRef<HTMLDivElement>(undefined);
const onBracketInit = useCallback((bracket: HTMLDivElement) => {
bracketRef.current = bracket;
@@ -146,7 +146,7 @@ export function BracketChart({ bracket }: BracketChartProps) {
</Button>
{showBracketChart ? (
<StyledBracketContainer ref={onBracketInit}>
<StyledBracket drag dragConstraints={bracketRef as RefObject<HTMLDivElement>}>
<StyledBracket drag dragConstraints={bracketRef as RefObject<HTMLDivElement | null>}>
<StyledCanvas ref={onCanvasInit} />
{bracket.rounds
.sort((a, b) => a.tier - b.tier)
@@ -1,7 +1,7 @@
import type { ComponentPropsWithoutRef, ReactNode } from "react";
import styled from "styled-components";
import { faAward, faSeedling, faUsers } from "@fortawesome/pro-solid-svg-icons";
import { faAward, faSeedling, faUsers } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { Column } from "@/components/box/Flex";
+3 -3
View File
@@ -1,8 +1,8 @@
import React, { useContext, useEffect, useState } from "react";
import styled from "styled-components";
import { faChevronUp } from "@fortawesome/pro-solid-svg-icons";
import { AnimatePresence, m } from "framer-motion";
import { faChevronUp } from "@fortawesome/free-solid-svg-icons";
import { AnimatePresence, m } from "motion/react";
import { Button } from "@/components/button/Button";
import { Icon } from "@/components/icon/Icon";
@@ -55,7 +55,7 @@ export function BackToTopButton() {
variant="primary"
isCircle={true}
onClick={scrollUp}
onMouseDown={(event: React.PointerEvent) => event.preventDefault()}
onMouseDown={(event) => event.preventDefault()}
$bottomOffset={currentWatchListItem ? 76 : 0}
>
<Icon icon={faChevronUp} />
+28 -12
View File
@@ -1,24 +1,28 @@
import { forwardRef } from "react";
import type { ComponentPropsWithoutRef, ForwardedRef, ReactNode } from "react";
import { type ComponentPropsWithRef } from "react";
import styled from "styled-components";
import { Solid } from "@/components/box/Solid";
import { NestableSlot } from "@/components/utils/NestableSlot";
import { withHover } from "@/styles/mixins";
import theme from "@/theme";
export const Button = forwardRef(ButtonWithRef);
interface ButtonProps extends ComponentPropsWithoutRef<typeof BaseButton> {
children?: ReactNode;
interface ButtonProps extends ComponentPropsWithRef<typeof BaseButton> {
asChild?: boolean;
variant?: "solid" | "primary" | "warning" | "silent";
isCircle?: boolean;
disabled?: boolean;
}
function ButtonWithRef(
{ variant = "solid", isCircle = false, disabled = false, title, ...props }: ButtonProps,
ref: ForwardedRef<HTMLButtonElement>,
) {
export function Button({
ref,
asChild,
children,
variant = "solid",
isCircle = false,
disabled = false,
title,
...props
}: ButtonProps) {
let Component;
if (variant === "solid") {
Component = SolidButton;
@@ -32,10 +36,22 @@ function ButtonWithRef(
throw new Error(`Unknown button variant "${variant}"!`);
}
return <Component $isCircle={isCircle} disabled={disabled} title={title} aria-label={title} ref={ref} {...props} />;
return (
<Component
ref={ref}
as={asChild ? NestableSlot : "button"}
$isCircle={isCircle}
disabled={disabled}
title={title}
aria-label={title}
{...props}
>
{children}
</Component>
);
}
const BaseButton = styled.button<{ $isCircle: boolean }>`
const BaseButton = styled.button<{ $isCircle?: boolean }>`
--gap: 0;
--focus-ring-color: ${theme.colors["text-primary"]};
+1 -1
View File
@@ -1,6 +1,6 @@
import type { ComponentPropsWithoutRef } from "react";
import { faFilter } from "@fortawesome/pro-solid-svg-icons";
import { faFilter } from "@fortawesome/free-solid-svg-icons";
import { IconTextButton } from "@/components/button/IconTextButton";
+16 -12
View File
@@ -1,11 +1,11 @@
import { forwardRef } from "react";
import type { ComponentPropsWithoutRef, ReactNode } from "react";
import type { ComponentPropsWithoutRef, ReactNode, RefObject } from "react";
import styled from "styled-components";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { Button } from "@/components/button/Button";
import { Icon } from "@/components/icon/Icon";
import { NestableSlottable } from "@/components/utils/NestableSlot";
import theme from "@/theme";
const StyledButton = styled(Button)<{ $collapseBreakpoint: string }>`
@@ -27,16 +27,14 @@ const StyledText = styled.span<{ $collapseBreakpoint: string }>`
}
`;
interface IconTextButtonProps extends ComponentPropsWithoutRef<typeof StyledButton> {
interface IconTextButtonProps extends Omit<ComponentPropsWithoutRef<typeof StyledButton>, "$collapseBreakpoint"> {
ref?: RefObject<HTMLButtonElement>;
icon: IconDefinition | ReactNode;
children?: ReactNode;
collapsible?: true | keyof typeof theme.breakpoints;
}
export const IconTextButton = forwardRef(function IconTextButton(
{ icon, children, collapsible, ...props }: IconTextButtonProps,
ref,
) {
export function IconTextButton({ ref, icon, children, collapsible, ...props }: IconTextButtonProps) {
let collapseBreakpoint = "0px";
if (collapsible === true) {
@@ -47,13 +45,19 @@ export const IconTextButton = forwardRef(function IconTextButton(
return (
<StyledButton ref={ref} variant="silent" $collapseBreakpoint={collapseBreakpoint} {...props}>
{isIconDefinition(icon) ? <Icon icon={icon} color="text-disabled" /> : icon}
{children !== null && children !== undefined ? (
<StyledText $collapseBreakpoint={collapseBreakpoint}>{children}</StyledText>
) : null}
<NestableSlottable child={children}>
{(child) => (
<>
{isIconDefinition(icon) ? <Icon icon={icon} color="text-disabled" /> : icon}
{child !== null && child !== undefined ? (
<StyledText $collapseBreakpoint={collapseBreakpoint}>{child}</StyledText>
) : null}
</>
)}
</NestableSlottable>
</StyledButton>
);
});
}
function isIconDefinition(icon: IconDefinition | ReactNode): icon is IconDefinition {
return !!icon && typeof icon === "object" && "icon" in icon;
+10 -8
View File
@@ -2,7 +2,7 @@ import { useContext } from "react";
import type { ComponentPropsWithoutRef } from "react";
import Link from "next/link";
import { faCompactDisc, faPlay } from "@fortawesome/pro-solid-svg-icons";
import { faCompactDisc, faPlay } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { Button } from "@/components/button/Button";
@@ -32,14 +32,16 @@ export function VideoButton({ anime, theme, entry, video, ...props }: VideoButto
: false;
return (
<Link href={`/anime/${anime.slug}/${videoSlug}`} passHref legacyBehavior>
<Button as="a" {...props}>
<Button as="span" variant="primary" isCircle>
<Icon icon={isPlaying ? faCompactDisc : faPlay} className={isPlaying ? "fa-spin" : undefined} />
<Button asChild {...props}>
<Link href={`/anime/${anime.slug}/${videoSlug}`}>
<Button asChild variant="primary" isCircle>
<span>
<Icon icon={isPlaying ? faCompactDisc : faPlay} className={isPlaying ? "fa-spin" : undefined} />
</span>
</Button>
<VideoTags video={video} hideTextOnMobile />
</Button>
</Link>
<VideoTags video={video} />
</Link>
</Button>
);
}
+2 -2
View File
@@ -1,7 +1,7 @@
import { Fragment } from "react";
import { Fragment, type MouseEvent } from "react";
import styled from "styled-components";
import { faChevronDown } from "@fortawesome/pro-solid-svg-icons";
import { faChevronDown } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { uniqBy } from "lodash-es";
+4 -4
View File
@@ -6,8 +6,8 @@ import theme from "@/theme";
import type { Colors } from "@/theme/colors";
export const Card = styled(Solid)<{
hoverable?: boolean;
color?: keyof Colors;
$hoverable?: boolean;
$color?: keyof Colors;
}>`
display: block;
position: relative;
@@ -23,7 +23,7 @@ export const Card = styled(Solid)<{
}
${(props) =>
props.hoverable &&
props.$hoverable &&
css`
cursor: pointer;
@@ -40,6 +40,6 @@ export const Card = styled(Solid)<{
left: 0;
width: 4px;
height: 100%;
background-color: ${(props) => (props.color ? theme.colors[props.color] : theme.colors["text-primary"])};
background-color: ${(props) => (props.$color ? theme.colors[props.$color] : theme.colors["text-primary"])};
}
`;
+2 -2
View File
@@ -1,6 +1,6 @@
import styled from "styled-components";
import { faExclamation } from "@fortawesome/pro-solid-svg-icons";
import { faExclamation } from "@fortawesome/free-solid-svg-icons";
import { Row } from "@/components/box/Flex";
import { Card } from "@/components/card/Card";
@@ -26,7 +26,7 @@ interface ErrorCardProps {
export function ErrorCard({ error }: ErrorCardProps) {
return (
<StyledCard color="text-warning">
<StyledCard $color="text-warning">
<Row style={{ "--gap": "1rem" }}>
<Text color="text-warning">
<Icon icon={faExclamation} />
+2 -2
View File
@@ -31,8 +31,8 @@ export function StudioSummaryCard({ studio }: StudioSummaryCardProps) {
to={`/studio/${studio.slug}`}
image={extractImages(studio).largeCover}
imageProps={{
objectFit: "contain",
backgroundColor,
$objectFit: "contain",
$backgroundColor: backgroundColor,
onLoad: handleLoad,
}}
/>
+14 -14
View File
@@ -26,24 +26,24 @@ const StyledSummaryCard = styled(Card)`
const StyledCover = styled.img.attrs({
loading: "lazy",
})<{
objectFit?: Property.ObjectFit;
backgroundColor?: Property.Background;
isLoading?: boolean;
isPlaceholder?: boolean;
$objectFit?: Property.ObjectFit;
$backgroundColor?: Property.Background;
$isLoading?: boolean;
$isPlaceholder?: boolean;
}>`
width: 48px;
height: 64px;
object-fit: ${(props) => props.objectFit ?? "cover"};
background: ${(props) => props.backgroundColor};
object-fit: ${(props) => props.$objectFit ?? "cover"};
background: ${(props) => props.$backgroundColor};
${(props) =>
props.isPlaceholder
props.$isPlaceholder
? css`
padding: 0.5rem;
object-fit: contain;
background-color: white;
`
: props.isLoading
: props.$isLoading
? loadingAnimation
: null}
`;
@@ -56,14 +56,14 @@ const StyledBody = styled(Column)`
word-break: break-all;
`;
type SummaryCardProps = ComponentPropsWithoutRef<typeof StyledSummaryCard> & {
interface SummaryCardProps extends Omit<ComponentPropsWithoutRef<typeof StyledSummaryCard>, "title"> {
title: string | ReactNode;
description?: string | ReactNode;
image?: string;
imageProps?: ComponentPropsWithoutRef<typeof StyledCover>;
to?: string;
children?: ReactNode;
};
}
export function SummaryCard({ title, description, image, imageProps, to, children, ...props }: SummaryCardProps) {
const [imageNotFound, setImageNotFound] = useState(false);
@@ -75,8 +75,8 @@ export function SummaryCard({ title, description, image, imageProps, to, childre
<StyledCover
alt="Cover"
src={(!imageNotFound && image) || withBasePath("/img/logo.svg")}
isLoading={imageLoading}
isPlaceholder={!image || imageNotFound}
$isLoading={imageLoading}
$isPlaceholder={!image || imageNotFound}
loading="lazy"
{...imageProps}
onLoad={(event) => {
@@ -85,11 +85,11 @@ export function SummaryCard({ title, description, image, imageProps, to, childre
imageProps.onLoad(event);
}
}}
onError={() => {
onError={(event) => {
setImageNotFound(true);
setImageLoading(false);
if (imageProps?.onError) {
imageProps.onError();
imageProps.onError(event);
}
}}
/>
+15 -15
View File
@@ -1,4 +1,4 @@
import { useState } from "react";
import { Fragment, useState } from "react";
import type { ComponentPropsWithoutRef, ReactNode } from "react";
import styled, { css } from "styled-components";
@@ -23,24 +23,24 @@ const StyledSummaryCard = styled(Card)`
const StyledCover = styled.img.attrs({
loading: "lazy",
})<{
objectFit?: Property.ObjectFit;
backgroundColor?: Property.Background;
isLoading?: boolean;
isPlaceholder?: boolean;
$objectFit?: Property.ObjectFit;
$backgroundColor?: Property.Background;
$isLoading?: boolean;
$isPlaceholder?: boolean;
}>`
width: 48px;
height: 64px;
object-fit: ${(props) => props.objectFit ?? "cover"};
background: ${(props) => props.backgroundColor};
object-fit: ${(props) => props.$objectFit ?? "cover"};
background: ${(props) => props.$backgroundColor};
${(props) =>
props.isPlaceholder
props.$isPlaceholder
? css`
padding: 0.5rem;
object-fit: contain;
background-color: white;
`
: props.isLoading
: props.$isLoading
? loadingAnimation
: null}
`;
@@ -87,10 +87,10 @@ SummaryCard.Description = function SummaryCardDescription({ children }: SummaryC
: children
.filter((child) => child)
.map((child, index, { length }) => (
<>
<Fragment key={index}>
{child}
{index < length - 1 && <span> &bull; </span>}
</>
</Fragment>
))}
</Text>
);
@@ -108,8 +108,8 @@ SummaryCard.Cover = function SummaryCardCover({ src, ...props }: SummaryCardCove
<StyledCover
alt="Cover"
src={(!imageNotFound && src) || withBasePath("/img/logo.svg")}
isLoading={imageLoading}
isPlaceholder={!src || imageNotFound}
$isLoading={imageLoading}
$isPlaceholder={!src || imageNotFound}
loading="lazy"
{...props}
onLoad={(event) => {
@@ -118,11 +118,11 @@ SummaryCard.Cover = function SummaryCardCover({ src, ...props }: SummaryCardCove
props.onLoad(event);
}
}}
onError={() => {
onError={(event) => {
setImageNotFound(true);
setImageLoading(false);
if (props?.onError) {
props.onError();
props.onError(event);
}
}}
/>
+10 -13
View File
@@ -1,8 +1,8 @@
import type { PropsWithChildren } from "react";
import type { MouseEvent, PropsWithChildren } from "react";
import styled from "styled-components";
import Link from "next/link";
import { faChevronDown } from "@fortawesome/pro-solid-svg-icons";
import { faChevronDown } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { Button } from "@/components/button/Button";
@@ -130,7 +130,7 @@ export function ThemeSummaryCard({
</Link>
<SummaryCard.Body>
<SummaryCard.Title>
<SongTitle song={theme.song} as={Link} href={href} onClick={() => onPlay?.()} />
<SongTitle song={theme.song} href={href} onClick={() => onPlay?.()} />
<Performances song={theme.song} artist={artist} />
</SummaryCard.Title>
<SummaryCard.Description>
@@ -178,20 +178,17 @@ export function ThemeSummaryCard({
?.filter((performance) => performance.artist.slug !== artist?.slug)
.sort((a, b) => a.artist.name.localeCompare(b.artist.name))
.map((performance) => (
<Link
<TableRow
key={performance.artist.slug}
as={Link}
href={`/artist/${performance.artist.slug}`}
passHref
legacyBehavior
>
<TableRow as="a">
<TableCell>
<Text color="text-primary" weight="600">
<TableCell>
<Text color="text-primary" weight="600">
{getDisplayedArtistName(performance)}
</Text>
</TableCell>
</TableRow>
</Link>
</Text>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
+15 -8
View File
@@ -1,8 +1,8 @@
import { type ForwardedRef, forwardRef, type ReactNode } from "react";
import { type ReactNode, type Ref } from "react";
import styled from "styled-components";
import Link from "next/link";
import { faPlay } from "@fortawesome/pro-solid-svg-icons";
import { faPlay } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { SummaryCard } from "@/components/card/SummaryCard2";
@@ -54,6 +54,7 @@ const StyledCoverOverlay = styled.div`
`;
interface VideoSummaryCardProps {
ref?: Ref<HTMLDivElement>;
video: VideoSummaryCardVideoFragment;
entry: VideoSummaryCardEntryFragment;
menu?: ReactNode;
@@ -62,10 +63,16 @@ interface VideoSummaryCardProps {
isPlaying?: boolean;
}
export const VideoSummaryCard = forwardRef(function VideoSummaryCard(
{ video, entry, menu, append, onPlay, isPlaying, ...props }: VideoSummaryCardProps,
ref: ForwardedRef<HTMLDivElement>,
) {
export function VideoSummaryCard({
ref,
video,
entry,
menu,
append,
onPlay,
isPlaying,
...props
}: VideoSummaryCardProps) {
const theme = entry.theme;
const anime = theme?.anime;
@@ -90,7 +97,7 @@ export const VideoSummaryCard = forwardRef(function VideoSummaryCard(
</StyledCoverLink>
<SummaryCard.Body>
<SummaryCard.Title>
<SongTitle song={theme.song} as={Link} href={href} onClick={onPlay} />
<SongTitle song={theme.song} href={href} onClick={onPlay} />
<Performances song={theme.song} />
</SummaryCard.Title>
<SummaryCard.Description>
@@ -108,7 +115,7 @@ export const VideoSummaryCard = forwardRef(function VideoSummaryCard(
</SummaryCard>
</StyledWrapper>
);
});
}
export const VideoSummaryCardFragmentVideo = gql`
${createVideoSlug.fragments.video}
+6 -12
View File
@@ -1,4 +1,4 @@
import { forwardRef } from "react";
import type { RefObject } from "react";
import styled, { keyframes } from "styled-components";
import * as RadixDialog from "@radix-ui/react-dialog";
@@ -47,37 +47,31 @@ const StyledDialogCard = styled(Card)`
display: none;
}
`;
const StyledHeader = styled.div`
const StyledHeader = styled(Text).attrs({ variant: "h2", as: RadixDialog.Title })`
margin: -24px -24px 24px -24px;
padding: 16px 24px;
background-color: ${theme.colors["background"]};
`;
interface DialogContentProps extends RadixDialog.DialogContentProps {
ref?: RefObject<HTMLDivElement>;
title?: string;
}
export const DialogContent = forwardRef<HTMLDivElement, DialogContentProps>(function DialogContent(
{ title, children, ...props },
ref,
) {
export function DialogContent({ ref, title, children, ...props }: DialogContentProps) {
return (
<RadixDialog.Portal>
<StyledOverlay>
<RadixDialog.Content asChild {...props} ref={ref}>
<StyledDialogCard>
{title ? (
<StyledHeader>
<Text variant="h2">{title}</Text>
</StyledHeader>
) : null}
{title ? <StyledHeader>{title}</StyledHeader> : null}
{children}
</StyledDialogCard>
</RadixDialog.Content>
</StyledOverlay>
</RadixDialog.Portal>
);
});
}
export const Dialog = RadixDialog.Root;
+1 -1
View File
@@ -2,7 +2,7 @@ import { useState } from "react";
import type { FormEvent, ReactNode } from "react";
import styled from "styled-components";
import { faPlus } from "@fortawesome/pro-solid-svg-icons";
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { isAxiosError } from "axios";
import { mutate } from "swr";
+1 -1
View File
@@ -2,7 +2,7 @@ import { useState } from "react";
import type { FormEvent, ReactNode } from "react";
import styled from "styled-components";
import { faPen } from "@fortawesome/pro-solid-svg-icons";
import { faPen } from "@fortawesome/free-solid-svg-icons";
import { isAxiosError } from "axios";
import gql from "graphql-tag";
import { mutate } from "swr";
@@ -1,7 +1,7 @@
import { useState } from "react";
import type { ReactNode } from "react";
import { faMinus } from "@fortawesome/pro-solid-svg-icons";
import { faMinus } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { mutate } from "swr";
@@ -1,7 +1,7 @@
import { useState } from "react";
import type { ReactNode } from "react";
import { faArrowDown, faMinus, faPlus } from "@fortawesome/pro-solid-svg-icons";
import { faArrowDown, faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import useSWR, { mutate } from "swr";
@@ -1,7 +1,7 @@
import { useState } from "react";
import type { ReactNode } from "react";
import { faMinus } from "@fortawesome/pro-solid-svg-icons";
import { faMinus } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { mutate } from "swr";
@@ -1,7 +1,7 @@
import styled, { keyframes } from "styled-components";
import Link from "next/link";
import { faArrowRight, faTrophy } from "@fortawesome/pro-solid-svg-icons";
import { faArrowRight, faTrophy } from "@fortawesome/free-solid-svg-icons";
import { Button } from "@/components/button/Button";
import { Icon } from "@/components/icon/Icon";
@@ -74,15 +74,15 @@ export function AnimeAwardsNowAvailable({ year }: AnimeAwardsNowAvailableProps)
return (
<EventButtonContainer>
<GradientBorder />
<Link href="/event/anime-awards" passHref legacyBehavior>
<EventButton forwardedAs="a">
<EventButton asChild>
<Link href="/event/anime-awards">
<EventIcon icon={faTrophy} />
<Text>
<Text color="text-primary">/r/anime Awards {year}</Text>: Results are now available!
</Text>
<Icon icon={faArrowRight} color="text-primary" />
</EventButton>
</Link>
</Link>
</EventButton>
</EventButtonContainer>
);
}
@@ -1,6 +1,6 @@
import type { ComponentPropsWithoutRef, ReactNode } from "react";
import { faChevronCircleRight } from "@fortawesome/pro-solid-svg-icons";
import { faChevronCircleRight } from "@fortawesome/free-solid-svg-icons";
import { Icon } from "@/components/icon/Icon";
import { Text } from "@/components/text/Text";
+1 -1
View File
@@ -54,7 +54,7 @@ function AnimeThemeFilterInternal({ themes }: AnimeThemeFilterProps) {
return (
<Column style={{ "--gap": "24px" }}>
{(groups.length > 1 || hasMultipleTypes) && (
<HorizontalScroll fixShadows>
<HorizontalScroll $fixShadows>
<Row style={{ "--gap": "16px" }}>
{groups.length > 1 && (
<Listbox
+9 -9
View File
@@ -1,4 +1,3 @@
import type { ComponentPropsWithoutRef } from "react";
import styled from "styled-components";
import Link from "next/link";
@@ -48,6 +47,15 @@ const StyledSocialButton = styled(Button).attrs({ variant: "silent", isCircle: t
font-size: 1.2rem;
`;
const FooterLink = styled(Text).attrs({ as: Link })``;
const FooterTextLink = styled(FooterLink).attrs({
link: true,
block: true,
color: "text-muted",
noWrap: true,
})``;
export function Footer() {
return (
<StyledFooter>
@@ -93,11 +101,3 @@ export function Footer() {
</StyledFooter>
);
}
function FooterLink(props: ComponentPropsWithoutRef<typeof Text>) {
return <Text as={Link} {...props} />;
}
function FooterTextLink(props: ComponentPropsWithoutRef<typeof FooterLink>) {
return <FooterLink link block color="text-muted" noWrap {...props} />;
}
+5 -5
View File
@@ -1,8 +1,8 @@
import type { ComponentPropsWithoutRef } from "react";
import type { ComponentPropsWithoutRef, ComponentPropsWithRef } from "react";
import styled from "styled-components";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { faTimes } from "@fortawesome/pro-solid-svg-icons";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { Button } from "@/components/button/Button";
import { Icon } from "@/components/icon/Icon";
@@ -41,12 +41,12 @@ const StyledResetButton = styled(Button).attrs({ variant: "silent", isCircle: tr
}
`;
interface InputProps extends ComponentPropsWithoutRef<typeof StyledSearchInput> {
interface InputProps extends Omit<ComponentPropsWithoutRef<typeof StyledSearchInput>, "onChange"> {
value: string;
onChange: (value: string) => void;
resettable?: boolean;
icon?: IconDefinition;
inputProps?: ComponentPropsWithoutRef<typeof StyledInput>;
inputProps?: ComponentPropsWithRef<typeof StyledInput>;
}
export function Input({ value, onChange, resettable = false, icon, inputProps = {}, ...props }: InputProps) {
@@ -61,7 +61,7 @@ export function Input({ value, onChange, resettable = false, icon, inputProps =
/>
{resettable && !!value && (
<StyledResetButton>
<Icon icon={faTimes} onClick={() => onChange && onChange("")} />
<Icon icon={faXmark} onClick={() => onChange && onChange("")} />
</StyledResetButton>
)}
</StyledSearchInput>
+47
View File
@@ -0,0 +1,47 @@
import { useQuery } from "@tanstack/react-query";
import gql from "graphql-tag";
import { range } from "lodash-es";
import { Column } from "@/components/box/Flex";
import {
VideoSummaryCard,
VideoSummaryCardFragmentEntry,
VideoSummaryCardFragmentVideo,
} from "@/components/card/VideoSummaryCard";
import { Skeleton } from "@/components/skeleton/Skeleton";
import type { HomePageMostViewedQuery } from "@/generated/graphql";
import { fetchDataClient } from "@/lib/client/index";
export function MostViewedVideos() {
const { data: mostViewed } = useQuery<HomePageMostViewedQuery["videoAll"] | Array<null>>({
queryKey: ["HomePageTrending"],
queryFn: async () => {
const { data } = await fetchDataClient<HomePageMostViewedQuery>(gql`
${VideoSummaryCardFragmentVideo}
${VideoSummaryCardFragmentEntry}
query HomePageMostViewed {
videoAll(orderBy: "views_count", orderDesc: true, limit: 10) {
...VideoSummaryCardVideo
entries {
...VideoSummaryCardEntry
}
}
}
`);
return data.videoAll;
},
placeholderData: range(10).map(() => null),
});
return (
<Column style={{ "--gap": "16px" }}>
{mostViewed?.map((video, index) => (
<Skeleton key={index} variant="summary-card" delay={index * 100}>
{video ? <VideoSummaryCard video={video} entry={video.entries[0]} /> : null}
</Skeleton>
))}
</Column>
);
}
@@ -0,0 +1,41 @@
import { useQuery } from "@tanstack/react-query";
import gql from "graphql-tag";
import { range } from "lodash-es";
import { Column } from "@/components/box/Flex";
import PlaylistSummaryCard from "@/components/card/PlaylistSummaryCard";
import { Skeleton } from "@/components/skeleton/Skeleton";
import type { HomePageRecentlyAddedPlaylistsQuery } from "@/generated/graphql";
import { fetchDataClient } from "@/lib/client/index";
export function RecentlyAddedPlaylists() {
const { data: recentlyAddedPlaylists } = useQuery<HomePageRecentlyAddedPlaylistsQuery["playlistAll"] | null[]>({
queryKey: ["HomePageRecentlyAddedPlaylists"],
queryFn: async () => {
const { data } = await fetchDataClient<HomePageRecentlyAddedPlaylistsQuery>(gql`
${PlaylistSummaryCard.fragments.playlist}
${PlaylistSummaryCard.fragments.showOwner}
query HomePageRecentlyAddedPlaylists {
playlistAll(orderBy: "created_at", orderDesc: true, limit: 10, onlyNonEmpty: true) {
...PlaylistSummaryCardPlaylist
...PlaylistSummaryCardShowOwner
}
}
`);
return data.playlistAll;
},
placeholderData: range(10).map(() => null),
});
return (
<Column style={{ "--gap": "16px" }}>
{recentlyAddedPlaylists?.map((playlist, index) => (
<Skeleton key={index} variant="summary-card" delay={index * 100}>
{playlist ? <PlaylistSummaryCard playlist={playlist} showOwner /> : null}
</Skeleton>
))}
</Column>
);
}
@@ -0,0 +1,47 @@
import { useQuery } from "@tanstack/react-query";
import gql from "graphql-tag";
import { range } from "lodash-es";
import { Column } from "@/components/box/Flex";
import {
VideoSummaryCard,
VideoSummaryCardFragmentEntry,
VideoSummaryCardFragmentVideo,
} from "@/components/card/VideoSummaryCard";
import { Skeleton } from "@/components/skeleton/Skeleton";
import type { HomePageRecentlyAddedQuery } from "@/generated/graphql";
import { fetchDataClient } from "@/lib/client";
export function RecentlyAddedVideos() {
const { data: recentlyAdded } = useQuery<HomePageRecentlyAddedQuery["videoAll"] | Array<null>>({
queryKey: ["HomePageRecentlyAdded"],
queryFn: async () => {
const { data } = await fetchDataClient<HomePageRecentlyAddedQuery>(gql`
${VideoSummaryCardFragmentVideo}
${VideoSummaryCardFragmentEntry}
query HomePageRecentlyAdded {
videoAll(orderBy: "id", orderDesc: true, limit: 10) {
...VideoSummaryCardVideo
entries {
...VideoSummaryCardEntry
}
}
}
`);
return data.videoAll;
},
placeholderData: range(10).map(() => null),
});
return (
<Column style={{ "--gap": "16px" }}>
{recentlyAdded?.map((video, index) => (
<Skeleton key={index} variant="summary-card" delay={index * 100}>
{video ? <VideoSummaryCard video={video} entry={video.entries[0]} /> : null}
</Skeleton>
))}
</Column>
);
}
+1 -1
View File
@@ -15,7 +15,7 @@ export function CoverImage({ resourceWithImages, ...props }: CoverImageProps) {
const { smallCover, largeCover } = extractImages(resourceWithImages);
return (
<AspectRatio ratio={2 / 3}>
<AspectRatio $ratio={2 / 3}>
<FullWidthImage
key={largeCover}
src={largeCover}
+1 -1
View File
@@ -133,7 +133,7 @@ export function MultiCoverImage({ resourcesWithImages, ...props }: MultiCoverIma
.slice(0, 4);
return (
<AspectRatio ratio={2 / 3}>
<AspectRatio $ratio={2 / 3}>
<StyledCoverContainer>
{images.length ? (
images.map(({ largeCover, smallCover, resource }) => (
+1 -1
View File
@@ -25,7 +25,7 @@ export function StudioCoverImage({ studio, ...props }: StudioCoverImageProps) {
const [imageNotFound, setImageNotFound] = useState(!largeCover);
return (
<AspectRatio ratio={2 / 3}>
<AspectRatio $ratio={2 / 3}>
{!imageNotFound ? (
<StyledImage
key={largeCover}
+3 -5
View File
@@ -47,11 +47,9 @@ export function AlphabeticalIndex<T extends AlphabeticalIndexItem>({ items, chil
<>
<StyledLetterList>
{itemsByFirstLetter.map(([firstLetter]) => (
<Link key={firstLetter} href={`#${firstLetter}`} passHref legacyBehavior>
<Text as="a" link>
{firstLetter.toUpperCase()}{" "}
</Text>
</Link>
<Text key={firstLetter} as={Link} href={`#${firstLetter}`} link>
{firstLetter.toUpperCase()}{" "}
</Text>
))}
</StyledLetterList>
{itemsByFirstLetter.map(([firstLetter, itemsWithFirstLetter]) => (
+18 -12
View File
@@ -1,7 +1,7 @@
import { forwardRef } from "react";
import type { RefObject } from "react";
import styled from "styled-components";
import { faCheck, faSort, faTimes } from "@fortawesome/pro-solid-svg-icons";
import { faCheck, faSort, faXmark } from "@fortawesome/free-solid-svg-icons";
import * as RadixSelect from "@radix-ui/react-select";
import { Button } from "@/components/button/Button";
@@ -82,6 +82,7 @@ const StyledListboxOption = styled(RadixSelect.Item)`
type ListboxProps = Omit<RadixSelect.SelectProps, "value" | "defaultValue" | "onValueChange"> &
(PropsNullable | PropsNotNullable) & {
ref?: RefObject<HTMLButtonElement>;
resettable?: boolean;
highlightNonDefault?: boolean;
};
@@ -100,10 +101,17 @@ interface PropsNotNullable {
defaultValue?: string;
}
export const Listbox = forwardRef<HTMLButtonElement, ListboxProps>(function Listbox(
{ value, onValueChange, defaultValue, nullable, resettable, highlightNonDefault, children, ...props },
export function Listbox({
ref,
) {
value,
onValueChange,
defaultValue,
nullable,
resettable,
highlightNonDefault,
children,
...props
}: ListboxProps) {
const radixValue = value === null ? NULL_VALUE : value;
const radixOnValueChange = (newValue: string) => {
if (nullable) {
@@ -130,7 +138,7 @@ export const Listbox = forwardRef<HTMLButtonElement, ListboxProps>(function List
onClick={() => radixOnValueChange(radixDefaultValue)}
onPointerDown={(event) => event.stopPropagation()}
>
<Icon icon={faTimes} />
<Icon icon={faXmark} />
</StyledListboxReset>
) : (
<Icon icon={faSort} />
@@ -149,16 +157,14 @@ export const Listbox = forwardRef<HTMLButtonElement, ListboxProps>(function List
</StyledListboxPopover>
</RadixSelect.Root>
);
});
}
export interface ListboxOptionProps extends Omit<RadixSelect.SelectItemProps, "value"> {
ref?: RefObject<HTMLDivElement>;
value: string | null;
}
export const ListboxOption = forwardRef<HTMLDivElement, ListboxOptionProps>(function ListboxOption(
{ value, children, ...props },
ref,
) {
export const ListboxOption = function ListboxOption({ ref, value, children, ...props }: ListboxOptionProps) {
const radixValue = value === null ? NULL_VALUE : value;
return (
@@ -169,4 +175,4 @@ export const ListboxOption = forwardRef<HTMLDivElement, ListboxOptionProps>(func
</RadixSelect.ItemIndicator>
</StyledListboxOption>
);
});
};
+1 -1
View File
@@ -1,7 +1,7 @@
import { useEffect, useState } from "react";
import styled from "styled-components";
import { m } from "framer-motion";
import { m } from "motion/react";
import { Text } from "@/components/text/Text";
import theme from "@/theme";
+8 -7
View File
@@ -1,4 +1,4 @@
import { forwardRef } from "react";
import type { RefObject } from "react";
import styled from "styled-components";
import type { DropdownMenuContentProps } from "@radix-ui/react-dropdown-menu";
@@ -26,10 +26,11 @@ const StyledMenuContent = styled(RadixMenu.Content)`
export const Menu = RadixMenu.Root;
export const MenuTrigger = RadixMenu.Trigger;
export const MenuContent = forwardRef<HTMLDivElement, DropdownMenuContentProps>(function MenuContent(
{ children, ...props },
forwardedRef,
) {
interface MenuContentProps extends DropdownMenuContentProps {
ref?: RefObject<HTMLDivElement>;
}
export function MenuContent({ ref, children, ...props }: MenuContentProps) {
return (
<RadixMenu.Portal>
<StyledMenuContent
@@ -38,13 +39,13 @@ export const MenuContent = forwardRef<HTMLDivElement, DropdownMenuContentProps>(
collisionPadding={8}
collisionBoundary={typeof document !== "undefined" ? document.body : []}
{...props}
ref={forwardedRef}
ref={ref}
>
{children}
</StyledMenuContent>
</RadixMenu.Portal>
);
});
}
export const MenuItem = styled(RadixMenu.Item)`
display: flex;
+1 -1
View File
@@ -1,6 +1,6 @@
import type { ReactNode } from "react";
import { faShare } from "@fortawesome/pro-solid-svg-icons";
import { faShare } from "@fortawesome/free-solid-svg-icons";
import { IconTextButton } from "@/components/button/IconTextButton";
import { Menu, MenuContent, MenuItem, MenuTrigger } from "@/components/menu/Menu";
+4 -4
View File
@@ -1,6 +1,6 @@
import { useContext } from "react";
import { faArrowTurnDownRight, faArrowTurnRight, faEllipsisV, faPlus } from "@fortawesome/pro-solid-svg-icons";
import { faArrowTurnDown, faArrowTurnUp, faEllipsisVertical, faPlus } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { Button } from "@/components/button/Button";
@@ -38,7 +38,7 @@ export function ThemeMenu({ theme }: ThemeMenuProps) {
<Menu modal={false}>
<MenuTrigger asChild>
<Button variant="silent" isCircle>
<Icon icon={faEllipsisV} />
<Icon icon={faEllipsisVertical} />
</Button>
</MenuTrigger>
<MenuContent>
@@ -56,11 +56,11 @@ export function ThemeMenu({ theme }: ThemeMenuProps) {
<>
<MenuSeparator />
<MenuItem onSelect={() => addWatchListItem(video, entryFlipped)}>
<Icon icon={faArrowTurnDownRight} color="text-disabled" />
<Icon icon={faArrowTurnDown} color="text-disabled" />
<Text>Add to Watch List</Text>
</MenuItem>
<MenuItem onSelect={() => addWatchListItemNext(video, entryFlipped)}>
<Icon icon={faArrowTurnRight} color="text-disabled" />
<Icon icon={faArrowTurnUp} color="text-disabled" />
<Text>Play Next</Text>
</MenuItem>
</>
+42 -51
View File
@@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { faRandom, faSearch, faTv, faUser } from "@fortawesome/pro-solid-svg-icons";
import { faMagnifyingGlass, faShuffle, faTv, faUser } from "@fortawesome/free-solid-svg-icons";
import { IconTextButton } from "@/components/button/IconTextButton";
import { ShuffleDialog } from "@/components/dialog/ShuffleDialog";
@@ -49,64 +49,55 @@ export function Navigation() {
<>
<StyledNavigation $floating={isFloating}>
<StyledNavigationContainer onClick={(event) => event.stopPropagation()}>
<Link href="/" passHref legacyBehavior>
<StyledLogoContainer>
<StyledLogo width="277" height="150" />
</StyledLogoContainer>
</Link>
<StyledLogoContainer as={Link} href="/">
<StyledLogo width="277" height="150" />
</StyledLogoContainer>
<StyledNavigationLinks>
<Link href="/search" passHref legacyBehavior>
<IconTextButton
forwardedAs="a"
icon={faSearch}
variant="silent"
collapsible
style={{ "--gap": "8px" }}
>
Search
</IconTextButton>
</Link>
<IconTextButton
asChild
icon={faMagnifyingGlass}
variant="silent"
collapsible
style={{ "--gap": "8px" }}
>
<Link href="/search">Search</Link>
</IconTextButton>
<ShuffleDialog
trigger={
<IconTextButton variant="silent" icon={faRandom} collapsible style={{ "--gap": "8px" }}>
<IconTextButton
variant="silent"
icon={faShuffle}
collapsible
style={{ "--gap": "8px" }}
>
Shuffle
</IconTextButton>
}
/>
<Link
href={currentYear && currentSeason ? `/year/${currentYear}/${currentSeason}` : "/"}
passHref
legacyBehavior
>
<IconTextButton
forwardedAs="a"
variant="silent"
icon={faTv}
collapsible
style={{ "--gap": "8px" }}
>
<IconTextButton asChild variant="silent" icon={faTv} collapsible style={{ "--gap": "8px" }}>
<Link href={currentYear && currentSeason ? `/year/${currentYear}/${currentSeason}` : "/"}>
Current Season
</IconTextButton>
</Link>
<Link href="/profile" passHref legacyBehavior>
<IconTextButton
forwardedAs="a"
variant="silent"
icon={
me.user ? (
<StyledProfileImageIcon>
<StyledProfileImage user={me.user} />
</StyledProfileImageIcon>
) : (
faUser
)
}
title="My Profile"
collapsible
>
My Profile
</IconTextButton>
</Link>
</Link>
</IconTextButton>
<IconTextButton
asChild
variant="silent"
icon={
me.user ? (
<StyledProfileImageIcon>
<StyledProfileImage user={me.user} />
</StyledProfileImageIcon>
) : (
faUser
)
}
title="My Profile"
collapsible
>
<Link href="/profile">My Profile</Link>
</IconTextButton>
</StyledNavigationLinks>
</StyledNavigationContainer>
</StyledNavigation>
+31 -47
View File
@@ -3,7 +3,7 @@ import styled from "styled-components";
import Link from "next/link";
import { useRouter } from "next/router";
import { faSearch } from "@fortawesome/pro-solid-svg-icons";
import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
import { debounce } from "lodash-es";
import { Input } from "@/components/form/Input";
@@ -60,7 +60,7 @@ export function SearchNavigation() {
updateSearchQuery(router, newInputSearchQuery);
};
const inputRef = useRef<HTMLInputElement>();
const inputRef = useRef<HTMLInputElement>(undefined);
const onMountInput = useCallback((input: HTMLInputElement) => {
// Only focus the input on desktop devices
@@ -111,58 +111,42 @@ export function SearchNavigation() {
placeholder: "Search",
}}
resettable
icon={faSearch}
icon={faMagnifyingGlass}
/>
<HorizontalScroll fixShadows>
<HorizontalScroll $fixShadows>
<Switcher selectedItem={(entity as string) || null}>
<SwitcherReset as={Link} prefetch={false} href={{ pathname: "/search", query }} />
<SwitcherOption
as={Link}
prefetch={false}
href={{ pathname: "/search/anime", query }}
value="anime"
>
Anime
<SwitcherReset asChild>
<Link href={{ pathname: "/search", query }} prefetch={false} />
</SwitcherReset>
<SwitcherOption asChild value="anime">
<Link href={{ pathname: "/search/anime", query }} prefetch={false}>
Anime
</Link>
</SwitcherOption>
<SwitcherOption
as={Link}
prefetch={false}
href={{ pathname: "/search/theme", query }}
value="theme"
>
Theme
<SwitcherOption asChild value="theme">
<Link href={{ pathname: "/search/theme", query }} prefetch={false}>
Theme
</Link>
</SwitcherOption>
<SwitcherOption
as={Link}
prefetch={false}
href={{ pathname: "/search/artist", query }}
value="artist"
>
Artist
<SwitcherOption asChild value="artist">
<Link href={{ pathname: "/search/artist", query }} prefetch={false}>
Artist
</Link>
</SwitcherOption>
<SwitcherOption
as={Link}
prefetch={false}
href={{ pathname: "/search/series", query }}
value="series"
>
Series
<SwitcherOption asChild value="series">
<Link href={{ pathname: "/search/series", query }} prefetch={false}>
Series
</Link>
</SwitcherOption>
<SwitcherOption
as={Link}
prefetch={false}
href={{ pathname: "/search/studio", query }}
value="studio"
>
Studio
<SwitcherOption asChild value="studio">
<Link href={{ pathname: "/search/studio", query }} prefetch={false}>
Studio
</Link>
</SwitcherOption>
<SwitcherOption
as={Link}
prefetch={false}
href={{ pathname: "/search/playlist", query }}
value="playlist"
>
Playlist
<SwitcherOption asChild value="playlist">
<Link href={{ pathname: "/search/playlist", query }} prefetch={false}>
Playlist
</Link>
</SwitcherOption>
</Switcher>
</HorizontalScroll>
@@ -11,16 +11,11 @@ export function SeasonNavigation(props: YearDetailPageProps | SeasonDetailPagePr
return (
<Row style={{ "--justify-content": "center" }}>
<HorizontalScroll fixShadows>
<HorizontalScroll $fixShadows>
<Switcher selectedItem={"season" in props ? props.season.value.toLowerCase() : null}>
{year.seasons.map((season) => (
<SwitcherOption
key={season.value}
href={`/year/${year.value}/${season.value.toLowerCase()}`}
as={Link}
value={season.value.toLowerCase()}
>
{season.value}
<SwitcherOption key={season.value} asChild value={season.value.toLowerCase()}>
<Link href={`/year/${year.value}/${season.value.toLowerCase()}`}>{season.value}</Link>
</SwitcherOption>
))}
</Switcher>
+10 -14
View File
@@ -30,25 +30,21 @@ export function YearNavigation({ year, yearAll }: YearDetailPageProps) {
<Row style={{ "--align-items": "center" }}>
<StyledYearPrevious>
{previousYear && (
<Link href={`/year/${previousYear}`} passHref legacyBehavior>
<Button as="a" variant="silent">
{previousYear}
</Button>
</Link>
<Button asChild variant="silent">
<Link href={`/year/${previousYear}`}>{previousYear}</Link>
</Button>
)}
</StyledYearPrevious>
<Link href={`/year`} passHref legacyBehavior>
<Button as="a" variant="silent">
<Button asChild variant="silent">
<Link href={`/year`}>
<Text variant="h1">{year.value}</Text>
</Button>
</Link>
</Link>
</Button>
<StyledYearNext>
{nextYear && (
<Link href={`/year/${nextYear}`} passHref legacyBehavior>
<Button as="a" variant="silent">
{nextYear}
</Button>
</Link>
<Button asChild variant="silent">
<Link href={`/year/${nextYear}`}>{nextYear}</Link>
</Button>
)}
</StyledYearNext>
</Row>
+1 -1
View File
@@ -1,6 +1,6 @@
import type { ReactNode } from "react";
import { faChevronDown, faSpinner } from "@fortawesome/pro-solid-svg-icons";
import { faChevronDown, faSpinner } from "@fortawesome/free-solid-svg-icons";
import { Column, Row } from "@/components/box/Flex";
import { Button } from "@/components/button/Button";
+10 -8
View File
@@ -2,9 +2,9 @@ import type { ReactNode } from "react";
import Link from "next/link";
import { useRouter } from "next/router";
import { faChevronDown } from "@fortawesome/pro-solid-svg-icons";
import { faChevronDown } from "@fortawesome/free-solid-svg-icons";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import gql from "graphql-tag";
import { useQuery } from "react-query";
import { Column, Row } from "@/components/box/Flex";
import { Button } from "@/components/button/Button";
@@ -66,8 +66,10 @@ export function SearchGlobal({ searchQuery }: SearchGlobalProps) {
{ args: { query: searchQuery ?? null } },
);
const { data, error, isLoading, isError } = useQuery(["searchGlobal", searchQuery], fetchSearchResults, {
keepPreviousData: true,
const { data, error, isLoading, isError } = useQuery({
queryKey: ["searchGlobal", searchQuery],
queryFn: fetchSearchResults,
placeholderData: keepPreviousData,
});
if (isError) {
@@ -183,11 +185,11 @@ function GlobalSearchSection<T>({ entity, title, results, renderSummaryCard }: G
<Column style={{ "--gap": "16px" }}>{resultsPreview.map(renderSummaryCard)}</Column>
{hasMoreResults && (
<Row style={{ "--justify-content": "center" }}>
<Link href={{ pathname: `/search/${entity}`, query: urlParams }} passHref legacyBehavior>
<Button as="a" variant="silent" isCircle title="See all results">
<Button asChild variant="silent" isCircle title="See all results">
<Link href={{ pathname: `/search/${entity}`, query: urlParams }}>
<Icon icon={faChevronDown} />
</Button>
</Link>
</Link>
</Button>
</Row>
)}
</>
+7 -3
View File
@@ -1,4 +1,4 @@
import { forwardRef } from "react";
import type { RefObject } from "react";
import styled from "styled-components";
import * as RadixSlider from "@radix-ui/react-slider";
@@ -46,7 +46,11 @@ const StyledThumb = styled(RadixSlider.Thumb)`
}
`;
export const Slider = forwardRef<HTMLSpanElement, RadixSlider.SliderProps>(function Slider(props, ref) {
interface SliderProps extends RadixSlider.SliderProps {
ref?: RefObject<HTMLSpanElement>;
}
export function Slider({ ref, ...props }: SliderProps) {
const value = (props.value || props.defaultValue) ?? [];
return (
@@ -59,4 +63,4 @@ export const Slider = forwardRef<HTMLSpanElement, RadixSlider.SliderProps>(funct
))}
</StyledSlider>
);
});
}
+37 -19
View File
@@ -2,12 +2,14 @@ import { createContext, useContext, useMemo } from "react";
import type { ComponentPropsWithoutRef, ComponentPropsWithRef, ReactNode } from "react";
import styled from "styled-components";
import { faTimes } from "@fortawesome/pro-solid-svg-icons";
import { LayoutGroup, m } from "framer-motion";
import { faXmark } from "@fortawesome/free-solid-svg-icons";
import { Slot, Slottable } from "@radix-ui/react-slot";
import { uniqueId as createUniqueId } from "lodash-es";
import { LayoutGroup, m } from "motion/react";
import { Solid } from "@/components/box/Solid";
import { Icon } from "@/components/icon/Icon";
import { NestableSlot, NestableSlottable } from "@/components/utils/NestableSlot";
import { withHover } from "@/styles/mixins";
import theme from "@/theme";
@@ -63,7 +65,7 @@ const StyledButton = styled.button<StyledButtonProps>`
transition: color 500ms;
${withHover`
${withHover<StyledButtonProps>`
color: ${(props) => (props.$isSelected ? theme.colors["text-on-primary"] : theme.colors["text"])};;
transition-duration: 250ms;
`}
@@ -83,7 +85,7 @@ const StyledTop = styled.span`
z-index: ${theme.zIndices.switcherText};
`;
type SwitcherProps<T extends string | null> = ComponentPropsWithoutRef<typeof StyledSwitcher> & {
type SwitcherProps<T extends string | null> = Omit<ComponentPropsWithoutRef<typeof StyledSwitcher>, "onChange"> & {
selectedItem: T;
onChange?: (value: T) => void;
children: ReactNode;
@@ -110,32 +112,46 @@ export function Switcher<T extends string | null>({ selectedItem, onChange, chil
}
interface SwitcherOptionProps extends ComponentPropsWithRef<typeof StyledButton> {
children: ReactNode;
value: string;
asChild?: boolean;
}
export function SwitcherOption({ children, value, ...props }: SwitcherOptionProps) {
export function SwitcherOption({ value, asChild, children, ...props }: SwitcherOptionProps) {
const context = useContext(SwitcherContext);
const isSelected = context.selectedItem === value;
return (
<StyledButton type="button" $isSelected={isSelected} onClick={() => context.select?.(value)} {...props}>
{isSelected && (
<StyledButtonBackground
layout
layoutId="button-bg"
layoutDependency={value}
transition={{ duration: 0.25 }}
/>
)}
<StyledTop>{children}</StyledTop>
<StyledButton
as={asChild ? NestableSlot : "button"}
type={asChild ? undefined : "button"}
$isSelected={isSelected}
onClick={() => context.select?.(value)}
{...props}
>
<NestableSlottable child={children}>
{(child) => (
<>
{isSelected && (
<StyledButtonBackground
layout
layoutId="button-bg"
layoutDependency={value}
transition={{ duration: 0.25 }}
/>
)}
<StyledTop>{child}</StyledTop>
</>
)}
</NestableSlottable>
</StyledButton>
);
}
interface SwitcherResetProps extends ComponentPropsWithRef<typeof StyledButton> {}
interface SwitcherResetProps extends ComponentPropsWithRef<typeof StyledButton> {
asChild?: boolean;
}
export function SwitcherReset(props: SwitcherResetProps) {
export function SwitcherReset({ asChild, children, ...props }: SwitcherResetProps) {
const context = useContext(SwitcherContext);
if (context.selectedItem === null) {
@@ -145,12 +161,14 @@ export function SwitcherReset(props: SwitcherResetProps) {
return (
<StyledButton
as={asChild ? Slot : "button"}
style={{ "--color": theme.colors["text-disabled"] }}
$isCircle
onClick={() => context.select?.(null)}
{...props}
>
<Icon icon={faTimes} />
<Icon icon={faXmark} />
<Slottable>{children}</Slottable>
</StyledButton>
);
}
+32 -34
View File
@@ -29,45 +29,43 @@ export function ThemeTable({ themes, onPlay }: ThemeTableProps) {
const anime = theme.anime as NonNullable<(typeof theme)["anime"]>;
const videoSlug = createVideoSlug(theme, entry, video);
return (
<Link
<TableRow
key={anime.slug + videoSlug}
as={Link}
href={`/anime/${anime.slug}/${videoSlug}`}
passHref
legacyBehavior
onClick={() => onPlay?.(theme.id, entryIndex, videoIndex)}
>
<TableRow as="a" onClick={() => onPlay?.(theme.id, entryIndex, videoIndex)}>
<TableCell style={{ "--span": entryIndex || videoIndex ? 2 : undefined }}>
{!videoIndex &&
((entry.version ?? 1) > 1 ? (
<Text variant="small" color="text-muted">
{theme.type}
{theme.sequence || null} v{entry.version}
</Text>
) : (
<Text variant="small">
{theme.type}
{theme.sequence || null}
</Text>
))}
<TableCell style={{ "--span": entryIndex || videoIndex ? 2 : undefined }}>
{!videoIndex &&
((entry.version ?? 1) > 1 ? (
<Text variant="small" color="text-muted">
{theme.type}
{theme.sequence || null} v{entry.version}
</Text>
) : (
<Text variant="small">
{theme.type}
{theme.sequence || null}
</Text>
))}
</TableCell>
{!entryIndex && !videoIndex && (
<TableCell>
<SongTitle song={theme.song} />
</TableCell>
{!entryIndex && !videoIndex && (
<TableCell>
<SongTitle song={theme.song} />
</TableCell>
)}
<TableCell>{!videoIndex && <EpisodeTag entry={entry} />}</TableCell>
<TableCell>
{!videoIndex && (
<Row $wrap style={{ "--gap": "8px", "--align-items": "baseline" }}>
<ContentWarningTags entry={entry} />
</Row>
)}
<TableCell>{!videoIndex && <EpisodeTag entry={entry} />}</TableCell>
<TableCell>
{!videoIndex && (
<Row $wrap style={{ "--gap": "8px", "--align-items": "baseline" }}>
<ContentWarningTags entry={entry} />
</Row>
)}
</TableCell>
<TableCell>
<VideoTags video={video} />
</TableCell>
</TableRow>
</Link>
</TableCell>
<TableCell>
<VideoTags video={video} />
</TableCell>
</TableRow>
);
}),
),
+33
View File
@@ -0,0 +1,33 @@
import type { HTMLAttributes, ReactNode } from "react";
import styled from "styled-components";
import { Text } from "@/components/text/Text";
import theme from "@/theme";
const StyledTag = styled.span`
display: flex;
padding: 2px 4px;
border-radius: 4px;
background-color: ${theme.colors["text-disabled"]};
color: ${theme.colors["solid-on-card"]};
`;
const StyledText = styled(Text)`
font-size: 0.7rem;
font-weight: 900;
line-height: 1;
text-transform: uppercase;
`;
interface BorderTagProps extends HTMLAttributes<HTMLSpanElement> {
children: ReactNode;
}
export function BorderTag({ children, ...props }: BorderTagProps) {
return (
<StyledTag {...props}>
<StyledText variant="small">{children}</StyledText>
</StyledTag>
);
}
+3 -3
View File
@@ -1,4 +1,4 @@
import { faExclamationCircle } from "@fortawesome/pro-solid-svg-icons";
import { faCircleExclamation } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { Icon } from "@/components/icon/Icon";
@@ -13,12 +13,12 @@ export function ContentWarningTags({ entry }: ContentWarningTagsProps) {
return (
<>
{entry.spoiler && (
<Tag icon={<Icon icon={faExclamationCircle} color="text-warning" />} textColor="text-warning-muted">
<Tag icon={<Icon icon={faCircleExclamation} color="text-warning" />} textColor="text-warning-muted">
SPOILER
</Tag>
)}
{entry.nsfw && (
<Tag icon={<Icon icon={faExclamationCircle} color="text-warning" />} textColor="text-warning-muted">
<Tag icon={<Icon icon={faCircleExclamation} color="text-warning" />} textColor="text-warning-muted">
NSFW
</Tag>
)}
+1 -1
View File
@@ -1,4 +1,4 @@
import { faFilm } from "@fortawesome/pro-solid-svg-icons";
import { faFilm } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { Tag } from "@/components/tag/Tag";
+10 -8
View File
@@ -1,4 +1,4 @@
import type { HTMLAttributes, ReactComponentElement, ReactElement, ReactNode } from "react";
import type { ComponentPropsWithoutRef, HTMLAttributes, ReactElement, ReactNode } from "react";
import styled, { css } from "styled-components";
import type { IconDefinition } from "@fortawesome/fontawesome-svg-core";
@@ -19,11 +19,13 @@ const StyledTag = styled.span`
}
`;
const StyledText = styled(Text)`
const StyledText = styled(Text)<{
$hideTextOnMobile: boolean;
}>`
letter-spacing: 0.05rem;
${(props) =>
props.hideTextOnMobile &&
props.$hideTextOnMobile &&
css`
@media (max-width: ${theme.breakpoints.mobileMax}) {
display: none;
@@ -31,8 +33,10 @@ const StyledText = styled(Text)`
`}
`;
type IconElement = ReactElement<ComponentPropsWithoutRef<typeof Icon>, typeof Icon>;
interface TagProps extends HTMLAttributes<HTMLSpanElement> {
icon?: ReactComponentElement<typeof Icon> | IconDefinition;
icon?: IconElement | IconDefinition;
children?: ReactNode;
hideTextOnMobile?: boolean;
textColor?: keyof Colors;
@@ -43,7 +47,7 @@ export function Tag({ icon, children, hideTextOnMobile = false, textColor, ...pr
<StyledTag {...props}>
{!!icon && (isIcon(icon) ? icon : <Icon icon={icon} color="text-disabled" />)}
{!!children && (
<StyledText variant="small" hideTextOnMobile={hideTextOnMobile} color={textColor}>
<StyledText variant="small" $hideTextOnMobile={hideTextOnMobile} color={textColor}>
{children}
</StyledText>
)}
@@ -51,8 +55,6 @@ export function Tag({ icon, children, hideTextOnMobile = false, textColor, ...pr
);
}
function isIcon(
value: ReactComponentElement<typeof Icon> | IconDefinition,
): value is ReactComponentElement<typeof Icon> {
function isIcon(value: IconElement | IconDefinition): value is IconElement {
return typeof value === "object" && "type" in value && (value as ReactElement).type === Icon;
}
+9 -23
View File
@@ -1,53 +1,39 @@
import styled from "styled-components";
import {
faAlignSlash,
faBringForward,
faCommentLines,
faCommentMusic,
faCompactDisc,
faEyes,
} from "@fortawesome/pro-solid-svg-icons";
import gql from "graphql-tag";
import { Row } from "@/components/box/Flex";
import { BorderTag } from "@/components/tag/BorderTag";
import { Tag } from "@/components/tag/Tag";
import type { VideoTagsVideoFragment } from "@/generated/graphql";
const StyledVideoTags = styled(Row)`
align-items: baseline;
align-items: center;
flex-wrap: wrap;
gap: 8px;
`;
interface VideoTagsProps {
video: VideoTagsVideoFragment;
hideTextOnMobile?: boolean;
}
export function VideoTags({ video, hideTextOnMobile = false }: VideoTagsProps) {
export function VideoTags({ video }: VideoTagsProps) {
return (
<StyledVideoTags>
<Tag title="Resolution">{video.resolution}p</Tag>
{video.nc && <Tag icon={faAlignSlash} title="No Credits" />}
{video.nc && <BorderTag title="No Credits">NC</BorderTag>}
{video.subbed && <Tag icon={faCommentLines} title="With Subtitles" />}
{video.subbed && <BorderTag title="With Subtitles">Subs</BorderTag>}
{video.lyrics && <Tag icon={faCommentMusic} title="With Lyrics" />}
{video.lyrics && <BorderTag title="With Lyrics">Lyrics</BorderTag>}
{video.uncen && <Tag icon={faEyes} title="Uncensored" />}
{video.uncen && <BorderTag title="Uncensored">Uncen</BorderTag>}
{!!video.source && (
<Tag icon={faCompactDisc} title="Source">
{video.source.toUpperCase()}
</Tag>
)}
{!!video.source && <BorderTag title="Source">{video.source.toUpperCase()}</BorderTag>}
{video.overlap !== "NONE" && (
<Tag icon={faBringForward} title="Overlap" hideTextOnMobile={hideTextOnMobile}>
{video.overlap.toUpperCase().replace("TRANSITION", "TRANS")}
</Tag>
<BorderTag title="Overlap">{video.overlap.toUpperCase().replace("TRANSITION", "TRANS")}</BorderTag>
)}
</StyledVideoTags>
);
+10 -10
View File
@@ -1,12 +1,11 @@
import type { ReactElement } from "react";
import styled, { css } from "styled-components";
import styled, { css, type ExecutionProps } from "styled-components";
import type { Property } from "csstype";
import theme from "@/theme";
import type { Colors } from "@/theme/colors";
interface TextProps {
interface TextProps extends ExecutionProps {
variant?: "h1" | "h2" | "h3" | "small" | "code";
link?: boolean;
maxLines?: number;
@@ -15,11 +14,15 @@ interface TextProps {
italics?: boolean;
weight?: Property.FontWeight;
color?: keyof Colors;
as?: string | ReactElement;
wrapAnywhere?: boolean;
}
export const Text = styled.span.attrs(getAttributes)<TextProps>`
export const Text = styled.span.attrs<TextProps>(getAttributes).withConfig({
shouldForwardProp: (prop) =>
!["variant", "link", "maxLines", "noWrap", "block", "italics", "weight", "color", "wrapAnywhere"].includes(
prop,
),
})`
// Reset margin for elements like <p>
margin: 0;
scroll-margin-top: 4rem;
@@ -115,12 +118,9 @@ export const Text = styled.span.attrs(getAttributes)<TextProps>`
`;
function getAttributes(props: TextProps) {
if (props.as) {
return props.as;
}
return {
as: getAs(props.variant),
...props,
as: props.as || getAs(props.variant),
};
}
+1 -1
View File
@@ -49,7 +49,7 @@ export function AnnouncementToast() {
}
return (
<Toast hoverable color="text-warning" onClick={() => closeToast("announcement")}>
<Toast $hoverable $color="text-warning" onClick={() => closeToast("announcement")}>
<StyledBody>
<StyledAnnouncements>
{announcements.map((announcement) => (
+8 -10
View File
@@ -12,15 +12,13 @@ interface PlaylistAddToastProps {
export function PlaylistAddToast({ theme }: PlaylistAddToastProps) {
return (
<Link href="/profile/playlist" passHref legacyBehavior>
<Toast as="a" hoverable>
<Row $wrap style={{ "--justify-content": "space-between", "--gap": "8px" }}>
<span>
<SongTitle song={theme.song} /> was added to the playlist!
</span>
<Text color="text-disabled">(Click to view playlist.)</Text>
</Row>
</Toast>
</Link>
<Toast as={Link} href="/profile/playlist" $hoverable>
<Row $wrap style={{ "--justify-content": "space-between", "--gap": "8px" }}>
<span>
<SongTitle song={theme.song} /> was added to the playlist!
</span>
<Text color="text-disabled">(Click to view playlist.)</Text>
</Row>
</Toast>
);
}
+9 -11
View File
@@ -15,17 +15,15 @@ interface PlaylistTrackAddToastProps {
export function PlaylistTrackAddToast({ playlist, entry }: PlaylistTrackAddToastProps) {
return (
<Link href={`/playlist/${playlist.id}`} passHref legacyBehavior>
<Toast as="a" hoverable>
<Row $wrap style={{ "--justify-content": "space-between", "--gap": "8px" }}>
<span>
<SongTitle song={entry.theme?.song ?? null} /> was added to{" "}
<Text color="text-primary">{playlist.name}</Text>!
</span>
<Text color="text-disabled">(Click to view playlist.)</Text>
</Row>
</Toast>
</Link>
<Toast as={Link} href={`/playlist/${playlist.id}`} $hoverable>
<Row $wrap style={{ "--justify-content": "space-between", "--gap": "8px" }}>
<span>
<SongTitle song={entry.theme?.song ?? null} /> was added to{" "}
<Text color="text-primary">{playlist.name}</Text>!
</span>
<Text color="text-disabled">(Click to view playlist.)</Text>
</Row>
</Toast>
);
}
@@ -18,17 +18,15 @@ interface PlaylistTrackRemoveToastProps {
export function PlaylistTrackRemoveToast({ playlist, entry }: PlaylistTrackRemoveToastProps) {
return (
<Link href={`/playlist/${playlist.id}`} passHref legacyBehavior>
<Toast as="a" hoverable>
<Row $wrap style={{ "--justify-content": "space-between", "--gap": "8px" }}>
<span>
<SongTitle song={entry.theme?.song ?? null} /> was removed from{" "}
<Text color="text-primary">{playlist.name}</Text>!
</span>
<Text color="text-disabled">(Click to view playlist.)</Text>
</Row>
</Toast>
</Link>
<Toast as={Link} href={`/playlist/${playlist.id}`} $hoverable>
<Row $wrap style={{ "--justify-content": "space-between", "--gap": "8px" }}>
<span>
<SongTitle song={entry.theme?.song ?? null} /> was removed from{" "}
<Text color="text-primary">{playlist.name}</Text>!
</span>
<Text color="text-disabled">(Click to view playlist.)</Text>
</Row>
</Toast>
);
}
+1 -1
View File
@@ -1,6 +1,6 @@
import styled from "styled-components";
import { AnimatePresence, m } from "framer-motion";
import { AnimatePresence, m } from "motion/react";
import { Container } from "@/components/container/Container";
import { useToasts } from "@/context/toastContext";
+2 -2
View File
@@ -1,5 +1,5 @@
import styled from "styled-components";
export const AspectRatio = styled.div<{ ratio: number }>`
aspect-ratio: ${(props) => props.ratio};
export const AspectRatio = styled.div<{ $ratio: number }>`
aspect-ratio: ${(props) => props.$ratio};
`;
+2 -2
View File
@@ -1,7 +1,7 @@
import type { ReactNode } from "react";
import styled from "styled-components";
import { faSpinnerThird } from "@fortawesome/pro-solid-svg-icons";
import { faSpinner } from "@fortawesome/free-solid-svg-icons";
import { Icon } from "@/components/icon/Icon";
@@ -26,7 +26,7 @@ export function Busy({ isBusy, children, ...props }: BusyProps) {
return (
<StyledWrapper {...props}>
<span style={{ visibility: isBusy ? "hidden" : undefined }}>{children}</span>
{isBusy ? <StyledIcon icon={faSpinnerThird} className="fa-spin" /> : null}
{isBusy ? <StyledIcon icon={faSpinner} className="fa-spin" /> : null}
</StyledWrapper>
);
}
@@ -1,3 +1,4 @@
import type { JSX } from "react";
interface ConditionalWrapperProps {
condition: boolean;
wrap: (children: JSX.Element) => JSX.Element;
+3 -5
View File
@@ -38,11 +38,9 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
Discord
</Text>
<span>. For now, you may </span>
<Link href="/" passHref legacyBehavior>
<Text as="a" link>
go back to the home page
</Text>
</Link>
<Text as={Link} href="/" link>
go back to the home page
</Text>
<span>.</span>
</Text>
</Container>
+2 -2
View File
@@ -1,7 +1,7 @@
import styled, { css } from "styled-components";
export const HorizontalScroll = styled.div<{
fixShadows?: boolean;
$fixShadows?: boolean;
}>`
display: flex;
overflow: auto;
@@ -16,7 +16,7 @@ export const HorizontalScroll = styled.div<{
}
${(props) =>
!!props.fixShadows &&
!!props.$fixShadows &&
css`
margin-top: -16px;
margin-bottom: -16px;
+127
View File
@@ -0,0 +1,127 @@
import * as React from "react";
import type { JSX } from "react";
import { composeRefs } from "@radix-ui/react-compose-refs";
/**
* Code is taken from this pull request: https://github.com/radix-ui/primitives/pull/2197
*
* This can be removed once the pull request got merged.
*/
/* eslint-disable @typescript-eslint/no-explicit-any */
/* -------------------------------------------------------------------------------------------------
* Slot
* -----------------------------------------------------------------------------------------------*/
interface SlotProps extends React.HTMLAttributes<HTMLElement> {
children?: React.ReactNode;
}
const Slot = React.forwardRef<HTMLElement, SlotProps>((props, forwardedRef) => {
const { children, ...slotProps } = props;
if (isSlottable(children)) {
const slottable = children;
return (
<SlotClone {...slotProps} ref={forwardedRef}>
{React.isValidElement<React.PropsWithChildren<unknown>>(slottable.props.child)
? React.cloneElement(
slottable.props.child,
undefined,
slottable.props.children(slottable.props.child.props.children),
)
: null}
</SlotClone>
);
}
return (
<SlotClone {...slotProps} ref={forwardedRef}>
{children}
</SlotClone>
);
});
Slot.displayName = "Slot";
/* -------------------------------------------------------------------------------------------------
* SlotClone
* -----------------------------------------------------------------------------------------------*/
interface SlotCloneProps {
children: React.ReactNode;
}
const SlotClone = React.forwardRef<any, SlotCloneProps>((props, forwardedRef) => {
const { children, ...slotProps } = props;
if (React.isValidElement<React.RefAttributes<unknown>>(children)) {
return React.cloneElement(children, {
...mergeProps(slotProps, children.props),
ref: forwardedRef ? composeRefs(forwardedRef, (children as any).ref) : (children as any).ref,
});
}
return React.Children.count(children) > 1 ? React.Children.only(null) : null;
});
SlotClone.displayName = "SlotClone";
/* -------------------------------------------------------------------------------------------------
* Slottable
* -----------------------------------------------------------------------------------------------*/
type SlottableProps = {
child: React.ReactNode;
children: (child: React.ReactNode) => JSX.Element;
};
const Slottable = ({ child, children }: SlottableProps) => {
return children(child);
};
/* ---------------------------------------------------------------------------------------------- */
type AnyProps = Record<string, any>;
function isSlottable(child: React.ReactNode): child is React.ReactElement<SlottableProps> {
return React.isValidElement(child) && child.type === Slottable;
}
function mergeProps(slotProps: AnyProps, childProps: AnyProps) {
// all child props should override
const overrideProps = { ...childProps };
for (const propName in childProps) {
const slotPropValue = slotProps[propName];
const childPropValue = childProps[propName];
const isHandler = /^on[A-Z]/.test(propName);
if (isHandler) {
// if the handler exists on both, we compose them
if (slotPropValue && childPropValue) {
overrideProps[propName] = (...args: unknown[]) => {
childPropValue(...args);
slotPropValue(...args);
};
}
// but if it exists only on the slot, we use only this one
else if (slotPropValue) {
overrideProps[propName] = slotPropValue;
}
}
// if it's `style`, we merge them
else if (propName === "style") {
overrideProps[propName] = { ...slotPropValue, ...childPropValue };
} else if (propName === "className") {
overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
}
}
return { ...slotProps, ...overrideProps };
}
export { Slot as NestableSlot, Slottable as NestableSlottable };
export type { SlotProps };
+7 -3
View File
@@ -53,9 +53,13 @@ export function PageRevalidation({ lastBuildAt, apiRequests, ...props }: PageRev
}
const minutesSinceLastBuild = Math.round((Date.now() - lastBuildAt) / 60000);
const lastBuildDescription = minutesSinceLastBuild
? `${minutesSinceLastBuild} minute${minutesSinceLastBuild === 1 ? "" : "s"}`
: "a few seconds";
const lastBuildDescription = (
<span suppressHydrationWarning>
{minutesSinceLastBuild
? `${minutesSinceLastBuild} minute${minutesSinceLastBuild === 1 ? "" : "s"}`
: "a few seconds"}
</span>
);
const apiRequestsDescription = apiRequests
? ` using ${apiRequests} API request${apiRequests === 1 ? "" : "s"}`
+8 -12
View File
@@ -75,9 +75,9 @@ export function Performances({ song, artist, maxPerformances = 3, expandable = f
<Text variant="small" color="text-muted">
<span> as </span>
<span>
<Link href={`/artist/${performedAs.artist.slug}`} passHref legacyBehavior>
<StyledArtistLink>{performedAs.as}</StyledArtistLink>
</Link>
<StyledArtistLink as={Link} href={`/artist/${performedAs.artist.slug}`}>
{performedAs.as}
</StyledArtistLink>
</span>
</Text>
) : null}
@@ -87,11 +87,9 @@ export function Performances({ song, artist, maxPerformances = 3, expandable = f
<span>
{performedWith.map((performance) => (
<StyledArtist key={performance.artist.slug}>
<Link href={`/artist/${performance.artist.slug}`} passHref legacyBehavior>
<StyledArtistLink>
{getDisplayedArtistName(performance)}
</StyledArtistLink>
</Link>
<StyledArtistLink as={Link} href={`/artist/${performance.artist.slug}`}>
{getDisplayedArtistName(performance)}
</StyledArtistLink>
</StyledArtist>
))}
</span>
@@ -107,11 +105,9 @@ export function Performances({ song, artist, maxPerformances = 3, expandable = f
<Text>
{performancesShown.map((performance) => (
<StyledArtist key={performance.artist.slug}>
<Link href={`/artist/${performance.artist.slug}`} passHref legacyBehavior>
<Text as="a" link>
<Text as={Link} href={`/artist/${performance.artist.slug}`} link>
{getDisplayedArtistName(performance)}
</Text>
</Link>
</Text>
</StyledArtist>
))}
{!!performancesHidden.length && (
+3 -5
View File
@@ -16,11 +16,9 @@ export function SongTitle({ song, href, ...props }: SongTitleProps) {
if (href) {
return (
<Link href={href} passHref legacyBehavior>
<Text as="a" link title={songTitle} italics={!song?.title} wrapAnywhere {...props}>
{songTitle}
</Text>
</Link>
<Text as={Link} href={href} link title={songTitle} italics={!song?.title} wrapAnywhere {...props}>
{songTitle}
</Text>
);
}
@@ -1,6 +1,6 @@
import styled from "styled-components";
import { m } from "framer-motion";
import { m } from "motion/react";
import { Switcher } from "@/components/switcher/Switcher";
import theme from "@/theme";
+1 -1
View File
@@ -30,7 +30,7 @@ interface VideoPlayerContextValue {
background: boolean;
videoPagePath: string;
playerRef: RefObject<HTMLVideoElement | HTMLAudioElement | null>;
progressRef: RefObject<HTMLDivElement>;
progressRef: RefObject<HTMLDivElement | null>;
previousVideoPath: string | null;
playPreviousTrack(navigate: boolean): void;
nextVideoPath: string | null;
@@ -2,7 +2,7 @@ import { useContext } from "react";
import styled from "styled-components";
import Link from "next/link";
import { faBackwardStep, faForwardStep, faPause, faPlay, faPlus, faXmark } from "@fortawesome/pro-solid-svg-icons";
import { faBackwardStep, faForwardStep, faPause, faPlay, faPlus, faXmark } from "@fortawesome/free-solid-svg-icons";
import { Column } from "@/components/box/Flex";
import { Solid } from "@/components/box/Solid";
@@ -121,11 +121,9 @@ export function VideoPlayerBar() {
{theme.group && ` (${theme.group.name})`}
</Text>
<Text variant="small"> from </Text>
<Link href={`/anime/${anime.slug}`} passHref legacyBehavior>
<Text as="a" link>
{anime.name}
</Text>
</Link>
<Text as={Link} href={`/anime/${anime.slug}`} link>
{anime.name}
</Text>
</Text>
{!!theme.song?.performances?.length && (
<Text variant="small" color="text-muted" maxLines={1}>
@@ -2,7 +2,7 @@ import { Fragment, useContext } from "react";
import styled from "styled-components";
import Link from "next/link";
import { faCheck, faCog, faCompress, faExpand, faPlus, faShare } from "@fortawesome/pro-solid-svg-icons";
import { faCheck, faCompress, faExpand, faGear, faPlus, faShare } from "@fortawesome/free-solid-svg-icons";
import { Row } from "@/components/box/Flex";
import { IconTextButton } from "@/components/button/IconTextButton";
@@ -90,7 +90,7 @@ export function VideoPlayerOverlay({ anime, themeIndex, entryIndex, videoIndex }
/>
<Menu modal={false}>
<MenuTrigger asChild>
<StyledOverlayButton icon={faCog} isCircle title="Change media type" />
<StyledOverlayButton icon={faGear} isCircle title="Change media type" />
</MenuTrigger>
<MenuContent collisionPadding={16}>
<MenuLabel>
@@ -153,7 +153,7 @@ export function VideoPlayerOverlay({ anime, themeIndex, entryIndex, videoIndex }
: "transparent"
}
/>
<VideoTags video={otherVideo} hideTextOnMobile />
<VideoTags video={otherVideo} />
</Link>
</MenuItem>
))}
@@ -1,7 +1,7 @@
import type { ComponentPropsWithoutRef } from "react";
import styled from "styled-components";
import { faVolume, faVolumeHigh, faVolumeLow, faVolumeSlash } from "@fortawesome/pro-solid-svg-icons";
import { faVolumeHigh, faVolumeLow, faVolumeXmark } from "@fortawesome/free-solid-svg-icons";
import { Row } from "@/components/box/Flex";
import { IconTextButton } from "@/components/button/IconTextButton";
@@ -30,14 +30,12 @@ export function VolumeControl(props: ComponentPropsWithoutRef<typeof StyledRow>)
const [volume, setVolume] = useSetting(GlobalVolume);
let icon;
if (volume > 0.66) {
if (volume > 0.5) {
icon = faVolumeHigh;
} else if (volume > 0.33) {
icon = faVolume;
} else if (volume > 0) {
icon = faVolumeLow;
} else {
icon = faVolumeSlash;
icon = faVolumeXmark;
}
return (
+1 -1
View File
@@ -89,7 +89,7 @@ export default function VideoScript({ video }: Props) {
) : null}
</>
) : video.script ? (
<Text download variant="small" link color="text-disabled" onClick={downloadVideoScript}>
<Text variant="small" link color="text-disabled" onClick={downloadVideoScript}>
Click to download encoding script.
</Text>
) : (
+72 -72
View File
@@ -860,6 +860,78 @@ export type AnimeThemeFilterThemeFragment = {
}>;
};
export type HomePageMostViewedQueryVariables = Exact<{ [key: string]: never }>;
export type HomePageMostViewedQuery = {
videoAll: Array<{
id: number;
basename: string;
tags: string;
entries: Array<{
id: number;
version: number | null;
theme: {
id: number;
type: string;
sequence: number | null;
group: { name: string; slug: string } | null;
anime: { slug: string; name: string; images: Array<{ link: string; facet: string | null }> };
song: {
title: string | null;
performances: Array<{
alias: string | null;
as: string | null;
artist: { slug: string; name: string };
}>;
} | null;
};
}>;
audio: { basename: string };
}>;
};
export type HomePageRecentlyAddedPlaylistsQueryVariables = Exact<{ [key: string]: never }>;
export type HomePageRecentlyAddedPlaylistsQuery = {
playlistAll: Array<{
id: string;
name: string;
visibility: PlaylistVisibility;
tracks_count: number;
user: { name: string };
}>;
};
export type HomePageRecentlyAddedQueryVariables = Exact<{ [key: string]: never }>;
export type HomePageRecentlyAddedQuery = {
videoAll: Array<{
id: number;
basename: string;
tags: string;
entries: Array<{
id: number;
version: number | null;
theme: {
id: number;
type: string;
sequence: number | null;
group: { name: string; slug: string } | null;
anime: { slug: string; name: string; images: Array<{ link: string; facet: string | null }> };
song: {
title: string | null;
performances: Array<{
alias: string | null;
as: string | null;
artist: { slug: string; name: string };
}>;
} | null;
};
}>;
audio: { basename: string };
}>;
};
type CoverImageResourceWithImages_Anime_Fragment = { images: Array<{ link: string; facet: string | null }> };
type CoverImageResourceWithImages_Artist_Fragment = { images: Array<{ link: string; facet: string | null }> };
@@ -2374,78 +2446,6 @@ export type EventPageQueryVariables = Exact<{ [key: string]: never }>;
export type EventPageQuery = { bracketAll: Array<{ slug: string; name: string }> };
export type HomePageRecentlyAddedQueryVariables = Exact<{ [key: string]: never }>;
export type HomePageRecentlyAddedQuery = {
videoAll: Array<{
id: number;
basename: string;
tags: string;
entries: Array<{
id: number;
version: number | null;
theme: {
id: number;
type: string;
sequence: number | null;
group: { name: string; slug: string } | null;
anime: { slug: string; name: string; images: Array<{ link: string; facet: string | null }> };
song: {
title: string | null;
performances: Array<{
alias: string | null;
as: string | null;
artist: { slug: string; name: string };
}>;
} | null;
};
}>;
audio: { basename: string };
}>;
};
export type HomePageMostViewedQueryVariables = Exact<{ [key: string]: never }>;
export type HomePageMostViewedQuery = {
videoAll: Array<{
id: number;
basename: string;
tags: string;
entries: Array<{
id: number;
version: number | null;
theme: {
id: number;
type: string;
sequence: number | null;
group: { name: string; slug: string } | null;
anime: { slug: string; name: string; images: Array<{ link: string; facet: string | null }> };
song: {
title: string | null;
performances: Array<{
alias: string | null;
as: string | null;
artist: { slug: string; name: string };
}>;
} | null;
};
}>;
audio: { basename: string };
}>;
};
export type HomePageRecentlyAddedPlaylistsQueryVariables = Exact<{ [key: string]: never }>;
export type HomePageRecentlyAddedPlaylistsQuery = {
playlistAll: Array<{
id: string;
name: string;
visibility: PlaylistVisibility;
tracks_count: number;
user: { name: string };
}>;
};
export type HomePageQueryVariables = Exact<{ [key: string]: never }>;
export type HomePageQuery = {
+7 -4
View File
@@ -1,4 +1,4 @@
import { useInfiniteQuery } from "react-query";
import { keepPreviousData, useInfiniteQuery } from "@tanstack/react-query";
import type { SearchArgs } from "@/generated/graphql";
import type { SimpleSearchArgs } from "@/lib/client/search";
@@ -12,14 +12,17 @@ export default function useEntitySearch<T>(
}>,
searchArgs: SimpleSearchArgs,
) {
const fetchEntityPage = ({ pageParam = 1 }) =>
const fetchEntityPage = ({ pageParam }: { pageParam: number }) =>
fetchResults({
...toSearchArgs(searchArgs),
page: pageParam,
});
return useInfiniteQuery(["searchEntity", entity, searchArgs], fetchEntityPage, {
return useInfiniteQuery({
queryKey: ["searchEntity", entity, searchArgs],
queryFn: fetchEntityPage,
initialPageParam: 1,
getNextPageParam: (lastPage) => lastPage.nextPage,
keepPreviousData: true,
placeholderData: keepPreviousData,
});
}
+1 -1
View File
@@ -4,7 +4,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
export default function useMouseRelax() {
const [isRelaxed, setRelaxed] = useState(false);
const timeoutId = useRef<number>();
const timeoutId = useRef<number>(undefined);
// Whenever the user moves or presses their pointer inside the element,
// start a timer and cancel any previous timers. If the timer finishes without
+4 -2
View File
@@ -3,7 +3,7 @@ import type { GetStaticPaths, GetStaticProps } from "next";
import Link from "next/link";
import type { MDXRemoteSerializeResult } from "next-mdx-remote";
import { faArrowLeft } from "@fortawesome/pro-solid-svg-icons";
import { faArrowLeft } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import type { ParsedUrlQuery } from "querystring";
@@ -59,9 +59,11 @@ interface DocumentPageParams extends ParsedUrlQuery {
}
export default function DocumentPage({ page }: DocumentPageProps) {
const author = typeof page.source.frontmatter?.author === "string" ? page.source.frontmatter.author : undefined;
return (
<>
<PageHeader title={page.name} author={page.source.frontmatter?.author} createdAt={page.created_at} />
<PageHeader title={page.name} author={author} createdAt={page.created_at} />
<StyledGrid>
<SEO title={page.name} />
<Markdown source={page.source} />
+3 -3
View File
@@ -6,9 +6,9 @@ import Head from "next/head";
import { useRouter } from "next/router";
import { config } from "@fortawesome/fontawesome-svg-core";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useFullscreen } from "ahooks";
import { LazyMotion } from "framer-motion";
import { QueryClient, QueryClientProvider } from "react-query";
import { LazyMotion } from "motion/react";
import useLocalStorageState from "use-local-storage-state";
import { Column } from "@/components/box/Flex";
@@ -257,7 +257,7 @@ export default function MyApp({ Component, pageProps }: AppProps) {
)}
{pageProps.isSearch && <SearchNavigation />}
{STAGING ? (
<Card color="text-warning">
<Card $color="text-warning">
<Text as="p">
<strong>WARNING</strong>: This version of the website is for testing purposes
only. Some pages or functions might not work.
+5 -10
View File
@@ -1,7 +1,7 @@
import type { GetStaticProps } from "next";
import Link from "next/link";
import { faDownload } from "@fortawesome/pro-solid-svg-icons";
import { faDownload } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { Column } from "@/components/box/Flex";
@@ -52,15 +52,10 @@ export default function DumpIndexPage({ dumpAll }: DumpIndexPageProps) {
title={dump.path}
description={`Created at: ${new Date(dump.created_at).toLocaleString()}`}
>
<IconTextButton
variant="solid"
forwardedAs="a"
href={dump.link}
download
icon={faDownload}
collapsible
>
Download
<IconTextButton asChild variant="solid" icon={faDownload} collapsible>
<a href={dump.link} download>
Download
</a>
</IconTextButton>
</SummaryCard>
))}
@@ -1,7 +1,7 @@
import { useContext, useEffect, useRef, useState } from "react";
import type { GetStaticPaths, GetStaticProps } from "next";
import { faChevronDown, faChevronUp } from "@fortawesome/pro-solid-svg-icons";
import { faChevronDown, faChevronUp } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { Column, Row } from "@/components/box/Flex";
@@ -112,14 +112,31 @@ export default function VideoPage({
const videoHeight = video.resolution ?? 720;
const videoWidth = Math.floor((videoHeight / 9) * 16);
const currentVideoCard = useRef<HTMLDivElement>(null);
const videoCardMap = useRef<Map<number, HTMLDivElement>>(null);
function getVideoCardMap() {
if (!videoCardMap.current) {
videoCardMap.current = new Map();
}
return videoCardMap.current;
}
useEffect(() => {
if (currentWatchListItem === null) {
return;
}
let isCancelled = false;
setTimeout(() => {
if (!isCancelled) {
currentVideoCard.current?.scrollIntoView({ block: "start", behavior: "smooth" });
if (isCancelled) {
return;
}
const currentVideoCard = getVideoCardMap().get(currentWatchListItem.watchListId);
if (!currentVideoCard) {
return;
}
currentVideoCard.scrollIntoView({ block: "start", behavior: "smooth" });
}, 200);
return () => {
@@ -148,8 +165,12 @@ export default function VideoPage({
<meta name="twitter:player:width" content={String(videoWidth)} />
<meta name="twitter:player:height" content={String(videoHeight)} />
</SEO>
<HorizontalScroll fixShadows>
<StyledSwitcher selectedItem={selectedTab} onChange={setSelectedTab}>
<HorizontalScroll $fixShadows>
<StyledSwitcher
selectedItem={selectedTab}
// Explicit type cast, because StyledSwitcher breaks the generic type of Switcher
onChange={(tab) => setSelectedTab(tab as typeof selectedTab)}
>
<SwitcherOption value="watch-list">Next Up</SwitcherOption>
<SwitcherOption value="info">Info</SwitcherOption>
<SwitcherOption value="related">Related</SwitcherOption>
@@ -176,11 +197,14 @@ export default function VideoPage({
{watchList.map((watchListItem) => (
<VideoSummaryCard
key={watchListItem.watchListId}
ref={
watchListItem.watchListId === currentWatchListItem?.watchListId
? currentVideoCard
: undefined
}
ref={(element: HTMLDivElement) => {
const videoCardMap = getVideoCardMap();
videoCardMap.set(watchListItem.watchListId, element);
return () => {
videoCardMap.delete(watchListItem.watchListId);
};
}}
video={watchListItem.video}
entry={watchListItem.entry}
onPlay={() => setCurrentWatchListItem(watchListItem)}
+13 -19
View File
@@ -68,25 +68,21 @@ export default function AnimeDetailPage({ anime }: AnimeDetailPageProps) {
</DescriptionList.Item>
) : null}
<DescriptionList.Item title="Premiere">
<Link
<Text
as={Link}
href={`/year/${anime.year}${anime.season ? `/${anime.season.toLowerCase()}` : ""}`}
passHref
legacyBehavior
link
>
<Text as="a" link>
{(anime.season ? anime.season + " " : "") + anime.year}
</Text>
</Link>
{(anime.season ? anime.season + " " : "") + anime.year}
</Text>
</DescriptionList.Item>
{anime.series?.length ? (
<DescriptionList.Item title="Series">
<StyledList>
{anime.series.sort(seriesNameComparator).map((series) => (
<Link key={series.slug} href={`/series/${series.slug}`} passHref legacyBehavior>
<Text as="a" link>
{series.name}
</Text>
</Link>
<Text key={series.slug} as={Link} href={`/series/${series.slug}`} link>
{series.name}
</Text>
))}
</StyledList>
</DescriptionList.Item>
@@ -98,11 +94,9 @@ export default function AnimeDetailPage({ anime }: AnimeDetailPageProps) {
<DescriptionList.Item title="Studios">
<StyledList>
{anime.studios.sort(studioNameComparator).map((studio) => (
<Link key={studio.slug} href={`/studio/${studio.slug}`} passHref legacyBehavior>
<Text as="a" link>
{studio.name}
</Text>
</Link>
<Text key={studio.slug} as={Link} href={`/studio/${studio.slug}`} link>
{studio.name}
</Text>
))}
</StyledList>
</DescriptionList.Item>
@@ -127,11 +121,11 @@ export default function AnimeDetailPage({ anime }: AnimeDetailPageProps) {
{!!anime.synopsis && (
<>
<Text variant="h2">Synopsis</Text>
<Card hoverable onClick={() => setCollapseSynopsis(!collapseSynopsis)}>
<Card $hoverable onClick={() => setCollapseSynopsis(!collapseSynopsis)}>
<HeightTransition>
<Text
as="p"
maxLines={collapseSynopsis ? 2 : null}
maxLines={collapseSynopsis ? 2 : undefined}
dangerouslySetInnerHTML={{ __html: anime.synopsis }}
/>
</HeightTransition>
+3 -5
View File
@@ -21,11 +21,9 @@ export default function AnimeIndexPage({ animeAll }: AnimeIndexPageProps) {
<Text variant="h1">Anime Index</Text>
<AlphabeticalIndex items={animeAll}>
{(anime) => (
<Link key={anime.slug} href={`/anime/${anime.slug}`} passHref legacyBehavior prefetch={false}>
<Text as="a" block link>
{anime.name}
</Text>
</Link>
<Text key={anime.slug} as={Link} href={`/anime/${anime.slug}`} prefetch={false} block link>
{anime.name}
</Text>
)}
</AlphabeticalIndex>
</>
+11 -15
View File
@@ -76,7 +76,7 @@ function getPerformanceFilter(key: string | null): (performance: Performance) =>
}
}
interface ArtistDetailPageProps extends RequiredNonNullable<ArtistDetailPageQuery> {}
type ArtistDetailPageProps = RequiredNonNullable<ArtistDetailPageQuery>;
interface ArtistDetailPageParams extends ParsedUrlQuery {
artistSlug: string;
@@ -204,13 +204,11 @@ export default function ArtistDetailPage({ artist }: ArtistDetailPageProps) {
<DescriptionList.Item title="Members">
<StyledList>
{artist.members.map(({ member, alias, as }) => (
<Link key={member.slug} href={`/artist/${member.slug}`} passHref legacyBehavior>
<Text as="a" link>
{member.name}
{alias ? ` (as ${alias})` : null}
{as ? ` (as ${as})` : null}
</Text>
</Link>
<Text key={member.slug} as={Link} href={`/artist/${member.slug}`} link>
{member.name}
{alias ? ` (as ${alias})` : null}
{as ? ` (as ${as})` : null}
</Text>
))}
</StyledList>
</DescriptionList.Item>
@@ -219,13 +217,11 @@ export default function ArtistDetailPage({ artist }: ArtistDetailPageProps) {
<DescriptionList.Item title="Member of">
<StyledList>
{artist.groups.map(({ group, alias, as }) => (
<Link key={group.slug} href={`/artist/${group.slug}`} passHref legacyBehavior>
<Text as="a" link>
{group.name}
{alias ? ` (as ${alias})` : null}
{as ? ` (as ${as})` : null}
</Text>
</Link>
<Text key={group.slug} as={Link} href={`/artist/${group.slug}`} link>
{group.name}
{alias ? ` (as ${alias})` : null}
{as ? ` (as ${as})` : null}
</Text>
))}
</StyledList>
</DescriptionList.Item>
+3 -5
View File
@@ -21,11 +21,9 @@ export default function ArtistIndexPage({ artistAll }: ArtistIndexPageProps) {
<Text variant="h1">Artist Index</Text>
<AlphabeticalIndex items={artistAll}>
{(artist) => (
<Link key={artist.slug} href={`/artist/${artist.slug}`} passHref legacyBehavior prefetch={false}>
<Text as="a" block link>
{artist.name}
</Text>
</Link>
<Text key={artist.slug} as={Link} href={`/artist/${artist.slug}`} prefetch={false} block link>
{artist.name}
</Text>
)}
</AlphabeticalIndex>
</>
+1 -1
View File
@@ -2,7 +2,7 @@ import { useState } from "react";
import styled from "styled-components";
import type { GetStaticPaths, GetStaticProps } from "next";
import { faStopwatch } from "@fortawesome/pro-solid-svg-icons";
import { faStopwatch } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import type { ParsedUrlQuery } from "querystring";
+5 -7
View File
@@ -4,9 +4,9 @@ import styled from "styled-components";
import type { GetStaticProps } from "next";
import Link from "next/link";
import { faAward, faHashtag, faUsers } from "@fortawesome/pro-solid-svg-icons";
import { m } from "framer-motion";
import { faAward, faHashtag, faUsers } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { m } from "motion/react";
import { SummaryCard } from "@/components/card/SummaryCard";
import { CornerIcon } from "@/components/icon/CornerIcon";
@@ -267,11 +267,9 @@ function AwardThemeSummaryCard({ theme, rank, votes, ...props }: AwardThemeSumma
{theme.type + (theme.sequence || "")}
{entry.version && entry.version > 1 ? `v${entry.version}` : null}
</span>
<Link href={`/anime/${theme.anime.slug}`} passHref legacyBehavior>
<Text as="a" link>
{theme.anime.name}
</Text>
</Link>
<Text as={Link} href={`/anime/${theme.anime.slug}`} link>
{theme.anime.name}
</Text>
</SummaryCard.Description>
);
+9 -9
View File
@@ -1,7 +1,7 @@
import styled from "styled-components";
import Link from "next/link";
import { faArrowRight, faAward, faTrophy } from "@fortawesome/pro-solid-svg-icons";
import { faArrowRight, faAward, faTrophy } from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { Column } from "@/components/box/Flex";
@@ -55,25 +55,25 @@ export default function EventPage({ awardAll, bracketAll }: EventPageProps) {
<Column style={{ "--gap": "16px" }}>
<Text variant="h2">Awards</Text>
{awardAll.map(({ name, slug }) => (
<Link key={slug} href={`/event/${slug}`} passHref legacyBehavior>
<BigButton forwardedAs="a">
<BigButton key={slug} asChild>
<Link href={`/event/${slug}`}>
<BigIcon icon={faAward} />
<StyledEventName>{name}</StyledEventName>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
</Link>
</Link>
</BigButton>
))}
</Column>
<Column style={{ "--gap": "16px" }}>
<Text variant="h2">Brackets</Text>
{bracketAll.map(({ name, slug }) => (
<Link key={slug} href={`/event/${slug}`} passHref legacyBehavior>
<BigButton forwardedAs="a">
<BigButton key={slug} asChild>
<Link href={`/event/${slug}`}>
<BigIcon icon={faTrophy} />
<StyledEventName>{name}</StyledEventName>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
</Link>
</Link>
</BigButton>
))}
</Column>
</StyledEventList>
+60 -158
View File
@@ -6,43 +6,31 @@ import type { MDXRemoteSerializeResult } from "next-mdx-remote";
import {
faArrowRight,
faAward,
faBookOpenCover,
faMegaphone,
faRandom,
faSearch,
faBookOpen,
faBullhorn,
faMagnifyingGlass,
faShuffle,
faTv,
faUser,
} from "@fortawesome/pro-solid-svg-icons";
} from "@fortawesome/free-solid-svg-icons";
import gql from "graphql-tag";
import { range } from "lodash-es";
import useSWR from "swr";
import { Column } from "@/components/box/Flex";
import { Button } from "@/components/button/Button";
import { AnnouncementCard } from "@/components/card/AnnouncementCard";
import PlaylistSummaryCard from "@/components/card/PlaylistSummaryCard";
import {
VideoSummaryCard,
VideoSummaryCardFragmentEntry,
VideoSummaryCardFragmentVideo,
} from "@/components/card/VideoSummaryCard";
import { ShuffleDialog } from "@/components/dialog/ShuffleDialog";
import { ExternalLink } from "@/components/external-link/ExternalLink";
import { FeaturedTheme } from "@/components/featured-theme/FeaturedTheme";
import { MostViewedVideos } from "@/components/home/MostViewedVideos";
import { RecentlyAddedPlaylists } from "@/components/home/RecentlyAddedPlaylists";
import { RecentlyAddedVideos } from "@/components/home/RecentlyAddedVideos";
import { Icon } from "@/components/icon/Icon";
import { ProfileImage } from "@/components/image/ProfileImage";
import { SEO } from "@/components/seo/SEO";
import { Skeleton } from "@/components/skeleton/Skeleton";
import { Text } from "@/components/text/Text";
import type {
HomePageMostViewedQuery,
HomePageQuery,
HomePageRecentlyAddedPlaylistsQuery,
HomePageRecentlyAddedQuery,
} from "@/generated/graphql";
import type { HomePageQuery } from "@/generated/graphql";
import useAuth from "@/hooks/useAuth";
import useCurrentSeason from "@/hooks/useCurrentSeason";
import { fetchDataClient } from "@/lib/client";
import { fetchData } from "@/lib/server";
import theme from "@/theme";
import getSharedPageProps from "@/utils/getSharedPageProps";
@@ -87,70 +75,6 @@ export default function HomePage({ featuredTheme, announcementSources }: HomePag
const { me } = useAuth();
const { currentYear, currentSeason } = useCurrentSeason();
const { data: recentlyAdded } = useSWR<HomePageRecentlyAddedQuery["videoAll"] | null[]>(
["HomePageRecentlyAdded"],
async () => {
const { data } = await fetchDataClient<HomePageRecentlyAddedQuery>(gql`
${VideoSummaryCardFragmentVideo}
${VideoSummaryCardFragmentEntry}
query HomePageRecentlyAdded {
videoAll(orderBy: "id", orderDesc: true, limit: 10) {
...VideoSummaryCardVideo
entries {
...VideoSummaryCardEntry
}
}
}
`);
return data.videoAll;
},
{ fallbackData: range(10).map(() => null) },
);
const { data: mostViewed } = useSWR<HomePageMostViewedQuery["videoAll"] | null[]>(
["HomePageTrending"],
async () => {
const { data } = await fetchDataClient<HomePageMostViewedQuery>(gql`
${VideoSummaryCardFragmentVideo}
${VideoSummaryCardFragmentEntry}
query HomePageMostViewed {
videoAll(orderBy: "views_count", orderDesc: true, limit: 10) {
...VideoSummaryCardVideo
entries {
...VideoSummaryCardEntry
}
}
}
`);
return data.videoAll;
},
{ fallbackData: range(10).map(() => null) },
);
const { data: recentlyAddedPlaylists } = useSWR<HomePageRecentlyAddedPlaylistsQuery["playlistAll"] | null[]>(
["HomePageRecentlyAddedPlaylists"],
async () => {
const { data } = await fetchDataClient<HomePageRecentlyAddedPlaylistsQuery>(gql`
${PlaylistSummaryCard.fragments.playlist}
${PlaylistSummaryCard.fragments.showOwner}
query HomePageRecentlyAddedPlaylists {
playlistAll(orderBy: "created_at", orderDesc: true, limit: 10, onlyNonEmpty: true) {
...PlaylistSummaryCardPlaylist
...PlaylistSummaryCardShowOwner
}
}
`);
return data.playlistAll;
},
{ fallbackData: range(10).map(() => null) },
);
return (
<>
<SEO />
@@ -168,96 +92,74 @@ export default function HomePage({ featuredTheme, announcementSources }: HomePag
<Text variant="h2">Explore The Database</Text>
<Grid $columns={3}>
<Link href="/search" passHref legacyBehavior>
<BigButton forwardedAs="a">
<BigIcon icon={faSearch} className="fa-flip-horizontal" />
<BigButton asChild>
<Link href="/search">
<BigIcon icon={faMagnifyingGlass} className="fa-flip-horizontal" />
<Text>Search</Text>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
</Link>
</Link>
</BigButton>
<ShuffleDialog
trigger={
<BigButton>
<BigIcon icon={faRandom} />
<BigIcon icon={faShuffle} />
<Text>Shuffle</Text>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
}
/>
<Link
href={currentYear && currentSeason ? `/year/${currentYear}/${currentSeason}` : "/"}
passHref
legacyBehavior
>
<BigButton forwardedAs="a">
<BigButton asChild>
<Link href={currentYear && currentSeason ? `/year/${currentYear}/${currentSeason}` : "/"}>
<BigIcon icon={faTv} />
<Text>Current Season</Text>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
</Link>
</Link>
</BigButton>
</Grid>
<Grid $columns={4}>
<Link href="/event" passHref legacyBehavior>
<BigButton forwardedAs="a">
<BigButton asChild>
<Link href="/event">
<BigIcon icon={faAward} />
<Text>Events</Text>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
</Link>
<Link href="/wiki" passHref legacyBehavior>
<BigButton forwardedAs="a">
<BigIcon icon={faBookOpenCover} />
</Link>
</BigButton>
<BigButton asChild>
<Link href="/wiki">
<BigIcon icon={faBookOpen} />
<Text>Wiki</Text>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
</Link>
<Link href="/blog" passHref legacyBehavior>
<BigButton forwardedAs="a">
<BigIcon icon={faMegaphone} />
</Link>
</BigButton>
<BigButton asChild>
<Link href="/blog">
<BigIcon icon={faBullhorn} />
<Text>Blog</Text>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
</Link>
<Link href="/profile" passHref legacyBehavior>
<BigButton forwardedAs="a">
</Link>
</BigButton>
<BigButton asChild>
<Link href="/profile">
{me.user ? <BigProfileImage user={me.user} size={96} /> : <BigIcon icon={faUser} />}
<Text>My Profile</Text>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
</Link>
</Link>
</BigButton>
</Grid>
<Grid $columns={3}>
<Column style={{ "--gap": "24px" }}>
<Text variant="h2">Recently Added</Text>
<Column style={{ "--gap": "16px" }}>
{recentlyAdded?.map((video, index) => (
<Skeleton key={index} variant="summary-card" delay={index * 100}>
{video ? <VideoSummaryCard video={video} entry={video.entries[0]} /> : null}
</Skeleton>
))}
</Column>
<RecentlyAddedVideos />
</Column>
<Column style={{ "--gap": "24px" }}>
<Text variant="h2">Most Viewed</Text>
<Column style={{ "--gap": "16px" }}>
{mostViewed?.map((video, index) => (
<Skeleton key={index} variant="summary-card" delay={index * 100}>
{video ? <VideoSummaryCard video={video} entry={video.entries[0]} /> : null}
</Skeleton>
))}
</Column>
<MostViewedVideos />
</Column>
<Column style={{ "--gap": "24px" }}>
<Text variant="h2">New Playlists</Text>
<Column style={{ "--gap": "16px" }}>
{recentlyAddedPlaylists?.map((playlist, index) => (
<Skeleton key={index} variant="summary-card" delay={index * 100}>
{playlist ? <PlaylistSummaryCard playlist={playlist} showOwner /> : null}
</Skeleton>
))}
</Column>
<RecentlyAddedPlaylists />
</Column>
</Grid>
@@ -285,36 +187,36 @@ export default function HomePage({ featuredTheme, announcementSources }: HomePag
<Text variant="h2">Dive Deeper</Text>
<Grid $columns={5}>
<Link href="/anime" passHref legacyBehavior>
<BigButton forwardedAs="a" style={{ "--height": "48px" }}>
<BigButton asChild style={{ "--height": "48px" }}>
<Link href="/anime">
<Text>Anime Index</Text>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
</Link>
<Link href="/artist" passHref legacyBehavior>
<BigButton forwardedAs="a" style={{ "--height": "48px" }}>
</Link>
</BigButton>
<BigButton asChild style={{ "--height": "48px" }}>
<Link href="/artist">
<Text>Artist Index</Text>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
</Link>
<Link href="/year" passHref legacyBehavior>
<BigButton forwardedAs="a" style={{ "--height": "48px" }}>
</Link>
</BigButton>
<BigButton asChild style={{ "--height": "48px" }}>
<Link href="/year">
<Text>Year Index</Text>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
</Link>
<Link href="/series" passHref legacyBehavior>
<BigButton forwardedAs="a" style={{ "--height": "48px" }}>
</Link>
</BigButton>
<BigButton asChild style={{ "--height": "48px" }}>
<Link href="/series">
<Text>Series Index</Text>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
</Link>
<Link href="/studio" passHref legacyBehavior>
<BigButton forwardedAs="a" style={{ "--height": "48px" }}>
</Link>
</BigButton>
<BigButton asChild style={{ "--height": "48px" }}>
<Link href="/studio">
<Text>Studio Index</Text>
<Icon icon={faArrowRight} color="text-primary" />
</BigButton>
</Link>
</Link>
</BigButton>
</Grid>
</>
);
+10 -10
View File
@@ -4,10 +4,10 @@ import type { GetServerSideProps } from "next";
import { useRouter } from "next/router";
import {
faArrowTurnDownRight,
faArrowTurnRight,
faArrowTurnDown,
faArrowTurnUp,
faCheck,
faEllipsisV,
faEllipsisVertical,
faGripVertical,
faPen,
faPlus,
@@ -15,11 +15,11 @@ import {
faTrash,
faTrophy,
faXmark,
} from "@fortawesome/pro-solid-svg-icons";
} from "@fortawesome/free-solid-svg-icons";
import { isAxiosError } from "axios";
import { Reorder, useDragControls } from "framer-motion";
import gql from "graphql-tag";
import { shuffle } from "lodash-es";
import { Reorder, useDragControls } from "motion/react";
import type { ParsedUrlQuery } from "querystring";
import useSWR, { mutate } from "swr";
@@ -519,9 +519,9 @@ function Description({ playlist, description, setDescription, isEditable, setEdi
</Text>
</div>
) : (
<Card hoverable onClick={() => setCollapsed(!isCollapsed)}>
<Card $hoverable onClick={() => setCollapsed(!isCollapsed)}>
<HeightTransition>
<Text as="p" maxLines={isCollapsed ? 2 : null}>
<Text as="p" maxLines={isCollapsed ? 2 : undefined}>
{description}
</Text>
</HeightTransition>
@@ -627,7 +627,7 @@ function PlaylistTrack({ playlist, track, isOwner, isRanking, isDraggable, onPla
<Menu modal={false}>
<MenuTrigger asChild>
<Button variant="silent" isCircle>
<Icon icon={faEllipsisV} />
<Icon icon={faEllipsisVertical} />
</Button>
</MenuTrigger>
<MenuContent>
@@ -645,11 +645,11 @@ function PlaylistTrack({ playlist, track, isOwner, isRanking, isDraggable, onPla
<>
<MenuSeparator />
<MenuItem onSelect={() => addWatchListItem(track.video, track.entry)}>
<Icon icon={faArrowTurnDownRight} color="text-disabled" />
<Icon icon={faArrowTurnDown} color="text-disabled" />
<Text>Add to Watch List</Text>
</MenuItem>
<MenuItem onSelect={() => addWatchListItemNext(track.video, track.entry)}>
<Icon icon={faArrowTurnRight} color="text-disabled" />
<Icon icon={faArrowTurnUp} color="text-disabled" />
<Text>Play Next</Text>
</MenuItem>
</>
+10 -5
View File
@@ -2,7 +2,12 @@ import { memo, useState } from "react";
import styled, { css } from "styled-components";
import type { GetServerSideProps } from "next";
import { faEllipsisV, faExclamationCircle, faPersonToDoor, faTrash } from "@fortawesome/pro-solid-svg-icons";
import {
faCircleExclamation,
faEllipsisVertical,
faRightFromBracket,
faTrash,
} from "@fortawesome/free-solid-svg-icons";
import { isAxiosError } from "axios";
import gql from "graphql-tag";
import useSWR from "swr";
@@ -200,10 +205,10 @@ export default function ProfilePage({ me: initialMe }: ProfilePageProps) {
<LogoutButton />
</StyledHeaderTop>
{!me.user.email_verified_at ? (
<Card color="text-warning">
<Card $color="text-warning">
<Column style={{ "--gap": "8px" }}>
<Text color="text-warning" weight="bold">
<Icon icon={faExclamationCircle} /> Your email address is not verified!
<Icon icon={faCircleExclamation} /> Your email address is not verified!
</Text>
<Text>
You haven&apos;t verified your email address, yet. Please do so to unlock all
@@ -251,7 +256,7 @@ export default function ProfilePage({ me: initialMe }: ProfilePageProps) {
<Menu modal={false}>
<MenuTrigger asChild>
<Button variant="silent" isCircle>
<Icon icon={faEllipsisV} />
<Icon icon={faEllipsisVertical} />
</Button>
</MenuTrigger>
<MenuContent>
@@ -363,7 +368,7 @@ function LogoutButton() {
}
return (
<IconTextButton icon={faPersonToDoor} onClick={performLogout}>
<IconTextButton icon={faRightFromBracket} onClick={performLogout}>
<Busy isBusy={isBusy}>Logout</Busy>
</IconTextButton>
);
+4 -4
View File
@@ -1,8 +1,8 @@
import { useState } from "react";
import styled from "styled-components";
import { faExclamationCircle } from "@fortawesome/pro-solid-svg-icons";
import { Reorder } from "framer-motion";
import { faCircleExclamation } from "@fortawesome/free-solid-svg-icons";
import { Reorder } from "motion/react";
import { Column, Row } from "@/components/box/Flex";
import { Button } from "@/components/button/Button";
@@ -156,9 +156,9 @@ export default function PlaylistPage() {
)}
{refreshError !== null && <Text color="text-warning">{refreshError}</Text>}
</Row>
<Card color="text-warning">
<Card $color="text-warning">
<Text color="text-warning" weight="bold">
<Icon icon={faExclamationCircle} /> Read this before using the local playlist:
<Icon icon={faCircleExclamation} /> Read this before using the local playlist:
</Text>
<Text as="p">
The local playlist is only saved in your browser&apos;s storage. You can&apos;t share it
+2 -2
View File
@@ -1,6 +1,6 @@
import { useRouter } from "next/router";
import { faCompass, faSearch } from "@fortawesome/pro-solid-svg-icons";
import { faCompass, faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
import { Column, Row } from "@/components/box/Flex";
import { Icon } from "@/components/icon/Icon";
@@ -21,7 +21,7 @@ export default function SearchGlobalPage() {
) : (
<Column style={{ "--gap": "16px" }}>
<Row style={{ "--gap": "16px" }}>
<Icon icon={faSearch} color="text-primary" />
<Icon icon={faMagnifyingGlass} color="text-primary" />
<Text color="text-muted">Looking for something specific? Use the search bar on the left!</Text>
</Row>
<Row style={{ "--gap": "16px" }}>
+1 -1
View File
@@ -36,7 +36,7 @@ const StyledDesktopOnly = styled.div`
}
`;
interface SeriesDetailPageProps extends RequiredNonNullable<SeriesDetailPageQuery> {}
type SeriesDetailPageProps = RequiredNonNullable<SeriesDetailPageQuery>;
interface SeriesDetailPageParams extends ParsedUrlQuery {
seriesSlug: string;
+3 -5
View File
@@ -21,11 +21,9 @@ export default function SeriesIndexPage({ seriesAll }: SeriesIndexPageProps) {
<Text variant="h1">Series Index</Text>
<AlphabeticalIndex items={seriesAll}>
{(series) => (
<Link key={series.slug} href={`/series/${series.slug}`} passHref legacyBehavior prefetch={false}>
<Text as="a" block link>
{series.name}
</Text>
</Link>
<Text key={series.slug} as={Link} href={`/series/${series.slug}`} prefetch={false} block link>
{series.name}
</Text>
)}
</AlphabeticalIndex>
</>
+1 -1
View File
@@ -54,7 +54,7 @@ const StyledList = styled.div`
text-align: center;
`;
interface StudioDetailPageProps extends RequiredNonNullable<StudioDetailPageQuery> {}
type StudioDetailPageProps = RequiredNonNullable<StudioDetailPageQuery>;
interface StudioDetailPageParams extends ParsedUrlQuery {
studioSlug: string;
+3 -5
View File
@@ -21,11 +21,9 @@ export default function StudioIndexPage({ studioAll }: StudioIndexPageProps) {
<Text variant="h1">Studio Index</Text>
<AlphabeticalIndex items={studioAll}>
{(studio) => (
<Link key={studio.slug} href={`/studio/${studio.slug}`} passHref legacyBehavior prefetch={false}>
<Text as="a" block link>
{studio.name}
</Text>
</Link>
<Text key={studio.slug} as={Link} href={`/studio/${studio.slug}`} prefetch={false} block link>
{studio.name}
</Text>
)}
</AlphabeticalIndex>
</>

Some files were not shown because too many files have changed in this diff Show More