mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
refactor: website facelift (#10823)
This commit is contained in:
@@ -6,3 +6,4 @@ public/searchIndex
|
|||||||
src/assets/readme
|
src/assets/readme
|
||||||
src/styles/unocss.css
|
src/styles/unocss.css
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
src/util/shiki.bundle.ts
|
||||||
|
|||||||
2
apps/website/next-env.d.ts
vendored
2
apps/website/next-env.d.ts
vendored
@@ -2,4 +2,4 @@
|
|||||||
/// <reference types="next/image-types/global" />
|
/// <reference types="next/image-types/global" />
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
// NOTE: This file should not be edited
|
||||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||||
|
|||||||
@@ -1,13 +1,19 @@
|
|||||||
/**
|
import type { NextConfig } from 'next';
|
||||||
* @type {import('next').NextConfig}
|
|
||||||
*/
|
|
||||||
export default {
|
export default {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
images: {
|
images: {
|
||||||
dangerouslyAllowSVG: true,
|
dangerouslyAllowSVG: true,
|
||||||
contentDispositionType: 'attachment',
|
contentDispositionType: 'attachment',
|
||||||
contentSecurityPolicy: "default-src 'self'; frame-src 'none'; sandbox;",
|
contentSecurityPolicy: "default-src 'self'; frame-src 'none'; sandbox;",
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: 'http',
|
||||||
|
hostname: 'localhost',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
poweredByHeader: false,
|
||||||
logging: {
|
logging: {
|
||||||
fetches: {
|
fetches: {
|
||||||
fullUrl: true,
|
fullUrl: true,
|
||||||
@@ -16,6 +22,8 @@ export default {
|
|||||||
experimental: {
|
experimental: {
|
||||||
ppr: true,
|
ppr: true,
|
||||||
reactCompiler: true,
|
reactCompiler: true,
|
||||||
|
useCache: true,
|
||||||
|
dynamicOnHover: true,
|
||||||
},
|
},
|
||||||
eslint: {
|
eslint: {
|
||||||
ignoreDuringBuilds: true,
|
ignoreDuringBuilds: true,
|
||||||
@@ -37,4 +45,4 @@ export default {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
};
|
} satisfies NextConfig;
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
"build:search_indices": "pnpm node scripts/generateAllIndices.js",
|
"build:search_indices": "pnpm node scripts/generateAllIndices.js",
|
||||||
"build:analyze": "turbo run docs --filter='@discordjs/*' --concurrency=4 && cross-env ANALYZE=true NEXT_PUBLIC_LOCAL_DEV=true pnpm run build:prod",
|
"build:analyze": "turbo run docs --filter='@discordjs/*' --concurrency=4 && cross-env ANALYZE=true NEXT_PUBLIC_LOCAL_DEV=true pnpm run build:prod",
|
||||||
"preview": "next start",
|
"preview": "next start",
|
||||||
"dev": "next dev",
|
"dev": "next dev --turbopack",
|
||||||
"lint": "pnpm run build:check && prettier --check . && cross-env TIMING=1 eslint --format=pretty src ",
|
"lint": "pnpm run build:check && prettier --check . && cross-env TIMING=1 eslint --format=pretty src ",
|
||||||
"format": "pnpm run build:check && prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src ",
|
"format": "pnpm run build:check && prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src ",
|
||||||
"fmt": "pnpm run format"
|
"fmt": "pnpm run format"
|
||||||
@@ -46,57 +46,78 @@
|
|||||||
"homepage": "https://discord.js.org",
|
"homepage": "https://discord.js.org",
|
||||||
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
"funding": "https://github.com/discordjs/discord.js?sponsor",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-collapsible": "^1.1.2",
|
"@radix-ui/react-collapsible": "^1.1.3",
|
||||||
"@react-icons/all-files": "^4.1.0",
|
"@react-icons/all-files": "^4.1.0",
|
||||||
"@vercel/analytics": "^1.4.1",
|
"@vercel/analytics": "^1.5.0",
|
||||||
"@vercel/edge-config": "^1.4.0",
|
"@vercel/edge-config": "^1.4.0",
|
||||||
"@vercel/og": "^0.6.4",
|
"@vercel/og": "^0.6.8",
|
||||||
"@vercel/postgres": "^0.9.0",
|
"@vercel/postgres": "^0.10.0",
|
||||||
"cmdk": "^1.0.4",
|
"cmdk": "^1.1.1",
|
||||||
|
"cva": "1.0.0-beta.3",
|
||||||
"geist": "^1.3.1",
|
"geist": "^1.3.1",
|
||||||
"jotai": "^2.11.0",
|
"immer": "^10.1.1",
|
||||||
"lucide-react": "^0.379.0",
|
"jotai": "^2.12.2",
|
||||||
"meilisearch": "^0.40.0",
|
"jotai-immer": "^0.4.1",
|
||||||
"next": "15.0.0-rc.0",
|
"lucide-react": "^0.487.0",
|
||||||
"next-mdx-remote-client": "^1.0.3",
|
"meilisearch": "^0.49.0",
|
||||||
"next-themes": "^0.3.0",
|
"motion": "^12.6.3",
|
||||||
"overlayscrollbars": "^2.10.1",
|
"next": "15.3.1-canary.2",
|
||||||
|
"next-mdx-remote-client": "^2.1.1",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
|
"nuqs": "^2.4.1",
|
||||||
|
"overlayscrollbars": "^2.11.1",
|
||||||
"overlayscrollbars-react": "^0.5.6",
|
"overlayscrollbars-react": "^0.5.6",
|
||||||
"react": "19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-aria-components": "^1.5.0",
|
"react-aria": "^3.38.1",
|
||||||
"react-dom": "19.1.0",
|
"react-aria-components": "^1.7.1",
|
||||||
|
"react-dom": "^19.1.0",
|
||||||
|
"react-error-boundary": "^5.0.0",
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"usehooks-ts": "^3.1.0",
|
"tailwind-merge": "^3.1.0",
|
||||||
|
"tw-animate-css": "^1.2.5",
|
||||||
|
"usehooks-ts": "^3.1.1",
|
||||||
"vaul": "^1.1.2"
|
"vaul": "^1.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@shikijs/rehype": "^1.24.4",
|
"@next/env": "^15.2.4",
|
||||||
"@tailwindcss/typography": "^0.5.15",
|
"@playwright/test": "^1.51.1",
|
||||||
|
"@shikijs/rehype": "^3.2.1",
|
||||||
|
"@tailwindcss/postcss": "^4.1.3",
|
||||||
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
|
"@tailwindcss/vite": "^4.1.3",
|
||||||
"@testing-library/react": "^16.3.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@testing-library/user-event": "^14.6.1",
|
"@testing-library/user-event": "^14.6.1",
|
||||||
"@types/node": "^22.14.0",
|
"@types/node": "^22.14.0",
|
||||||
"@types/react": "^19.1.0",
|
"@types/react": "^19.1.0",
|
||||||
"@types/react-dom": "^19.1.1",
|
"@types/react-dom": "^19.1.1",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"@vitest/browser": "^3.1.1",
|
||||||
|
"@vitest/coverage-v8": "^2.1.8",
|
||||||
"autoprefixer": "^10.4.21",
|
"autoprefixer": "^10.4.21",
|
||||||
"babel-plugin-react-compiler": "0.0.0-experimental-592953e-20240517",
|
"babel-plugin-react-compiler": "19.0.0-beta-e993439-20250328",
|
||||||
"cpy-cli": "^5.0.0",
|
"cpy-cli": "^5.0.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^9.24.0",
|
"eslint": "^9.24.0",
|
||||||
"eslint-config-neon": "^0.2.7",
|
"eslint-config-neon": "^0.2.7",
|
||||||
"eslint-formatter-compact": "^8.40.0",
|
|
||||||
"eslint-formatter-pretty": "^6.0.1",
|
"eslint-formatter-pretty": "^6.0.1",
|
||||||
|
"git-describe": "^4.1.1",
|
||||||
"happy-dom": "^17.4.4",
|
"happy-dom": "^17.4.4",
|
||||||
|
"msw": "^2.7.3",
|
||||||
|
"playwright": "^1.51.1",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.3",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.14",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.1",
|
||||||
"remark-rehype": "^11.1.1",
|
"remark-rehype": "^11.1.2",
|
||||||
"shiki": "^1.24.4",
|
"shiki": "^3.2.1",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^4.1.3",
|
||||||
|
"tailwindcss-react-aria-components": "^2.0.0",
|
||||||
"turbo": "^2.5.0",
|
"turbo": "^2.5.0",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "^5.8.2",
|
||||||
"vercel": "^41.4.1"
|
"vercel": "^41.4.1",
|
||||||
|
"vite-tsconfig-paths": "^5.1.4",
|
||||||
|
"vitest": "^2.1.8",
|
||||||
|
"vitest-browser-react": "^0.1.1"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.12.0"
|
"node": ">=22.12.0"
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
5
apps/website/postcss.config.js
Normal file
5
apps/website/postcss.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
'@tailwindcss/postcss': {},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { CmdKNoSRR } from '@/components/CmdK';
|
||||||
|
import { fetchDependencies } from '@/util/fetchDependencies';
|
||||||
|
|
||||||
|
export async function CmdK({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
readonly params: Promise<{ readonly packageName: string; readonly version: string }>;
|
||||||
|
}) {
|
||||||
|
const { packageName, version } = await params;
|
||||||
|
|
||||||
|
const dependencies = await fetchDependencies({ packageName, version });
|
||||||
|
|
||||||
|
return <CmdKNoSRR dependencies={dependencies} />;
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/* eslint-disable react/no-unknown-property */
|
/* eslint-disable react/no-unknown-property */
|
||||||
|
|
||||||
import { ImageResponse } from 'next/og';
|
import { ImageResponse } from 'next/og';
|
||||||
import { resolveKind } from '~/util/resolveNodeKind';
|
import { resolveKind } from '@/util/resolveNodeKind';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
export const runtime = 'edge';
|
||||||
|
|
||||||
@@ -15,14 +15,24 @@ export const contentType = 'image/png';
|
|||||||
export default async function Image({
|
export default async function Image({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
readonly params: { readonly item: string; readonly packageName: string; readonly version: string };
|
readonly params: Promise<{ readonly item: string; readonly packageName: string; readonly version: string }>;
|
||||||
}) {
|
}) {
|
||||||
const normalizeItem = params.item.split(encodeURIComponent(':')).join('.').toLowerCase();
|
const { item, packageName, version } = await params;
|
||||||
|
|
||||||
const isMainVersion = params.version === 'main';
|
const [fontDataBold, fontDataBlack] = await Promise.all([
|
||||||
|
fetch(new URL('../../../../../../assets/Geist-Bold.ttf', import.meta.url), {
|
||||||
|
next: { revalidate: 604_800 },
|
||||||
|
}).then(async (res) => res.arrayBuffer()),
|
||||||
|
fetch(new URL('../../../../../../assets/Geist-Black.ttf', import.meta.url), {
|
||||||
|
next: { revalidate: 604_800 },
|
||||||
|
}).then(async (res) => res.arrayBuffer()),
|
||||||
|
]);
|
||||||
|
const normalizeItem = item.split(encodeURIComponent(':')).join('.').toLowerCase();
|
||||||
|
|
||||||
|
const isMain = version === 'main';
|
||||||
const fileContent = await fetch(
|
const fileContent = await fetch(
|
||||||
`${process.env.BLOB_STORAGE_URL}/rewrite/${params.packageName}/${params.version}.${normalizeItem}.api.json`,
|
`${process.env.BLOB_STORAGE_URL}/rewrite/${packageName}/${version}.${normalizeItem}.api.json`,
|
||||||
{ next: isMainVersion ? { revalidate: 0 } : { revalidate: 604_800 } },
|
{ next: { revalidate: isMain ? 0 : 604_800 } },
|
||||||
);
|
);
|
||||||
const node = await fileContent.json();
|
const node = await fileContent.json();
|
||||||
|
|
||||||
@@ -30,7 +40,7 @@ export default async function Image({
|
|||||||
(
|
(
|
||||||
<div tw="flex bg-[#121212] h-full w-full p-14">
|
<div tw="flex bg-[#121212] h-full w-full p-14">
|
||||||
<div tw="flex flex-col mx-auto h-full text-white">
|
<div tw="flex flex-col mx-auto h-full text-white">
|
||||||
<div tw="flex text-4xl text-gray-400">{params.packageName}</div>
|
<div tw="flex text-4xl text-gray-400">{packageName}</div>
|
||||||
<div tw="flex flex-col justify-between h-full w-full pt-14">
|
<div tw="flex flex-col justify-between h-full w-full pt-14">
|
||||||
<div tw="flex items-center max-w-full">
|
<div tw="flex items-center max-w-full">
|
||||||
<span tw="mr-6">{resolveKind(node.kind, 94)}</span>
|
<span tw="mr-6">{resolveKind(node.kind, 94)}</span>
|
||||||
@@ -40,7 +50,7 @@ export default async function Image({
|
|||||||
whiteSpace: 'nowrap',
|
whiteSpace: 'nowrap',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
tw="text-[5.5rem] font-bold w-full"
|
tw="text-[5.5rem] font-bold w-full"
|
||||||
>
|
>
|
||||||
{node.displayName}
|
{node.displayName}
|
||||||
</h2>
|
</h2>
|
||||||
@@ -94,6 +104,20 @@ export default async function Image({
|
|||||||
),
|
),
|
||||||
{
|
{
|
||||||
...size,
|
...size,
|
||||||
|
fonts: [
|
||||||
|
{
|
||||||
|
name: 'Geist',
|
||||||
|
data: fontDataBold,
|
||||||
|
weight: 700,
|
||||||
|
style: 'normal',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Geist',
|
||||||
|
data: fontDataBlack,
|
||||||
|
weight: 900,
|
||||||
|
style: 'normal',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,44 @@
|
|||||||
|
'use cache';
|
||||||
|
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import { DocItem } from '~/components/DocItem';
|
import { DocItem } from '@/components/DocItem';
|
||||||
import { fetchNode } from '~/util/fetchNode';
|
import { fetchNode } from '@/util/fetchNode';
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
readonly params: {
|
readonly params: Promise<{
|
||||||
readonly item: string;
|
readonly item: string;
|
||||||
readonly packageName: string;
|
readonly packageName: string;
|
||||||
readonly version: string;
|
readonly version: string;
|
||||||
};
|
}>;
|
||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
const normalizeItem = params.item.split(encodeURIComponent(':'))[0];
|
const { item, packageName, version } = await params;
|
||||||
|
|
||||||
|
const normalizeItem = item.split(encodeURIComponent(':'))[0];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: `${normalizeItem} (${params.packageName} - ${params.version})`,
|
title: `${normalizeItem} (${packageName} - ${version})`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function Page({
|
export default async function Page({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
readonly params: { readonly item: string; readonly packageName: string; readonly version: string };
|
readonly params: Promise<{ readonly item: string; readonly packageName: string; readonly version: string }>;
|
||||||
}) {
|
}) {
|
||||||
const node = await fetchNode({ item: params.item, packageName: params.packageName, version: params.version });
|
const { item, packageName, version } = await params;
|
||||||
|
|
||||||
|
const node = await fetchNode({ item, packageName, version });
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="flex w-full flex-col gap-8 pb-12 md:pb-0">
|
<main className="mx-auto flex w-full max-w-screen-xl flex-col gap-8 px-6 py-4">
|
||||||
<DocItem node={node} packageName={params.packageName} version={params.version} />
|
<DocItem node={node} packageName={packageName} version={version} />
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,33 @@
|
|||||||
import type { Metadata } from 'next';
|
'use cache';
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
import type { PropsWithChildren } from 'react';
|
|
||||||
import { Navigation } from '~/components/Navigation';
|
|
||||||
import { OverlayScrollbarsComponent } from '~/components/OverlayScrollbars';
|
|
||||||
import { Drawer } from '~/components/ui/Drawer';
|
|
||||||
import { Footer } from '~/components/ui/Footer';
|
|
||||||
import { fetchDependencies } from '~/util/fetchDependencies';
|
|
||||||
|
|
||||||
// eslint-disable-next-line promise/prefer-await-to-then
|
import { VscGithubInverted } from '@react-icons/all-files/vsc/VscGithubInverted';
|
||||||
const CmdK = dynamic(async () => import('~/components/ui/CmdK').then((mod) => mod.CmdK), { ssr: false });
|
import type { Metadata } from 'next';
|
||||||
|
import Link from 'next/link';
|
||||||
|
import { Suspense, type PropsWithChildren } from 'react';
|
||||||
|
import { Footer } from '@/components/Footer';
|
||||||
|
import { Navigation } from '@/components/Navigation';
|
||||||
|
import { Scrollbars } from '@/components/OverlayScrollbars';
|
||||||
|
import { PackageSelect } from '@/components/PackageSelect';
|
||||||
|
import { SearchButton } from '@/components/SearchButton';
|
||||||
|
import { ThemeSwitchNoSRR } from '@/components/ThemeSwitch';
|
||||||
|
import { VersionSelect } from '@/components/VersionSelect';
|
||||||
|
import { Sidebar, SidebarContent, SidebarHeader, SidebarInset, SidebarTrigger } from '@/components/ui/Sidebar';
|
||||||
|
import { buttonStyles } from '@/styles/ui/button';
|
||||||
|
import { ENV } from '@/util/env';
|
||||||
|
import { fetchVersions } from '@/util/fetchVersions';
|
||||||
|
import { CmdK } from './CmdK';
|
||||||
|
|
||||||
export async function generateMetadata({
|
export async function generateMetadata({
|
||||||
params,
|
params,
|
||||||
}: {
|
}: {
|
||||||
readonly params: { readonly packageName: string; readonly version: string };
|
readonly params: Promise<{ readonly packageName: string; readonly version: string }>;
|
||||||
}): Promise<Metadata> {
|
}): Promise<Metadata> {
|
||||||
|
const { packageName, version } = await params;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: {
|
title: {
|
||||||
template: '%s | discord.js',
|
template: '%s | discord.js',
|
||||||
default: `${params.packageName} (${params.version})`,
|
default: `${packageName} (${version})`,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -26,44 +35,74 @@ export async function generateMetadata({
|
|||||||
export default async function Layout({
|
export default async function Layout({
|
||||||
params,
|
params,
|
||||||
children,
|
children,
|
||||||
}: PropsWithChildren<{ readonly params: { readonly packageName: string; readonly version: string } }>) {
|
}: PropsWithChildren<{ readonly params: Promise<{ readonly packageName: string; readonly version: string }> }>) {
|
||||||
const dependencies = await fetchDependencies({ packageName: params.packageName, version: params.version });
|
const { packageName, version } = await params;
|
||||||
|
|
||||||
|
const versions = fetchVersions(packageName);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
// eslint-disable-next-line react/no-unknown-property
|
<>
|
||||||
<div className="mx-auto flex max-w-screen-2xl flex-col gap-12 p-6 md:flex-row" vaul-drawer-wrapper="">
|
<Sidebar closeButton={false} intent="inset">
|
||||||
<div className="sticky top-6 hidden flex-shrink-0 self-start md:block">
|
<SidebarHeader className="bg-[#f3f3f4] p-4 dark:bg-[#121214]">
|
||||||
<OverlayScrollbarsComponent
|
<div className="flex flex-col gap-2">
|
||||||
className="max-h-[calc(100dvh-48px)]"
|
<div className="flex place-content-between place-items-center p-1">
|
||||||
defer
|
<Link className="text-xl font-bold" href={`/docs/packages/${packageName}/${version}`}>
|
||||||
options={{
|
{packageName}
|
||||||
overflow: { x: 'hidden' },
|
</Link>
|
||||||
scrollbars: {
|
<div className="flex place-items-center gap-2">
|
||||||
autoHide: 'scroll',
|
<Link
|
||||||
autoHideDelay: 500,
|
aria-label="GitHub"
|
||||||
autoHideSuspend: true,
|
className={buttonStyles({ variant: 'filled', size: 'icon-sm' })}
|
||||||
clickScroll: true,
|
href="https://github.com/discordjs/discord.js"
|
||||||
},
|
rel="external noopener noreferrer"
|
||||||
}}
|
target="_blank"
|
||||||
>
|
>
|
||||||
<Navigation className="pr-4" packageName={params.packageName} version={params.version} />
|
<VscGithubInverted aria-hidden data-slot="icon" size={18} />
|
||||||
</OverlayScrollbarsComponent>
|
</Link>
|
||||||
</div>
|
<ThemeSwitchNoSRR />
|
||||||
<div className="pb-12">
|
</div>
|
||||||
{children}
|
</div>
|
||||||
<Footer />
|
<PackageSelect />
|
||||||
</div>
|
{/* <h3 className="p-1 text-lg font-semibold">{version}</h3> */}
|
||||||
<div className="fixed bottom-0 left-0 right-0 md:hidden">
|
<VersionSelect versionsPromise={versions} />
|
||||||
<Drawer>
|
<SearchButton />
|
||||||
<Navigation
|
</div>
|
||||||
className="max-w-none overflow-auto p-0 lg:max-w-none"
|
</SidebarHeader>
|
||||||
drawer
|
<SidebarContent className="bg-[#f3f3f4] p-0 py-4 pl-4 dark:bg-[#121214]">
|
||||||
packageName={params.packageName}
|
<Scrollbars>
|
||||||
version={params.version}
|
<Navigation packageName={packageName} version={version} />
|
||||||
/>
|
</Scrollbars>
|
||||||
</Drawer>
|
</SidebarContent>
|
||||||
</div>
|
</Sidebar>
|
||||||
<CmdK dependencies={dependencies} />
|
<SidebarInset>
|
||||||
</div>
|
{ENV.IS_LOCAL_DEV ? (
|
||||||
|
<div className="sticky top-0 z-10 flex place-content-center place-items-center border border-red-400/35 bg-red-500/65 p-2 px-4 text-center text-base text-white shadow-md backdrop-blur">
|
||||||
|
Local test environment
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{ENV.IS_PREVIEW ? (
|
||||||
|
<div className="sticky top-0 z-10 flex place-content-center place-items-center border border-red-400/35 bg-red-500/65 p-2 px-4 text-center text-base text-white shadow-md backdrop-blur">
|
||||||
|
Preview environment
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
<div className="bg-[#fbfbfb] pb-12 dark:bg-[#1a1a1e]">
|
||||||
|
<div className="relative px-6 pt-6 md:hidden">
|
||||||
|
<div className="fixed top-5 left-6 z-20 md:hidden">
|
||||||
|
<SidebarTrigger aria-label="Navigation" size="icon" variant="filled" />
|
||||||
|
</div>
|
||||||
|
<div className="flex place-content-end">
|
||||||
|
<Link className="text-xl font-bold" href={`/docs/packages/${packageName}/${version}`}>
|
||||||
|
{packageName}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
</SidebarInset>
|
||||||
|
<Suspense>
|
||||||
|
<CmdK params={params} />
|
||||||
|
</Suspense>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,19 @@
|
|||||||
|
'use cache';
|
||||||
|
|
||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import rehypeShikiFromHighlighter from '@shikijs/rehype/core';
|
import rehypeShikiFromHighlighter from '@shikijs/rehype/core';
|
||||||
import { MDXRemote } from 'next-mdx-remote-client/rsc';
|
import { MDXRemote } from 'next-mdx-remote-client/rsc';
|
||||||
import remarkGfm from 'remark-gfm';
|
import remarkGfm from 'remark-gfm';
|
||||||
import { getHighlighterCore } from 'shiki/core';
|
import { getSingletonHighlighter } from '@/util/shiki.bundle';
|
||||||
import getWasm from 'shiki/wasm';
|
|
||||||
|
|
||||||
const highlighter = await getHighlighterCore({
|
export default async function Page({ params }: { readonly params: Promise<{ readonly packageName: string }> }) {
|
||||||
themes: [import('shiki/themes/github-light.mjs'), import('shiki/themes/github-dark-dimmed.mjs')],
|
const { packageName } = await params;
|
||||||
langs: [
|
|
||||||
import('shiki/langs/typescript.mjs'),
|
|
||||||
import('shiki/langs/javascript.mjs'),
|
|
||||||
import('shiki/langs/shellscript.mjs'),
|
|
||||||
],
|
|
||||||
loadWasm: getWasm,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default async function Page({ params }: { readonly params: { readonly packageName: string } }) {
|
const fileContent = await readFile(join(process.cwd(), `src/assets/readme/${packageName}/home-README.md`), 'utf8');
|
||||||
const fileContent = await readFile(
|
|
||||||
join(process.cwd(), `src/assets/readme/${params.packageName}/home-README.md`),
|
|
||||||
'utf8',
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="prose prose-neutral mx-auto max-w-screen-xl dark:prose-invert">
|
<div className="prose prose-neutral dark:prose-invert prose-a:[&>img]:inline-block prose-a:[&>img]:m-0 prose-a:[&>img[height='44']]:h-11 prose-p:my-2 prose-pre:py-3 prose-pre:rounded-sm prose-pre:px-0 prose-pre:border prose-pre:border-[#d4d4d4] dark:prose-pre:border-[#404040] prose-code:font-normal prose-a:text-[#5865F2] prose-a:no-underline prose-a:hover:text-[#3d48c3] dark:prose-a:hover:text-[#7782fa] mx-auto max-w-screen-xl px-6 py-6 [&_code_span:last-of-type:empty]:hidden [&_div[align='center']_p_a+a]:ml-2">
|
||||||
<MDXRemote
|
<MDXRemote
|
||||||
options={{
|
options={{
|
||||||
mdxOptions: {
|
mdxOptions: {
|
||||||
@@ -31,7 +21,10 @@ export default async function Page({ params }: { readonly params: { readonly pac
|
|||||||
rehypePlugins: [
|
rehypePlugins: [
|
||||||
[
|
[
|
||||||
rehypeShikiFromHighlighter,
|
rehypeShikiFromHighlighter,
|
||||||
highlighter,
|
await getSingletonHighlighter({
|
||||||
|
langs: ['typescript', 'javascript', 'shellscript'],
|
||||||
|
themes: ['github-light', 'github-dark-dimmed'],
|
||||||
|
}),
|
||||||
{
|
{
|
||||||
themes: {
|
themes: {
|
||||||
light: 'github-light',
|
light: 'github-light',
|
||||||
|
|||||||
@@ -3,18 +3,17 @@ import { GeistMono } from 'geist/font/mono';
|
|||||||
import { GeistSans } from 'geist/font/sans';
|
import { GeistSans } from 'geist/font/sans';
|
||||||
import type { Metadata, Viewport } from 'next';
|
import type { Metadata, Viewport } from 'next';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { LocalizedStringProvider } from 'react-aria-components/i18n';
|
import { DESCRIPTION } from '@/util/constants';
|
||||||
import { DESCRIPTION } from '~/util/constants';
|
import { ENV } from '@/util/env';
|
||||||
import { ENV } from '~/util/env';
|
|
||||||
import { Providers } from './providers';
|
import { Providers } from './providers';
|
||||||
|
|
||||||
import '~/styles/main.css';
|
import '@/styles/base.css';
|
||||||
import 'overlayscrollbars/overlayscrollbars.css';
|
import 'overlayscrollbars/overlayscrollbars.css';
|
||||||
|
|
||||||
export const viewport: Viewport = {
|
export const viewport: Viewport = {
|
||||||
themeColor: [
|
themeColor: [
|
||||||
{ media: '(prefers-color-scheme: light)', color: '#ffffff' },
|
{ media: '(prefers-color-scheme: light)', color: '#fbfbfb' },
|
||||||
{ media: '(prefers-color-scheme: dark)', color: '#121212' },
|
{ media: '(prefers-color-scheme: dark)', color: '#1a1a1e' },
|
||||||
],
|
],
|
||||||
colorScheme: 'light dark',
|
colorScheme: 'light dark',
|
||||||
};
|
};
|
||||||
@@ -70,28 +69,15 @@ export const metadata: Metadata = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
other: {
|
other: {
|
||||||
'msapplication-TileColor': '#121212',
|
'msapplication-TileColor': '#1a1a1e',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function RootLayout({ children }: PropsWithChildren) {
|
export default async function RootLayout({ children }: PropsWithChildren) {
|
||||||
return (
|
return (
|
||||||
<html className={`${GeistSans.variable} ${GeistMono.variable} antialiased`} lang="en" suppressHydrationWarning>
|
<html className={`${GeistSans.variable} ${GeistMono.variable} antialiased`} lang="en" suppressHydrationWarning>
|
||||||
<body className="relative bg-white dark:bg-[#121212]">
|
<body className="text-base-md text-base-neutral-900 dark:text-base-neutral-40 overscroll-y-none bg-[#fbfbfb] dark:bg-[#1a1a1e]">
|
||||||
<LocalizedStringProvider locale="en-US" />
|
<Providers>{children}</Providers>
|
||||||
<Providers>
|
|
||||||
{ENV.IS_LOCAL_DEV ? (
|
|
||||||
<div className="fixed left-1/2 top-2 z-10 flex -translate-x-1/2 place-content-center place-items-center rounded-md border border-red-400/35 bg-red-500/65 p-2 px-4 text-center text-base text-white shadow-md backdrop-blur">
|
|
||||||
Local test environment
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{ENV.IS_PREVIEW ? (
|
|
||||||
<div className="fixed left-1/2 top-2 z-10 flex -translate-x-1/2 place-content-center place-items-center rounded-md border border-red-400/35 bg-red-500/65 p-2 px-4 text-center text-base text-white shadow-md backdrop-blur">
|
|
||||||
Preview environment
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{children}
|
|
||||||
</Providers>
|
|
||||||
<Analytics />
|
<Analytics />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import Link from 'next/link';
|
|||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto flex min-h-[calc(100vh_-_100px)] max-w-lg flex-col place-content-center place-items-center gap-8 px-8 py-16 lg:px-6 lg:py-0">
|
<div className="mx-auto flex min-h-[calc(100vh_-_100px)] max-w-lg flex-col place-content-center place-items-center gap-8 px-8 py-16 lg:px-6 lg:py-0">
|
||||||
<h1 className="text-[9rem] font-black leading-none md:text-[12rem]">404</h1>
|
<h1 className="text-[9rem] leading-none font-black md:text-[12rem]">404</h1>
|
||||||
<h2 className="text-[2rem] md:text-[3rem]">Not found.</h2>
|
<h2 className="text-[2rem] md:text-[3rem]">Not found.</h2>
|
||||||
<Link
|
<Link
|
||||||
className="inline-flex rounded-md border border-transparent bg-blurple px-6 py-2 font-medium text-white"
|
className="bg-base-blurple-400 inline-flex rounded-md border border-transparent px-6 py-2 font-medium text-white"
|
||||||
href="/docs"
|
href="/docs"
|
||||||
>
|
>
|
||||||
Take me back
|
Take me back
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/* eslint-disable react/no-unknown-property */
|
/* eslint-disable react/no-unknown-property */
|
||||||
|
|
||||||
import { ImageResponse } from 'next/og';
|
import { ImageResponse } from 'next/og';
|
||||||
|
|
||||||
export const runtime = 'edge';
|
export const runtime = 'edge';
|
||||||
@@ -11,15 +12,19 @@ export const size = {
|
|||||||
export const contentType = 'image/png';
|
export const contentType = 'image/png';
|
||||||
|
|
||||||
export default async function Image() {
|
export default async function Image() {
|
||||||
|
const fontData = await fetch(new URL('../assets/Geist-Black.ttf', import.meta.url), {
|
||||||
|
next: { revalidate: 604_800 },
|
||||||
|
}).then(async (res) => res.arrayBuffer());
|
||||||
|
|
||||||
return new ImageResponse(
|
return new ImageResponse(
|
||||||
(
|
(
|
||||||
<div tw="flex bg-[#121212] h-full w-full">
|
<div tw="flex bg-[#121214] h-full w-full">
|
||||||
<div tw="mx-auto flex items-center h-full">
|
<div tw="mx-auto flex items-center h-full">
|
||||||
<div tw="flex">
|
<div tw="flex">
|
||||||
<div tw="flex">
|
<div tw="flex">
|
||||||
<div tw="flex flex-col font-black text-[5.5rem] text-white">
|
<div tw="flex flex-col font-black text-8xl text-white leading-tight">
|
||||||
<div tw="flex flex-row">
|
<div tw="flex flex-row">
|
||||||
The <span tw="bg-[#5865f2] rounded-lg py-1 px-6 ml-4">most popular</span>
|
The <span tw="bg-[#5865f2] rounded-md px-3 py-2 ml-4 bottom-2">most popular</span>
|
||||||
</div>
|
</div>
|
||||||
<span>way to build Discord</span>
|
<span>way to build Discord</span>
|
||||||
<span>bots.</span>
|
<span>bots.</span>
|
||||||
@@ -31,6 +36,14 @@ export default async function Image() {
|
|||||||
),
|
),
|
||||||
{
|
{
|
||||||
...size,
|
...size,
|
||||||
|
fonts: [
|
||||||
|
{
|
||||||
|
name: 'Geist',
|
||||||
|
data: fontData,
|
||||||
|
weight: 900,
|
||||||
|
style: 'normal',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,42 @@
|
|||||||
import { ExternalLink } from 'lucide-react';
|
import { ExternalLink } from 'lucide-react';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import vercelLogo from '~/assets/powered-by-vercel.svg';
|
import vercelLogo from '@/assets/powered-by-vercel.svg';
|
||||||
import workersLogo from '~/assets/powered-by-workers.png';
|
import workersLogo from '@/assets/powered-by-workers.png';
|
||||||
import { InstallButton } from '~/components/ui/InstallButton';
|
import { InstallButton } from '@/components/InstallButton';
|
||||||
import { DESCRIPTION } from '~/util/constants';
|
import { buttonStyles } from '@/styles/ui/button';
|
||||||
|
import { DESCRIPTION } from '@/util/constants';
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
return (
|
return (
|
||||||
<div className="mx-auto flex min-h-screen w-full max-w-screen-lg flex-col place-content-center place-items-center gap-24 px-8 pb-16 pt-12">
|
<div className="mx-auto flex min-h-screen w-full max-w-screen-lg flex-col place-content-center place-items-center gap-24 px-8 pt-12 pb-16">
|
||||||
<div className="flex flex-col gap-10 text-center">
|
<div className="flex flex-col gap-10 text-center">
|
||||||
<h1 className="z-10 text-3xl font-black leading-tight sm:text-7xl sm:leading-tight">
|
<h1 className="text-base-heading-xl font-black sm:text-7xl sm:leading-tight">
|
||||||
The <span className="relative rounded bg-blurple px-3 py-1 text-white">most popular</span> way to build
|
The{' '}
|
||||||
Discord bots.
|
<span className="bg-base-blurple-400 text-base-neutral-0 relative rounded-sm px-3 py-2">most popular</span>{' '}
|
||||||
|
way to build Discord bots.
|
||||||
</h1>
|
</h1>
|
||||||
<p className="z-10 leading-normal text-neutral-700 dark:text-neutral-300 md:my-6">{DESCRIPTION}</p>
|
<p className="text-base-neutral-600 dark:text-base-neutral-300 md:my-6">{DESCRIPTION}</p>
|
||||||
|
|
||||||
<div className="flex flex-wrap place-content-center gap-4 sm:flex-wrap md:flex-row">
|
<div className="flex flex-wrap place-content-center gap-4 sm:flex-wrap md:flex-row">
|
||||||
<Link
|
<Link className={buttonStyles({ variant: 'filled' })} href="/docs">
|
||||||
className="inline-flex rounded-md border border-transparent bg-blurple px-6 py-2 font-medium text-white"
|
|
||||||
href="/docs"
|
|
||||||
>
|
|
||||||
Docs
|
Docs
|
||||||
</Link>
|
</Link>
|
||||||
<a
|
<a
|
||||||
className="inline-flex gap-2 rounded-md border border-neutral-300 bg-white px-6 py-2 font-medium hover:bg-neutral-200 dark:border-neutral-700 dark:bg-transparent dark:hover:bg-neutral-800"
|
className={buttonStyles({ variant: 'discreet' })}
|
||||||
href="https://discordjs.guide"
|
href="https://discordjs.guide"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Guide <ExternalLink aria-hidden size={20} />
|
Guide <ExternalLink aria-hidden data-slot="icon" size={18} />
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
className="inline-flex gap-2 rounded-md border border-neutral-300 bg-white px-6 py-2 font-medium hover:bg-neutral-200 dark:border-neutral-700 dark:bg-transparent dark:hover:bg-neutral-800"
|
className={buttonStyles({ variant: 'discreet' })}
|
||||||
href="https://github.com/discordjs/discord.js"
|
href="https://github.com/discordjs/discord.js"
|
||||||
rel="external noopener noreferrer"
|
rel="external noopener noreferrer"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
GitHub <ExternalLink aria-hidden size={20} />
|
GitHub <ExternalLink aria-hidden data-slot="icon" size={18} />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,12 @@
|
|||||||
import { Provider as JotaiProvider } from 'jotai';
|
import { Provider as JotaiProvider } from 'jotai';
|
||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import { ThemeProvider } from 'next-themes';
|
import { ThemeProvider } from 'next-themes';
|
||||||
|
import { NuqsAdapter } from 'nuqs/adapters/next/app';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { RouterProvider } from 'react-aria-components';
|
import { RouterProvider } from 'react-aria-components';
|
||||||
import { useSystemThemeFallback } from '~/hooks/useSystemThemeFallback';
|
import { SidebarProvider } from '@/components/ui/Sidebar';
|
||||||
import { useUnregisterServiceWorker } from '~/hooks/useUnregisterServiceWorker';
|
import { useSystemThemeFallback } from '@/hooks/useSystemThemeFallback';
|
||||||
|
import { useUnregisterServiceWorker } from '@/hooks/useUnregisterServiceWorker';
|
||||||
|
|
||||||
export function Providers({ children }: PropsWithChildren) {
|
export function Providers({ children }: PropsWithChildren) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -14,10 +16,14 @@ export function Providers({ children }: PropsWithChildren) {
|
|||||||
useSystemThemeFallback();
|
useSystemThemeFallback();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RouterProvider navigate={router.push}>
|
<NuqsAdapter>
|
||||||
<JotaiProvider>
|
<ThemeProvider attribute="class">
|
||||||
<ThemeProvider attribute="class">{children}</ThemeProvider>
|
<RouterProvider navigate={router.push}>
|
||||||
</JotaiProvider>
|
<JotaiProvider>
|
||||||
</RouterProvider>
|
<SidebarProvider defaultOpen>{children}</SidebarProvider>
|
||||||
|
</JotaiProvider>
|
||||||
|
</RouterProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
</NuqsAdapter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
apps/website/src/assets/Geist-Black.ttf
Normal file
BIN
apps/website/src/assets/Geist-Black.ttf
Normal file
Binary file not shown.
BIN
apps/website/src/assets/Geist-Bold.ttf
Normal file
BIN
apps/website/src/assets/Geist-Bold.ttf
Normal file
Binary file not shown.
@@ -4,7 +4,7 @@ import type { PropsWithChildren } from 'react';
|
|||||||
export function Badge({ children, className = '' }: PropsWithChildren<{ readonly className?: string }>) {
|
export function Badge({ children, className = '' }: PropsWithChildren<{ readonly className?: string }>) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={`inline-flex place-items-center gap-1 rounded-full px-2 py-1 font-sans text-sm font-normal leading-none ${className}`}
|
className={`inline-flex place-items-center gap-1 rounded-full px-2 py-1 font-sans text-sm leading-none font-normal ${className}`}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -4,13 +4,15 @@ import { Command } from 'cmdk';
|
|||||||
import { useAtom, useSetAtom } from 'jotai';
|
import { useAtom, useSetAtom } from 'jotai';
|
||||||
import { ArrowRight } from 'lucide-react';
|
import { ArrowRight } from 'lucide-react';
|
||||||
import MeiliSearch from 'meilisearch';
|
import MeiliSearch from 'meilisearch';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
import { usePathname, useRouter } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useDebounceValue } from 'usehooks-ts';
|
import { useDebounceValue, useMediaQuery } from 'usehooks-ts';
|
||||||
import { isCmdKOpenAtom } from '~/stores/cmdk';
|
import { Scrollbars } from '@/components/OverlayScrollbars';
|
||||||
import { isDrawerOpenAtom } from '~/stores/drawer';
|
import { isCmdKOpenAtom } from '@/stores/cmdk';
|
||||||
import { resolveKind } from '~/util/resolveNodeKind';
|
import { isDrawerOpenAtom } from '@/stores/drawer';
|
||||||
import { OverlayScrollbarsComponent } from '../OverlayScrollbars';
|
import { cx } from '@/styles/cva';
|
||||||
|
import { resolveKind } from '@/util/resolveNodeKind';
|
||||||
|
|
||||||
const client = new MeiliSearch({
|
const client = new MeiliSearch({
|
||||||
host: 'https://search.discordjs.dev',
|
host: 'https://search.discordjs.dev',
|
||||||
@@ -24,6 +26,7 @@ export function CmdK({ dependencies }: { readonly dependencies: string[] }) {
|
|||||||
const setDrawerOpen = useSetAtom(isDrawerOpenAtom);
|
const setDrawerOpen = useSetAtom(isDrawerOpenAtom);
|
||||||
const [search, setSearch] = useDebounceValue('', 250);
|
const [search, setSearch] = useDebounceValue('', 250);
|
||||||
const [searchResults, setSearchResults] = useState<any[]>([]);
|
const [searchResults, setSearchResults] = useState<any[]>([]);
|
||||||
|
const isMobile = useMediaQuery('(max-width: 600px)');
|
||||||
|
|
||||||
const packageName = pathname?.split('/').slice(3, 4)[0];
|
const packageName = pathname?.split('/').slice(3, 4)[0];
|
||||||
const branchName = pathname?.split('/').slice(4, 5)[0];
|
const branchName = pathname?.split('/').slice(4, 5)[0];
|
||||||
@@ -41,9 +44,9 @@ export function CmdK({ dependencies }: { readonly dependencies: string[] }) {
|
|||||||
>
|
>
|
||||||
{resolveKind(item.kind)}
|
{resolveKind(item.kind)}
|
||||||
<div className="flex flex-grow flex-col">
|
<div className="flex flex-grow flex-col">
|
||||||
<span className="font-semibold">{item.name}</span>
|
<span className="font-semibold wrap-anywhere">{item.name}</span>
|
||||||
<span className="line-clamp-1 text-sm">{item.summary}</span>
|
<span className={cx('truncate text-sm', isMobile ? 'max-w-[30ch]' : 'max-w-[40ch]')}>{item.summary}</span>
|
||||||
<span className="truncate text-xs">{item.path}</span>
|
<span className={cx('truncate text-xs', isMobile ? 'max-w-[30ch]' : 'max-w-[40ch]')}>{item.path}</span>
|
||||||
</div>
|
</div>
|
||||||
<ArrowRight aria-hidden className="flex-shrink-0" />
|
<ArrowRight aria-hidden className="flex-shrink-0" />
|
||||||
</Command.Item>
|
</Command.Item>
|
||||||
@@ -114,11 +117,11 @@ export function CmdK({ dependencies }: { readonly dependencies: string[] }) {
|
|||||||
shouldFilter={false}
|
shouldFilter={false}
|
||||||
>
|
>
|
||||||
<Command.Input
|
<Command.Input
|
||||||
className="mb-4 w-full border-b border-neutral-300 bg-transparent px-2 pb-4 pt-2 outline-none dark:border-neutral-700"
|
className="mb-4 w-full border-b border-neutral-300 bg-transparent px-2 pt-2 pb-4 outline-none dark:border-neutral-700"
|
||||||
onValueChange={setSearch}
|
onValueChange={setSearch}
|
||||||
placeholder="Quick search..."
|
placeholder="Quick search..."
|
||||||
/>
|
/>
|
||||||
<OverlayScrollbarsComponent
|
<Scrollbars
|
||||||
className="max-h-96 pr-3"
|
className="max-h-96 pr-3"
|
||||||
defer
|
defer
|
||||||
options={{
|
options={{
|
||||||
@@ -140,7 +143,9 @@ export function CmdK({ dependencies }: { readonly dependencies: string[] }) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Command.List>
|
</Command.List>
|
||||||
</OverlayScrollbarsComponent>
|
</Scrollbars>
|
||||||
</Command.Dialog>
|
</Command.Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const CmdKNoSRR = dynamic(async () => CmdK, { ssr: false });
|
||||||
@@ -1,25 +1,25 @@
|
|||||||
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
|
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
|
||||||
import { Code2, LinkIcon } from 'lucide-react';
|
import { Code2, LinkIcon } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { ENV } from '~/util/env';
|
import { ENV } from '@/util/env';
|
||||||
import { ParameterNode } from './ParameterNode';
|
import { ParameterNode } from './ParameterNode';
|
||||||
import { SummaryNode } from './SummaryNode';
|
import { SummaryNode } from './SummaryNode';
|
||||||
|
|
||||||
export async function ConstructorNode({ node, version }: { readonly node: any; readonly version: string }) {
|
export async function ConstructorNode({ node, version }: { readonly node: any; readonly version: string }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-4">
|
||||||
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">
|
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">
|
||||||
<VscSymbolMethod aria-hidden className="flex-shrink-0" size={24} />
|
<VscSymbolMethod aria-hidden className="flex-shrink-0" size={24} />
|
||||||
Constructors
|
Constructors
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="flex place-content-between place-items-center">
|
<div className="flex place-content-between place-items-center gap-1">
|
||||||
<h3
|
<h3
|
||||||
className={`${ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-16' : 'scroll-mt-8'} group break-words font-mono font-semibold`}
|
className={`${ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-16' : 'scroll-mt-8'} group px-2 font-mono font-semibold break-all`}
|
||||||
id="constructor"
|
id="constructor"
|
||||||
>
|
>
|
||||||
{/* constructor({parsedContent.constructor.parametersString}) */}
|
{/* constructor({parsedContent.constructor.parametersString}) */}
|
||||||
<Link className="float-left -ml-6 hidden pb-2 pr-2 group-hover:block" href="#constructor">
|
<Link className="float-left -ml-6 hidden pr-2 pb-2 group-hover:block" href="#constructor">
|
||||||
<LinkIcon aria-hidden size={16} />
|
<LinkIcon aria-hidden size={16} />
|
||||||
</Link>
|
</Link>
|
||||||
constructor({node.parameters?.length ? <ParameterNode node={node.parameters} version={version} /> : null})
|
constructor({node.parameters?.length ? <ParameterNode node={node.parameters} version={version} /> : null})
|
||||||
@@ -44,7 +44,7 @@ export async function ConstructorNode({ node, version }: { readonly node: any; r
|
|||||||
<SummaryNode node={node.summary.summarySection} padding version={version} />
|
<SummaryNode node={node.summary.summarySection} padding version={version} />
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div aria-hidden className="px-4">
|
<div aria-hidden className="p-4">
|
||||||
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { EventNode } from './EventNode';
|
|||||||
import { InformationNode } from './InformationNode';
|
import { InformationNode } from './InformationNode';
|
||||||
import { MethodNode } from './MethodNode';
|
import { MethodNode } from './MethodNode';
|
||||||
import { Outline } from './Outline';
|
import { Outline } from './Outline';
|
||||||
import { OverlayScrollbarsComponent } from './OverlayScrollbars';
|
import { Scrollbars } from './OverlayScrollbars';
|
||||||
import { ParameterNode } from './ParameterNode';
|
import { ParameterNode } from './ParameterNode';
|
||||||
import { PropertyNode } from './PropertyNode';
|
import { PropertyNode } from './PropertyNode';
|
||||||
import { ReturnNode } from './ReturnNode';
|
import { ReturnNode } from './ReturnNode';
|
||||||
@@ -26,12 +26,14 @@ async function OverloadNode({
|
|||||||
readonly packageName: string;
|
readonly packageName: string;
|
||||||
readonly version: string;
|
readonly version: string;
|
||||||
}) {
|
}) {
|
||||||
|
'use cache';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs className="flex flex-col gap-4">
|
<Tabs className="flex flex-col gap-4">
|
||||||
<TabList className="flex gap-2">
|
<TabList className="flex gap-2">
|
||||||
{node.overloads.map((overload: any) => (
|
{node.overloads.map((overload: any) => (
|
||||||
<Tab
|
<Tab
|
||||||
className="cursor-pointer rounded-full bg-neutral-800/10 px-2 py-1 font-sans text-sm font-normal leading-none text-neutral-800 hover:bg-neutral-800/20 data-[selected]:bg-neutral-500 data-[selected]:text-neutral-100 dark:bg-neutral-200/10 dark:text-neutral-200 dark:hover:bg-neutral-200/20 dark:data-[selected]:bg-neutral-500/70"
|
className="cursor-pointer rounded-full bg-neutral-800/10 px-2 py-1 font-sans text-sm leading-none font-normal text-neutral-800 hover:bg-neutral-800/20 data-[selected]:bg-neutral-500 data-[selected]:text-neutral-100 dark:bg-neutral-200/10 dark:text-neutral-200 dark:hover:bg-neutral-200/20 dark:data-[selected]:bg-neutral-500/70"
|
||||||
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
||||||
key={`overload-tab-${overload.displayName}-${overload.overloadIndex}`}
|
key={`overload-tab-${overload.displayName}-${overload.overloadIndex}`}
|
||||||
>
|
>
|
||||||
@@ -41,7 +43,7 @@ async function OverloadNode({
|
|||||||
</TabList>
|
</TabList>
|
||||||
{node.overloads.map((overload: any) => (
|
{node.overloads.map((overload: any) => (
|
||||||
<TabPanel
|
<TabPanel
|
||||||
className="flex flex-col gap-8"
|
className="flex flex-col gap-4"
|
||||||
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
||||||
key={`overload-tab-panel-${overload.displayName}-${overload.overloadIndex}`}
|
key={`overload-tab-panel-${overload.displayName}-${overload.overloadIndex}`}
|
||||||
>
|
>
|
||||||
@@ -52,7 +54,7 @@ async function OverloadNode({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DocItem({
|
export async function DocItem({
|
||||||
node,
|
node,
|
||||||
packageName,
|
packageName,
|
||||||
version,
|
version,
|
||||||
@@ -61,6 +63,8 @@ export function DocItem({
|
|||||||
readonly packageName: string;
|
readonly packageName: string;
|
||||||
readonly version: string;
|
readonly version: string;
|
||||||
}) {
|
}) {
|
||||||
|
'use cache';
|
||||||
|
|
||||||
if (node.overloads?.length) {
|
if (node.overloads?.length) {
|
||||||
return <OverloadNode node={node} packageName={packageName} version={version} />;
|
return <OverloadNode node={node} packageName={packageName} version={version} />;
|
||||||
}
|
}
|
||||||
@@ -69,16 +73,13 @@ export function DocItem({
|
|||||||
<>
|
<>
|
||||||
<InformationNode node={node} version={version} />
|
<InformationNode node={node} version={version} />
|
||||||
|
|
||||||
<OverlayScrollbarsComponent
|
<Scrollbars className="border-base-neutral-200 dark:border-base-neutral-600 bg-base-neutral-100 dark:bg-base-neutral-900 rounded-sm border">
|
||||||
className="rounded-md border border-neutral-300 bg-neutral-100 dark:border-neutral-700 dark:bg-neutral-900"
|
<SyntaxHighlighter
|
||||||
defer
|
className="bg-[#f3f3f4] py-4 text-sm dark:bg-[#121214]"
|
||||||
options={{
|
code={node.sourceExcerpt}
|
||||||
overflow: { y: 'hidden' },
|
lang="typescript"
|
||||||
scrollbars: { autoHide: 'scroll', autoHideDelay: 500, autoHideSuspend: true, clickScroll: true },
|
/>
|
||||||
}}
|
</Scrollbars>
|
||||||
>
|
|
||||||
<SyntaxHighlighter className="py-4 text-sm" code={node.sourceExcerpt} lang="typescript" />
|
|
||||||
</OverlayScrollbarsComponent>
|
|
||||||
|
|
||||||
{node.summary?.deprecatedBlock.length ? (
|
{node.summary?.deprecatedBlock.length ? (
|
||||||
<DeprecatedNode deprecatedBlock={node.summary.deprecatedBlock} version={version} />
|
<DeprecatedNode deprecatedBlock={node.summary.deprecatedBlock} version={version} />
|
||||||
@@ -95,7 +96,7 @@ export function DocItem({
|
|||||||
{node.constructor?.parametersString ? <ConstructorNode node={node.constructor} version={version} /> : null}
|
{node.constructor?.parametersString ? <ConstructorNode node={node.constructor} version={version} /> : null}
|
||||||
|
|
||||||
{node.typeParameters?.length ? (
|
{node.typeParameters?.length ? (
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-4">
|
||||||
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">
|
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">
|
||||||
<VscSymbolParameter aria-hidden className="flex-shrink-0" size={24} />
|
<VscSymbolParameter aria-hidden className="flex-shrink-0" size={24} />
|
||||||
Type Parameters
|
Type Parameters
|
||||||
@@ -105,7 +106,7 @@ export function DocItem({
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{node.parameters?.length ? (
|
{node.parameters?.length ? (
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-4">
|
||||||
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">
|
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">
|
||||||
<VscSymbolParameter aria-hidden className="flex-shrink-0" size={24} />
|
<VscSymbolParameter aria-hidden className="flex-shrink-0" size={24} />
|
||||||
Parameters
|
Parameters
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { BuiltinDocumentationLinks } from '~/util/builtinDocumentationLinks';
|
import { BuiltinDocumentationLinks } from '@/util/builtinDocumentationLinks';
|
||||||
import { OverlayScrollbarsComponent } from './OverlayScrollbars';
|
import { Scrollbars } from './OverlayScrollbars';
|
||||||
import { SyntaxHighlighter } from './SyntaxHighlighter';
|
import { SyntaxHighlighter } from './SyntaxHighlighter';
|
||||||
|
|
||||||
export async function DocNode({ node, version }: { readonly node?: any; readonly version: string }) {
|
export async function DocNode({ node, version }: { readonly node?: any; readonly version: string }) {
|
||||||
@@ -12,9 +12,11 @@ export async function DocNode({ node, version }: { readonly node?: any; readonly
|
|||||||
if (node.resolvedPackage) {
|
if (node.resolvedPackage) {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
className="font-mono text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
className="text-base-blurple-400 hover:text-base-blurple-500 dark:hover:text-base-blurple-300 font-mono"
|
||||||
href={`/docs/packages/${node.resolvedPackage.packageName}/${node.resolvedPackage.version ?? version}/${node.uri}`}
|
href={`/docs/packages/${node.resolvedPackage.packageName}/${node.resolvedPackage.version ?? version}/${node.uri}`}
|
||||||
key={`${node.text}-${idx}`}
|
key={`${node.uri}-${idx}`}
|
||||||
|
// @ts-expect-error - unstable_dynamicOnHover is not part of the public types
|
||||||
|
unstable_dynamicOnHover
|
||||||
>
|
>
|
||||||
{node.text}
|
{node.text}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -24,7 +26,7 @@ export async function DocNode({ node, version }: { readonly node?: any; readonly
|
|||||||
if (node.uri) {
|
if (node.uri) {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
className="text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
className="text-base-blurple-400 hover:text-base-blurple-500 dark:hover:text-base-blurple-300"
|
||||||
href={node.uri}
|
href={node.uri}
|
||||||
key={`${node.text}-${idx}`}
|
key={`${node.text}-${idx}`}
|
||||||
rel="external noreferrer noopener"
|
rel="external noreferrer noopener"
|
||||||
@@ -39,7 +41,7 @@ export async function DocNode({ node, version }: { readonly node?: any; readonly
|
|||||||
const href = BuiltinDocumentationLinks[node.text as keyof typeof BuiltinDocumentationLinks];
|
const href = BuiltinDocumentationLinks[node.text as keyof typeof BuiltinDocumentationLinks];
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
className="text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
className="text-base-blurple-400 hover:text-base-blurple-500 dark:hover:text-base-blurple-300"
|
||||||
href={href}
|
href={href}
|
||||||
key={`${node.text}-${idx}`}
|
key={`${node.text}-${idx}`}
|
||||||
rel="external noreferrer noopener"
|
rel="external noreferrer noopener"
|
||||||
@@ -64,16 +66,13 @@ export async function DocNode({ node, version }: { readonly node?: any; readonly
|
|||||||
const { language, text } = node;
|
const { language, text } = node;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OverlayScrollbarsComponent
|
<Scrollbars
|
||||||
className="my-4 rounded-md border border-neutral-300 bg-neutral-100 dark:border-neutral-700 dark:bg-neutral-900"
|
className="border-base-neutral-200 dark:border-base-neutral-600 bg-base-neutral-100 dark:bg-base-neutral-900 my-4 rounded-sm border"
|
||||||
defer
|
defer
|
||||||
options={{
|
key={`${language}-${text}-${idx}`}
|
||||||
overflow: { y: 'hidden' },
|
|
||||||
scrollbars: { autoHide: 'scroll', autoHideDelay: 500, autoHideSuspend: true, clickScroll: true },
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<SyntaxHighlighter className="py-4 text-sm " code={text} lang={language} />
|
<SyntaxHighlighter className="bg-[#f3f3f4] py-4 text-sm dark:bg-[#121214]" code={text} lang={language} />
|
||||||
</OverlayScrollbarsComponent>
|
</Scrollbars>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { VscSymbolEnumMember } from '@react-icons/all-files/vsc/VscSymbolEnumMem
|
|||||||
import { Code2, LinkIcon } from 'lucide-react';
|
import { Code2, LinkIcon } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { ENV } from '~/util/env';
|
import { ENV } from '@/util/env';
|
||||||
import { Badges } from './Badges';
|
import { Badges } from './Badges';
|
||||||
import { DeprecatedNode } from './DeprecatedNode';
|
import { DeprecatedNode } from './DeprecatedNode';
|
||||||
import { ExampleNode } from './ExampleNode';
|
import { ExampleNode } from './ExampleNode';
|
||||||
@@ -23,25 +23,25 @@ export async function EnumMemberNode({
|
|||||||
readonly version: string;
|
readonly version: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-4">
|
||||||
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">
|
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">
|
||||||
<VscSymbolEnumMember aria-hidden className="flex-shrink-0" size={24} />
|
<VscSymbolEnumMember aria-hidden className="flex-shrink-0" size={24} />
|
||||||
Members
|
Members
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-4">
|
||||||
{node.map((enumMember: any, idx: number) => (
|
{node.map((enumMember: any, idx: number) => (
|
||||||
<Fragment key={`${enumMember.displayName}-${idx}`}>
|
<Fragment key={`${enumMember.displayName}-${idx}`}>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex place-content-between place-items-center">
|
<div className="flex place-content-between place-items-center gap-1">
|
||||||
<h3
|
<h3
|
||||||
className={`${ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-16' : 'scroll-mt-8'} group break-words font-mono font-semibold`}
|
className={`${ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-16' : 'scroll-mt-8'} group px-2 font-mono font-semibold break-all`}
|
||||||
id={enumMember.displayName}
|
id={enumMember.displayName}
|
||||||
>
|
>
|
||||||
<Badges node={enumMember} />
|
<Badges node={enumMember} />
|
||||||
<span>
|
<span>
|
||||||
<Link
|
<Link
|
||||||
className="float-left -ml-6 hidden pb-2 pr-2 group-hover:block"
|
className="float-left -ml-6 hidden pr-2 pb-2 group-hover:block"
|
||||||
href={`#${enumMember.displayName}`}
|
href={`#${enumMember.displayName}`}
|
||||||
>
|
>
|
||||||
<LinkIcon aria-hidden size={16} />
|
<LinkIcon aria-hidden size={16} />
|
||||||
@@ -100,7 +100,7 @@ export async function EnumMemberNode({
|
|||||||
<SeeNode node={enumMember.summary.seeBlocks} padding version={version} />
|
<SeeNode node={enumMember.summary.seeBlocks} padding version={version} />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div aria-hidden className="px-4">
|
<div aria-hidden className="p-4">
|
||||||
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { VscSymbolEvent } from '@react-icons/all-files/vsc/VscSymbolEvent';
|
import { VscSymbolEvent } from '@react-icons/all-files/vsc/VscSymbolEvent';
|
||||||
import { ChevronDown, ChevronUp, Code2, LinkIcon } from 'lucide-react';
|
import { ChevronDown, ChevronUp, Code2, LinkIcon } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { ENV } from '~/util/env';
|
import { ENV } from '@/util/env';
|
||||||
import { Badges } from './Badges';
|
import { Badges } from './Badges';
|
||||||
import { DeprecatedNode } from './DeprecatedNode';
|
import { DeprecatedNode } from './DeprecatedNode';
|
||||||
import { ExampleNode } from './ExampleNode';
|
import { ExampleNode } from './ExampleNode';
|
||||||
@@ -28,14 +28,14 @@ async function EventBodyNode({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex place-content-between place-items-center">
|
<div className="flex place-content-between place-items-center gap-1">
|
||||||
<h3
|
<h3
|
||||||
className={`${overload ? (ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-24' : 'scroll-mt-16') : ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-16' : 'scroll-mt-8'} group break-words font-mono font-semibold`}
|
className={`${overload ? (ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-24' : 'scroll-mt-16') : ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-16' : 'scroll-mt-8'} group px-2 font-mono font-semibold break-all`}
|
||||||
id={event.displayName}
|
id={event.displayName}
|
||||||
>
|
>
|
||||||
<Badges node={event} /> {event.displayName}
|
<Badges node={event} /> {event.displayName}
|
||||||
<span>
|
<span>
|
||||||
<Link className="float-left -ml-6 hidden pb-2 pr-2 group-hover:block" href={`#${event.displayName}`}>
|
<Link className="float-left -ml-6 hidden pr-2 pb-2 group-hover:block" href={`#${event.displayName}`}>
|
||||||
<LinkIcon aria-hidden size={16} />
|
<LinkIcon aria-hidden size={16} />
|
||||||
</Link>
|
</Link>
|
||||||
{event.typeParameters?.length ? (
|
{event.typeParameters?.length ? (
|
||||||
@@ -86,7 +86,7 @@ async function EventBodyNode({
|
|||||||
|
|
||||||
{event.summary?.seeBlocks.length ? <SeeNode node={event.summary.seeBlocks} padding version={version} /> : null}
|
{event.summary?.seeBlocks.length ? <SeeNode node={event.summary.seeBlocks} padding version={version} /> : null}
|
||||||
</div>
|
</div>
|
||||||
<div aria-hidden className="px-4">
|
<div aria-hidden className="p-4">
|
||||||
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -107,7 +107,7 @@ async function OverloadNode({
|
|||||||
<TabList className="flex gap-2">
|
<TabList className="flex gap-2">
|
||||||
{event.overloads.map((overload: any) => (
|
{event.overloads.map((overload: any) => (
|
||||||
<Tab
|
<Tab
|
||||||
className="cursor-pointer rounded-full bg-neutral-800/10 px-2 py-1 font-sans text-sm font-normal leading-none text-neutral-800 hover:bg-neutral-800/20 data-[selected]:bg-neutral-500 data-[selected]:text-neutral-100 dark:bg-neutral-200/10 dark:text-neutral-200 dark:hover:bg-neutral-200/20 dark:data-[selected]:bg-neutral-500/70"
|
className="cursor-pointer rounded-full bg-neutral-800/10 px-2 py-1 font-sans text-sm leading-none font-normal text-neutral-800 hover:bg-neutral-800/20 data-[selected]:bg-neutral-500 data-[selected]:text-neutral-100 dark:bg-neutral-200/10 dark:text-neutral-200 dark:hover:bg-neutral-200/20 dark:data-[selected]:bg-neutral-500/70"
|
||||||
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
||||||
key={`overload-tab-${overload.displayName}-${overload.overloadIndex}`}
|
key={`overload-tab-${overload.displayName}-${overload.overloadIndex}`}
|
||||||
>
|
>
|
||||||
@@ -117,7 +117,7 @@ async function OverloadNode({
|
|||||||
</TabList>
|
</TabList>
|
||||||
{event.overloads.map((overload: any) => (
|
{event.overloads.map((overload: any) => (
|
||||||
<TabPanel
|
<TabPanel
|
||||||
className="flex flex-col gap-8"
|
className="flex flex-col gap-4"
|
||||||
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
||||||
key={`overload-tab-panel-${overload.displayName}-${overload.overloadIndex}`}
|
key={`overload-tab-panel-${overload.displayName}-${overload.overloadIndex}`}
|
||||||
>
|
>
|
||||||
@@ -138,8 +138,8 @@ export async function EventNode({
|
|||||||
readonly version: string;
|
readonly version: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Collapsible className="flex flex-col gap-8" defaultOpen>
|
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
||||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
|
||||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||||
<VscSymbolEvent aria-hidden className="flex-shrink-0" size={24} /> Events
|
<VscSymbolEvent aria-hidden className="flex-shrink-0" size={24} /> Events
|
||||||
</h2>
|
</h2>
|
||||||
@@ -148,7 +148,7 @@ export async function EventNode({
|
|||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-4">
|
||||||
{node.map((event: any) =>
|
{node.map((event: any) =>
|
||||||
event.overloads?.length ? (
|
event.overloads?.length ? (
|
||||||
<OverloadNode
|
<OverloadNode
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { DocNode } from './DocNode';
|
|||||||
|
|
||||||
export async function ExampleNode({ node, version }: { readonly node: any; readonly version: string }) {
|
export async function ExampleNode({ node, version }: { readonly node: any; readonly version: string }) {
|
||||||
return (
|
return (
|
||||||
<div className="break-words pl-4">
|
<div className="pl-4 break-words">
|
||||||
<span className="font-semibold">Examples:</span>
|
<span className="font-semibold">Examples:</span>
|
||||||
<DocNode node={node} version={version} />
|
<DocNode node={node} version={version} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { BuiltinDocumentationLinks } from '~/util/builtinDocumentationLinks';
|
import { BuiltinDocumentationLinks } from '@/util/builtinDocumentationLinks';
|
||||||
|
|
||||||
export async function ExcerptNode({ node, version }: { readonly node?: any; readonly version: string }) {
|
export async function ExcerptNode({ node, version }: { readonly node?: any; readonly version: string }) {
|
||||||
const createExcerpt = (excerpts: any) => {
|
const createExcerpt = (excerpts: any, idx: number) => {
|
||||||
const excerpt = Array.isArray(excerpts) ? excerpts : (excerpts.excerpts ?? [excerpts]);
|
const excerpt = Array.isArray(excerpts) ? excerpts : (excerpts.excerpts ?? [excerpts]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -13,14 +13,17 @@ export async function ExcerptNode({ node, version }: { readonly node?: any; read
|
|||||||
? 'after:content-[",_"] last-of-type:after:content-none'
|
? 'after:content-[",_"] last-of-type:after:content-none'
|
||||||
: ''
|
: ''
|
||||||
}
|
}
|
||||||
|
key={`${excerpt.text}-${idx}`}
|
||||||
>
|
>
|
||||||
{excerpt.map((excerpt: any, idx: number) => {
|
{excerpt.map((excerpt: any, idx: number) => {
|
||||||
if (excerpt.resolvedItem) {
|
if (excerpt.resolvedItem) {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
className="text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
className="text-base-blurple-400 hover:text-base-blurple-500 dark:hover:text-base-blurple-300"
|
||||||
href={`/docs/packages/${excerpt.resolvedItem.packageName}/${excerpt.resolvedItem.version ?? version}/${excerpt.resolvedItem.uri}`}
|
href={`/docs/packages/${excerpt.resolvedItem.packageName}/${excerpt.resolvedItem.version ?? version}/${excerpt.resolvedItem.uri}`}
|
||||||
key={`${excerpt.resolvedItem.displayName}-${idx}`}
|
key={`${excerpt.resolvedItem.displayName}-${idx}`}
|
||||||
|
// @ts-expect-error - unstable_dynamicOnHover is not part of the public types
|
||||||
|
unstable_dynamicOnHover
|
||||||
>
|
>
|
||||||
{excerpt.text}
|
{excerpt.text}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -30,7 +33,7 @@ export async function ExcerptNode({ node, version }: { readonly node?: any; read
|
|||||||
if (excerpt.href) {
|
if (excerpt.href) {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
className="text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
className="text-base-blurple-400 hover:text-base-blurple-500 dark:hover:text-base-blurple-300"
|
||||||
href={excerpt.href}
|
href={excerpt.href}
|
||||||
key={`${excerpt.text}-${idx}`}
|
key={`${excerpt.text}-${idx}`}
|
||||||
rel="external noreferrer noopener"
|
rel="external noreferrer noopener"
|
||||||
@@ -43,9 +46,10 @@ export async function ExcerptNode({ node, version }: { readonly node?: any; read
|
|||||||
|
|
||||||
if (excerpt.text in BuiltinDocumentationLinks) {
|
if (excerpt.text in BuiltinDocumentationLinks) {
|
||||||
const href = BuiltinDocumentationLinks[excerpt.text as keyof typeof BuiltinDocumentationLinks];
|
const href = BuiltinDocumentationLinks[excerpt.text as keyof typeof BuiltinDocumentationLinks];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
className="text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
className="text-base-blurple-400 hover:text-base-blurple-500 dark:hover:text-base-blurple-300"
|
||||||
href={href}
|
href={href}
|
||||||
key={`${excerpt.text}-${idx}`}
|
key={`${excerpt.text}-${idx}`}
|
||||||
rel="external noreferrer noopener"
|
rel="external noreferrer noopener"
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import vercelLogo from '~/assets/powered-by-vercel.svg';
|
import vercelLogo from '@/assets/powered-by-vercel.svg';
|
||||||
import workersLogo from '~/assets/powered-by-workers.png';
|
import workersLogo from '@/assets/powered-by-workers.png';
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
return (
|
return (
|
||||||
<footer className="md:pl-12 md:pr-12">
|
<footer className="md:pr-12 md:pl-12">
|
||||||
<div className="flex flex-col flex-wrap place-content-center gap-6 pt-12 sm:flex-row md:gap-12">
|
<div className="flex flex-col flex-wrap place-content-center gap-6 pt-12 sm:flex-row md:gap-12">
|
||||||
<div className="flex flex-wrap place-content-center place-items-center gap-4">
|
<div className="flex flex-wrap place-content-center place-items-center gap-4">
|
||||||
<a
|
<a
|
||||||
@@ -5,10 +5,10 @@ import { InheritanceNode } from './InheritanceNode';
|
|||||||
|
|
||||||
export async function InformationNode({ node, version }: { readonly node: any; readonly version: string }) {
|
export async function InformationNode({ node, version }: { readonly node: any; readonly version: string }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex place-content-between place-items-center">
|
<div className="flex place-content-between place-items-center gap-1">
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<h1 className="text-xl">
|
<h1 className="text-xl">
|
||||||
<DocKind node={node} /> <span className="break-words font-bold">{node.displayName}</span>
|
<DocKind node={node} /> <span className="font-bold break-all">{node.displayName}</span>
|
||||||
</h1>
|
</h1>
|
||||||
{node.implements ? <InheritanceNode node={node.implements} text="implements" version={version} /> : null}
|
{node.implements ? <InheritanceNode node={node.implements} text="implements" version={version} /> : null}
|
||||||
{node.extends ? <InheritanceNode node={node.extends} text="extends" version={version} /> : null}
|
{node.extends ? <InheritanceNode node={node.extends} text="extends" version={version} /> : null}
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ export async function InheritanceNode({
|
|||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2 className="inline-block min-w-min text-sm italic text-neutral-500 dark:text-neutral-400">{text}</h2>{' '}
|
<h2 className="inline-block min-w-min text-sm text-neutral-500 italic dark:text-neutral-400">{text}</h2>{' '}
|
||||||
<span className="break-words font-mono text-sm">
|
<span className="font-mono text-sm break-all">
|
||||||
<ExcerptNode node={node} version={version} />
|
<ExcerptNode node={node} version={version} />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ export async function InheritedFromNode({
|
|||||||
readonly version: string;
|
readonly version: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<p className="break-words pl-4">
|
<p className="pl-4 break-words">
|
||||||
<span className="font-semibold">Inherited from:</span>{' '}
|
<span className="font-semibold">Inherited from:</span>{' '}
|
||||||
<Link
|
<Link
|
||||||
className="font-mono text-blurple hover:text-blurple-500 dark:hover:text-blurple-300"
|
className="text-base-blurple-400 hover:text-base-blurple-500 dark:hover:text-base-blurple-300 font-mono"
|
||||||
href={`/docs/packages/${packageName}/${version}/${node}`}
|
href={`/docs/packages/${packageName}/${version}/${node}`}
|
||||||
|
// @ts-expect-error - unstable_dynamicOnHover is not part of the public types
|
||||||
|
unstable_dynamicOnHover
|
||||||
>
|
>
|
||||||
{node.slice(0, node.indexOf(':'))}
|
{node.slice(0, node.indexOf(':'))}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ export function InstallButton({ className = '' }: { readonly className?: string
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={`cursor-copy rounded-md border border-neutral-300 bg-white px-4 py-2 font-mono hover:bg-neutral-200 dark:border-neutral-700 dark:bg-transparent dark:hover:bg-neutral-800 ${className}`}
|
className={`cursor-copy rounded-sm border border-neutral-300 bg-white px-4 py-2 font-mono hover:bg-neutral-200 dark:border-neutral-700 dark:bg-transparent dark:hover:bg-neutral-800 ${className}`}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setInteracted(true);
|
setInteracted(true);
|
||||||
await copyToClipboard('npm install discord.js');
|
await copyToClipboard('npm install discord.js');
|
||||||
}}
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span className="font-semibold text-blurple">{'>'}</span> npm install discord.js{' '}
|
<span className="text-base-blurple-400 font-semibold">{'>'}</span> npm install discord.js{' '}
|
||||||
{copiedText && interacted ? (
|
{copiedText && interacted ? (
|
||||||
<CopyCheck aria-hidden className="ml-1 inline-block text-green-500" size={20} />
|
<CopyCheck aria-hidden className="ml-1 inline-block text-green-500" size={20} />
|
||||||
) : (
|
) : (
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
|
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
|
||||||
import { ChevronDown, ChevronUp, Code2, LinkIcon } from 'lucide-react';
|
import { ChevronDown, ChevronUp, Code2, LinkIcon } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { ENV } from '~/util/env';
|
import { ENV } from '@/util/env';
|
||||||
import { Badges } from './Badges';
|
import { Badges } from './Badges';
|
||||||
import { DeprecatedNode } from './DeprecatedNode';
|
import { DeprecatedNode } from './DeprecatedNode';
|
||||||
import { ExampleNode } from './ExampleNode';
|
import { ExampleNode } from './ExampleNode';
|
||||||
@@ -29,14 +29,14 @@ async function MethodBodyNode({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex place-content-between place-items-center">
|
<div className="flex place-content-between place-items-center gap-1">
|
||||||
<h3
|
<h3
|
||||||
className={`${overload ? (ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-24' : 'scroll-mt-16') : ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-16' : 'scroll-mt-8'} group break-words font-mono font-semibold`}
|
className={`${overload ? (ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-24' : 'scroll-mt-16') : ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-16' : 'scroll-mt-8'} group px-2 font-mono font-semibold break-all`}
|
||||||
id={method.displayName}
|
id={method.displayName}
|
||||||
>
|
>
|
||||||
<Badges node={method} /> {method.displayName}
|
<Badges node={method} /> {method.displayName}
|
||||||
<span>
|
<span>
|
||||||
<Link className="float-left -ml-6 hidden pb-2 pr-2 group-hover:block" href={`#${method.displayName}`}>
|
<Link className="float-left -ml-6 hidden pr-2 pb-2 group-hover:block" href={`#${method.displayName}`}>
|
||||||
<LinkIcon aria-hidden size={16} />
|
<LinkIcon aria-hidden size={16} />
|
||||||
</Link>
|
</Link>
|
||||||
{method.typeParameters?.length ? (
|
{method.typeParameters?.length ? (
|
||||||
@@ -90,7 +90,7 @@ async function MethodBodyNode({
|
|||||||
<SeeNode node={method.summary.seeBlocks} padding version={version} />
|
<SeeNode node={method.summary.seeBlocks} padding version={version} />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div aria-hidden className="px-4">
|
<div aria-hidden className="p-4">
|
||||||
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -111,7 +111,7 @@ async function OverloadNode({
|
|||||||
<TabList className="flex gap-2">
|
<TabList className="flex gap-2">
|
||||||
{method.overloads.map((overload: any) => (
|
{method.overloads.map((overload: any) => (
|
||||||
<Tab
|
<Tab
|
||||||
className="cursor-pointer rounded-full bg-neutral-800/10 px-2 py-1 font-sans text-sm font-normal leading-none text-neutral-800 hover:bg-neutral-800/20 data-[selected]:bg-neutral-500 data-[selected]:text-neutral-100 dark:bg-neutral-200/10 dark:text-neutral-200 dark:hover:bg-neutral-200/20 dark:data-[selected]:bg-neutral-500/70"
|
className="cursor-pointer rounded-full bg-neutral-800/10 px-2 py-1 font-sans text-sm leading-none font-normal text-neutral-800 hover:bg-neutral-800/20 data-[selected]:bg-neutral-500 data-[selected]:text-neutral-100 dark:bg-neutral-200/10 dark:text-neutral-200 dark:hover:bg-neutral-200/20 dark:data-[selected]:bg-neutral-500/70"
|
||||||
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
||||||
key={`overload-tab-${overload.displayName}-${overload.overloadIndex}`}
|
key={`overload-tab-${overload.displayName}-${overload.overloadIndex}`}
|
||||||
>
|
>
|
||||||
@@ -121,7 +121,7 @@ async function OverloadNode({
|
|||||||
</TabList>
|
</TabList>
|
||||||
{method.overloads.map((overload: any) => (
|
{method.overloads.map((overload: any) => (
|
||||||
<TabPanel
|
<TabPanel
|
||||||
className="flex flex-col gap-8"
|
className="flex flex-col gap-4"
|
||||||
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
id={`overload-${overload.displayName}-${overload.overloadIndex}`}
|
||||||
key={`overload-tab-panel-${overload.displayName}-${overload.overloadIndex}`}
|
key={`overload-tab-panel-${overload.displayName}-${overload.overloadIndex}`}
|
||||||
>
|
>
|
||||||
@@ -142,8 +142,8 @@ export async function MethodNode({
|
|||||||
readonly version: string;
|
readonly version: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Collapsible className="flex flex-col gap-8" defaultOpen>
|
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
||||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
|
||||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||||
<VscSymbolMethod aria-hidden className="flex-shrink-0" size={24} /> Methods
|
<VscSymbolMethod aria-hidden className="flex-shrink-0" size={24} /> Methods
|
||||||
</h2>
|
</h2>
|
||||||
@@ -152,7 +152,7 @@ export async function MethodNode({
|
|||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-4">
|
||||||
{node.map((method: any) =>
|
{node.map((method: any) =>
|
||||||
method.overloads?.length ? (
|
method.overloads?.length ? (
|
||||||
<OverloadNode
|
<OverloadNode
|
||||||
|
|||||||
@@ -1,41 +1,17 @@
|
|||||||
import { VscGithubInverted } from '@react-icons/all-files/vsc/VscGithubInverted';
|
|
||||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
import { ChevronDown, ChevronUp } from 'lucide-react';
|
||||||
import dynamic from 'next/dynamic';
|
|
||||||
import Link from 'next/link';
|
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
import { fetchSitemap } from '~/util/fetchSitemap';
|
import { fetchSitemap } from '@/util/fetchSitemap';
|
||||||
import { fetchVersions } from '~/util/fetchVersions';
|
|
||||||
import { resolveNodeKind } from './DocKind';
|
import { resolveNodeKind } from './DocKind';
|
||||||
import { NavigationItem } from './NavigationItem';
|
import { NavigationItem } from './NavigationItem';
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/Collapsible';
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from './ui/Collapsible';
|
||||||
import { PackageSelect } from './ui/PackageSelect';
|
|
||||||
import { SearchButton } from './ui/SearchButton';
|
|
||||||
import { VersionSelect } from './ui/VersionSelect';
|
|
||||||
|
|
||||||
// eslint-disable-next-line promise/prefer-await-to-then
|
export async function Navigation({ packageName, version }: { readonly packageName: string; readonly version: string }) {
|
||||||
const ThemeSwitch = dynamic(async () => import('~/components/ui/ThemeSwitch').then((mod) => mod.ThemeSwitch), {
|
|
||||||
ssr: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function Navigation({
|
|
||||||
className = '',
|
|
||||||
packageName,
|
|
||||||
version,
|
|
||||||
drawer = false,
|
|
||||||
}: {
|
|
||||||
readonly className?: string;
|
|
||||||
readonly drawer?: boolean;
|
|
||||||
readonly packageName: string;
|
|
||||||
readonly version: string;
|
|
||||||
}) {
|
|
||||||
const node = await fetchSitemap({ packageName, version });
|
const node = await fetchSitemap({ packageName, version });
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
const versions = await fetchVersions(packageName);
|
|
||||||
|
|
||||||
const groupedNodes = node.reduce((acc: any, node: any) => {
|
const groupedNodes = node.reduce((acc: any, node: any) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
(acc[node.kind.toLowerCase()] ||= []).push(node);
|
(acc[node.kind.toLowerCase()] ||= []).push(node);
|
||||||
@@ -43,187 +19,156 @@ export async function Navigation({
|
|||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={`flex min-w-52 max-w-52 flex-col gap-2 lg:min-w-72 lg:max-w-72 ${className}`}>
|
<nav className="flex flex-col gap-2 pr-3">
|
||||||
<div
|
{groupedNodes.class?.length ? (
|
||||||
className={`sticky top-0 flex flex-col gap-4 pb-4 ${drawer ? 'bg-neutral-100 dark:bg-neutral-900' : 'bg-white dark:bg-[#121212]'}`}
|
<Collapsible className="flex flex-col gap-2" defaultOpen>
|
||||||
>
|
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
|
||||||
<div className="flex flex-col gap-2 pt-px">
|
<h4 className="font-semibold">Classes</h4>
|
||||||
<div className="flex place-content-between place-items-center p-1">
|
<ChevronDown aria-hidden className='group-data-[state="open"]:hidden' size={24} />
|
||||||
<Link className="text-xl font-bold" href={`/docs/packages/${packageName}/${version}`}>
|
<ChevronUp aria-hidden className='group-data-[state="closed"]:hidden' size={24} />
|
||||||
{packageName}
|
</CollapsibleTrigger>
|
||||||
</Link>
|
<CollapsibleContent>
|
||||||
<div className="flex gap-2">
|
<div className="flex flex-col">
|
||||||
<Link
|
{groupedNodes.class.map((node: any, idx: number) => {
|
||||||
aria-label="GitHub"
|
const kind = resolveNodeKind(node.kind);
|
||||||
className="rounded-full"
|
return (
|
||||||
href="https://github.com/discordjs/discord.js"
|
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
||||||
rel="external noopener noreferrer"
|
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
||||||
target="_blank"
|
{node.kind[0]}
|
||||||
>
|
</div>{' '}
|
||||||
<VscGithubInverted aria-hidden size={24} />
|
<span className="font-sans">{node.name}</span>
|
||||||
</Link>
|
</NavigationItem>
|
||||||
<ThemeSwitch />
|
);
|
||||||
|
})}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</CollapsibleContent>
|
||||||
<PackageSelect packageName={packageName} />
|
</Collapsible>
|
||||||
{/* <h3 className="p-1 text-lg font-semibold">{version}</h3> */}
|
) : null}
|
||||||
<VersionSelect packageName={packageName} version={version} versions={versions} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SearchButton />
|
{groupedNodes.function?.length ? (
|
||||||
</div>
|
<Collapsible className="flex flex-col gap-2" defaultOpen>
|
||||||
|
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
|
||||||
|
<h4 className="font-semibold">Functions</h4>
|
||||||
|
<ChevronDown aria-hidden className='group-data-[state="open"]:hidden' size={24} />
|
||||||
|
<ChevronUp aria-hidden className='group-data-[state="closed"]:hidden' size={24} />
|
||||||
|
</CollapsibleTrigger>
|
||||||
|
<CollapsibleContent>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
{groupedNodes.function.map((node: any, idx: number) => {
|
||||||
|
const kind = resolveNodeKind(node.kind);
|
||||||
|
return (
|
||||||
|
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
||||||
|
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
||||||
|
{node.kind[0]}
|
||||||
|
</div>{' '}
|
||||||
|
<span className="font-sans">{node.name}</span>
|
||||||
|
</NavigationItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</CollapsibleContent>
|
||||||
|
</Collapsible>
|
||||||
|
) : null}
|
||||||
|
|
||||||
<nav className="flex flex-col gap-4">
|
{groupedNodes.enum?.length ? (
|
||||||
{groupedNodes.class?.length ? (
|
<Collapsible className="flex flex-col gap-2" defaultOpen>
|
||||||
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
|
||||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
<h4 className="font-semibold">Enums</h4>
|
||||||
<h4 className="font-semibold">Classes</h4>
|
<ChevronDown aria-hidden className='group-data-[state="open"]:hidden' size={24} />
|
||||||
<ChevronDown aria-hidden className='group-data-[state="open"]:hidden' size={24} />
|
<ChevronUp aria-hidden className='group-data-[state="closed"]:hidden' size={24} />
|
||||||
<ChevronUp aria-hidden className='group-data-[state="closed"]:hidden' size={24} />
|
</CollapsibleTrigger>
|
||||||
</CollapsibleTrigger>
|
<CollapsibleContent>
|
||||||
<CollapsibleContent>
|
<div className="flex flex-col">
|
||||||
<div className="flex flex-col gap-1.5">
|
{groupedNodes.enum.map((node: any, idx: number) => {
|
||||||
{groupedNodes.class.map((node: any, idx: number) => {
|
const kind = resolveNodeKind(node.kind);
|
||||||
const kind = resolveNodeKind(node.kind);
|
return (
|
||||||
return (
|
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
||||||
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
||||||
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
{node.kind[0]}
|
||||||
{node.kind[0]}
|
</div>{' '}
|
||||||
</div>{' '}
|
<span className="font-sans">{node.name}</span>
|
||||||
<span className="font-sans">{node.name}</span>
|
</NavigationItem>
|
||||||
</NavigationItem>
|
);
|
||||||
);
|
})}
|
||||||
})}
|
</div>
|
||||||
</div>
|
</CollapsibleContent>
|
||||||
</CollapsibleContent>
|
</Collapsible>
|
||||||
</Collapsible>
|
) : null}
|
||||||
) : null}
|
|
||||||
|
|
||||||
{groupedNodes.function?.length ? (
|
{groupedNodes.interface?.length ? (
|
||||||
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
<Collapsible className="flex flex-col gap-2" defaultOpen>
|
||||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
|
||||||
<h4 className="font-semibold">Functions</h4>
|
<h4 className="font-semibold">Interfaces</h4>
|
||||||
<ChevronDown aria-hidden className='group-data-[state="open"]:hidden' size={24} />
|
<ChevronDown aria-hidden className='group-data-[state="open"]:hidden' size={24} />
|
||||||
<ChevronUp aria-hidden className='group-data-[state="closed"]:hidden' size={24} />
|
<ChevronUp aria-hidden className='group-data-[state="closed"]:hidden' size={24} />
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col">
|
||||||
{groupedNodes.function.map((node: any, idx: number) => {
|
{groupedNodes.interface.map((node: any, idx: number) => {
|
||||||
const kind = resolveNodeKind(node.kind);
|
const kind = resolveNodeKind(node.kind);
|
||||||
return (
|
return (
|
||||||
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
||||||
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
||||||
{node.kind[0]}
|
{node.kind[0]}
|
||||||
</div>{' '}
|
</div>{' '}
|
||||||
<span className="font-sans">{node.name}</span>
|
<span className="font-sans">{node.name}</span>
|
||||||
</NavigationItem>
|
</NavigationItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{groupedNodes.enum?.length ? (
|
{groupedNodes.typealias?.length ? (
|
||||||
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
<Collapsible className="flex flex-col gap-2" defaultOpen>
|
||||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
|
||||||
<h4 className="font-semibold">Enums</h4>
|
<h4 className="font-semibold">Types</h4>
|
||||||
<ChevronDown aria-hidden className='group-data-[state="open"]:hidden' size={24} />
|
<ChevronDown aria-hidden className='group-data-[state="open"]:hidden' size={24} />
|
||||||
<ChevronUp aria-hidden className='group-data-[state="closed"]:hidden' size={24} />
|
<ChevronUp aria-hidden className='group-data-[state="closed"]:hidden' size={24} />
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col">
|
||||||
{groupedNodes.enum.map((node: any, idx: number) => {
|
{groupedNodes.typealias.map((node: any, idx: number) => {
|
||||||
const kind = resolveNodeKind(node.kind);
|
const kind = resolveNodeKind(node.kind);
|
||||||
return (
|
return (
|
||||||
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
||||||
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
||||||
{node.kind[0]}
|
{node.kind[0]}
|
||||||
</div>{' '}
|
</div>{' '}
|
||||||
<span className="font-sans">{node.name}</span>
|
<span className="font-sans">{node.name}</span>
|
||||||
</NavigationItem>
|
</NavigationItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{groupedNodes.interface?.length ? (
|
{groupedNodes.variable?.length ? (
|
||||||
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
<Collapsible className="flex flex-col gap-2" defaultOpen>
|
||||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
|
||||||
<h4 className="font-semibold">Interfaces</h4>
|
<h4 className="font-semibold">Variables</h4>
|
||||||
<ChevronDown aria-hidden className='group-data-[state="open"]:hidden' size={24} />
|
<ChevronDown aria-hidden className='group-data-[state="open"]:hidden' size={24} />
|
||||||
<ChevronUp aria-hidden className='group-data-[state="closed"]:hidden' size={24} />
|
<ChevronUp aria-hidden className='group-data-[state="closed"]:hidden' size={24} />
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col">
|
||||||
{groupedNodes.interface.map((node: any, idx: number) => {
|
{groupedNodes.variable.map((node: any, idx: number) => {
|
||||||
const kind = resolveNodeKind(node.kind);
|
const kind = resolveNodeKind(node.kind);
|
||||||
return (
|
return (
|
||||||
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
||||||
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
||||||
{node.kind[0]}
|
{node.kind[0]}
|
||||||
</div>{' '}
|
</div>{' '}
|
||||||
<span className="font-sans">{node.name}</span>
|
<span className="font-sans">{node.name}</span>
|
||||||
</NavigationItem>
|
</NavigationItem>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
</CollapsibleContent>
|
</CollapsibleContent>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
) : null}
|
) : null}
|
||||||
|
</nav>
|
||||||
{groupedNodes.typealias?.length ? (
|
|
||||||
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
|
||||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
|
||||||
<h4 className="font-semibold">Types</h4>
|
|
||||||
<ChevronDown aria-hidden className='group-data-[state="open"]:hidden' size={24} />
|
|
||||||
<ChevronUp aria-hidden className='group-data-[state="closed"]:hidden' size={24} />
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
<CollapsibleContent>
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
{groupedNodes.typealias.map((node: any, idx: number) => {
|
|
||||||
const kind = resolveNodeKind(node.kind);
|
|
||||||
return (
|
|
||||||
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
|
||||||
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
|
||||||
{node.kind[0]}
|
|
||||||
</div>{' '}
|
|
||||||
<span className="font-sans">{node.name}</span>
|
|
||||||
</NavigationItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</CollapsibleContent>
|
|
||||||
</Collapsible>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
{groupedNodes.variable?.length ? (
|
|
||||||
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
|
||||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
|
||||||
<h4 className="font-semibold">Variables</h4>
|
|
||||||
<ChevronDown aria-hidden className='group-data-[state="open"]:hidden' size={24} />
|
|
||||||
<ChevronUp aria-hidden className='group-data-[state="closed"]:hidden' size={24} />
|
|
||||||
</CollapsibleTrigger>
|
|
||||||
<CollapsibleContent>
|
|
||||||
<div className="flex flex-col gap-1.5">
|
|
||||||
{groupedNodes.variable.map((node: any, idx: number) => {
|
|
||||||
const kind = resolveNodeKind(node.kind);
|
|
||||||
return (
|
|
||||||
<NavigationItem key={`${node.name}-${idx}`} node={node} packageName={packageName} version={version}>
|
|
||||||
<div className={`inline-block h-6 w-6 rounded-full text-center ${kind.background} ${kind.text}`}>
|
|
||||||
{node.kind[0]}
|
|
||||||
</div>{' '}
|
|
||||||
<span className="font-sans">{node.name}</span>
|
|
||||||
</NavigationItem>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</CollapsibleContent>
|
|
||||||
</Collapsible>
|
|
||||||
) : null}
|
|
||||||
</nav>
|
|
||||||
</aside>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname, useRouter } from 'next/navigation';
|
||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { isDrawerOpenAtom } from '~/stores/drawer';
|
import { isDrawerOpenAtom } from '@/stores/drawer';
|
||||||
|
import { cx } from '@/styles/cva';
|
||||||
|
|
||||||
export function NavigationItem({
|
export function NavigationItem({
|
||||||
node,
|
node,
|
||||||
@@ -16,6 +17,7 @@ export function NavigationItem({
|
|||||||
readonly packageName: string;
|
readonly packageName: string;
|
||||||
readonly version: string;
|
readonly version: string;
|
||||||
}>) {
|
}>) {
|
||||||
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const setDrawerOpen = useSetAtom(isDrawerOpenAtom);
|
const setDrawerOpen = useSetAtom(isDrawerOpenAtom);
|
||||||
|
|
||||||
@@ -23,10 +25,19 @@ export function NavigationItem({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
className={`truncate rounded-md p-2 font-mono transition-colors hover:bg-neutral-200 dark:hover:bg-neutral-800 md:px-1 md:py-1 ${pathname === href ? 'bg-neutral-200 font-medium text-blurple dark:bg-neutral-800' : ''}`}
|
className={cx(
|
||||||
|
'dark:hover:text-base-neutral-40 hover:text-base-neutral-900 truncate rounded-lg p-2 font-mono text-[#676771] transition-colors hover:bg-[#e7e7e9] active:bg-[#e4e4e7] md:py-1 dark:text-[#83838b] dark:hover:bg-[#1d1d1e] dark:active:bg-[#27272b]',
|
||||||
|
pathname === href &&
|
||||||
|
'dark:text-base-neutral-40 text-base-neutral-900 bg-[#d9d9dc] font-medium dark:bg-[#323235] dark:hover:bg-[#323235]',
|
||||||
|
)}
|
||||||
href={href}
|
href={href}
|
||||||
onClick={() => setDrawerOpen(false)}
|
onClick={() => setDrawerOpen(false)}
|
||||||
|
onMouseEnter={() => router.prefetch(href)}
|
||||||
|
onTouchStart={() => router.prefetch(href)}
|
||||||
|
prefetch={false}
|
||||||
title={node.name}
|
title={node.name}
|
||||||
|
// @ts-expect-error - unstable_dynamicOnHover is not part of the public types
|
||||||
|
unstable_dynamicOnHover
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ export async function Outline({ node }: { readonly node: any }) {
|
|||||||
const hasAny = node.members?.properties?.length || node.members?.events?.length || node.members?.methods?.length;
|
const hasAny = node.members?.properties?.length || node.members?.events?.length || node.members?.methods?.length;
|
||||||
|
|
||||||
return hasAny ? (
|
return hasAny ? (
|
||||||
<Collapsible className="flex flex-col gap-8" defaultOpen>
|
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
||||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
|
||||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||||
<VscListSelection aria-hidden className="flex-shrink-0" size={24} /> Table of contents
|
<VscListSelection aria-hidden className="flex-shrink-0" size={24} /> Table of contents
|
||||||
</h2>
|
</h2>
|
||||||
@@ -22,11 +22,11 @@ export async function Outline({ node }: { readonly node: any }) {
|
|||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="grid gap-2 sm:grid-cols-2">
|
<div className="grid gap-2 sm:grid-cols-2">
|
||||||
{node.members?.properties?.length ? (
|
{node.members?.properties?.length ? (
|
||||||
<Collapsible className="flex flex-col gap-4 px-4" defaultOpen>
|
<Collapsible className="flex flex-col gap-2" defaultOpen>
|
||||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
|
||||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||||
<VscSymbolProperty aria-hidden className="flex-shrink-0" size={24} />
|
<VscSymbolProperty aria-hidden className="flex-shrink-0" size={24} />
|
||||||
Properties
|
Properties
|
||||||
@@ -36,13 +36,13 @@ export async function Outline({ node }: { readonly node: any }) {
|
|||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div className="flex flex-col gap-2 px-4">
|
<div className="flex flex-col px-4">
|
||||||
{node.members.properties.map((property: any, idx: number) => (
|
{node.members.properties.map((property: any, idx: number) => (
|
||||||
<Fragment key={`${property.displayName}-${idx}`}>
|
<Fragment key={`${property.displayName}-${idx}`}>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex place-content-between place-items-center">
|
<div className="flex place-content-between place-items-center">
|
||||||
<Link
|
<Link
|
||||||
className="grow truncate rounded-md p-2 font-mono transition-colors hover:bg-neutral-200 dark:hover:bg-neutral-800 md:px-1 md:py-1"
|
className="max-w-[25ch] grow truncate rounded-md p-2 font-mono transition-colors hover:bg-[#e7e7e9] md:max-w-none md:py-1 dark:hover:bg-[#242428]"
|
||||||
href={`#${property.displayName}`}
|
href={`#${property.displayName}`}
|
||||||
>
|
>
|
||||||
{property.displayName}
|
{property.displayName}
|
||||||
@@ -57,8 +57,8 @@ export async function Outline({ node }: { readonly node: any }) {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{node.members?.methods?.length ? (
|
{node.members?.methods?.length ? (
|
||||||
<Collapsible className="flex flex-col gap-4 px-4" defaultOpen>
|
<Collapsible className="flex flex-col gap-2" defaultOpen>
|
||||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
|
||||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||||
<VscSymbolMethod aria-hidden className="flex-shrink-0" size={24} />
|
<VscSymbolMethod aria-hidden className="flex-shrink-0" size={24} />
|
||||||
Methods
|
Methods
|
||||||
@@ -68,13 +68,13 @@ export async function Outline({ node }: { readonly node: any }) {
|
|||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div className="flex flex-col gap-2 px-4">
|
<div className="flex flex-col px-4">
|
||||||
{node.members.methods.map((method: any, idx: number) => (
|
{node.members.methods.map((method: any, idx: number) => (
|
||||||
<Fragment key={`${method.displayName}-${idx}`}>
|
<Fragment key={`${method.displayName}-${idx}`}>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex place-content-between place-items-center">
|
<div className="flex place-content-between place-items-center">
|
||||||
<Link
|
<Link
|
||||||
className="grow truncate rounded-md p-2 font-mono transition-colors hover:bg-neutral-200 dark:hover:bg-neutral-800 md:px-1 md:py-1"
|
className="max-w-[25ch] grow truncate rounded-md p-2 font-mono transition-colors hover:bg-[#e7e7e9] md:max-w-none md:py-1 dark:hover:bg-[#242428]"
|
||||||
href={`#${method.displayName}`}
|
href={`#${method.displayName}`}
|
||||||
>
|
>
|
||||||
{method.displayName}
|
{method.displayName}
|
||||||
@@ -89,8 +89,8 @@ export async function Outline({ node }: { readonly node: any }) {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{node.members?.events?.length ? (
|
{node.members?.events?.length ? (
|
||||||
<Collapsible className="flex flex-col gap-4 px-4" defaultOpen>
|
<Collapsible className="flex flex-col gap-2" defaultOpen>
|
||||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
|
||||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||||
<VscSymbolEvent aria-hidden className="flex-shrink-0" size={24} />
|
<VscSymbolEvent aria-hidden className="flex-shrink-0" size={24} />
|
||||||
Events
|
Events
|
||||||
@@ -100,13 +100,13 @@ export async function Outline({ node }: { readonly node: any }) {
|
|||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div className="flex flex-col gap-2 px-4">
|
<div className="flex flex-col px-4">
|
||||||
{node.members.events.map((event: any, idx: number) => (
|
{node.members.events.map((event: any, idx: number) => (
|
||||||
<Fragment key={`${event.displayName}-${idx}`}>
|
<Fragment key={`${event.displayName}-${idx}`}>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex place-content-between place-items-center">
|
<div className="flex place-content-between place-items-center">
|
||||||
<Link
|
<Link
|
||||||
className="grow truncate rounded-md p-2 font-mono transition-colors hover:bg-neutral-200 dark:hover:bg-neutral-800 md:px-1 md:py-1"
|
className="max-w-[25ch] grow truncate rounded-md p-2 font-mono transition-colors hover:bg-[#e7e7e9] md:max-w-none md:py-1 dark:hover:bg-[#242428]"
|
||||||
href={`#${event.displayName}`}
|
href={`#${event.displayName}`}
|
||||||
>
|
>
|
||||||
{event.displayName}
|
{event.displayName}
|
||||||
@@ -120,7 +120,7 @@ export async function Outline({ node }: { readonly node: any }) {
|
|||||||
</Collapsible>
|
</Collapsible>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div aria-hidden className="px-4">
|
<div aria-hidden className="p-4">
|
||||||
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { OverlayScrollbars, ClickScrollPlugin } from 'overlayscrollbars';
|
import { OverlayScrollbars, ClickScrollPlugin } from 'overlayscrollbars';
|
||||||
|
import type { OverlayScrollbarsComponentProps } from 'overlayscrollbars-react';
|
||||||
|
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||||
|
import { cx } from '@/styles/cva';
|
||||||
|
|
||||||
OverlayScrollbars.plugin(ClickScrollPlugin);
|
OverlayScrollbars.plugin(ClickScrollPlugin);
|
||||||
|
|
||||||
export { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
export function Scrollbars(props: OverlayScrollbarsComponentProps) {
|
||||||
|
const { className, children, ...additionalProps } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<OverlayScrollbarsComponent {...additionalProps} className={cx('', className)} defer>
|
||||||
|
{children}
|
||||||
|
</OverlayScrollbarsComponent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
30
apps/website/src/components/PackageSelect.tsx
Normal file
30
apps/website/src/components/PackageSelect.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { Select, SelectList, SelectOption, SelectTrigger } from '@/components/ui/Select';
|
||||||
|
import { PACKAGES } from '@/util/constants';
|
||||||
|
|
||||||
|
export function PackageSelect() {
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select aria-label="Select a package" defaultSelectedKey={params.packageName as string}>
|
||||||
|
<SelectTrigger className="bg-[#f3f3f4] dark:bg-[#121214]" />
|
||||||
|
<SelectList classNames={{ popover: 'bg-[#f3f3f4] dark:bg-[#28282d]' }} items={PACKAGES}>
|
||||||
|
{(item) => (
|
||||||
|
<SelectOption
|
||||||
|
className="dark:pressed:bg-[#313135] bg-[#f3f3f4] dark:bg-[#28282d] dark:hover:bg-[#313135]"
|
||||||
|
href={`/docs/packages/${item.name}/stable`}
|
||||||
|
id={item.name}
|
||||||
|
key={item.name}
|
||||||
|
onHoverStart={() => router.prefetch(`/docs/packages/${item.name}/stable`)}
|
||||||
|
textValue={item.name}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</SelectOption>
|
||||||
|
)}
|
||||||
|
</SelectList>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -15,13 +15,13 @@ export async function ParameterNode({
|
|||||||
readonly version: string;
|
readonly version: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={`${description ? 'flex flex-col gap-8' : 'inline'}`}>
|
<div className={`${description ? 'flex flex-col gap-4' : 'inline'}`}>
|
||||||
{node.map((parameter: any, idx: number) => (
|
{node.map((parameter: any, idx: number) => (
|
||||||
<Fragment key={`${parameter.name}-${idx}`}>
|
<Fragment key={`${parameter.name}-${idx}`}>
|
||||||
<div className={description ? 'group' : 'inline after:content-[",_"] last-of-type:after:content-none'}>
|
<div className={description ? 'group' : 'inline after:content-[",_"] last-of-type:after:content-none'}>
|
||||||
<span className="font-mono font-semibold">
|
<span className="font-mono font-semibold">
|
||||||
{description ? (
|
{description ? (
|
||||||
<Link className="float-left -ml-6 hidden pb-2 pr-2 group-hover:block" href={`#${parameter.name}`}>
|
<Link className="float-left -ml-6 hidden pr-2 pb-2 group-hover:block" href={`#${parameter.name}`}>
|
||||||
<LinkIcon aria-hidden size={16} />
|
<LinkIcon aria-hidden size={16} />
|
||||||
</Link>
|
</Link>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -39,7 +39,7 @@ export async function ParameterNode({
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
{description ? (
|
{description ? (
|
||||||
<div aria-hidden className="px-4">
|
<div aria-hidden className="p-4">
|
||||||
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { VscSymbolProperty } from '@react-icons/all-files/vsc/VscSymbolProperty'
|
|||||||
import { ChevronDown, ChevronUp, Code2, LinkIcon } from 'lucide-react';
|
import { ChevronDown, ChevronUp, Code2, LinkIcon } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { ENV } from '~/util/env';
|
import { ENV } from '@/util/env';
|
||||||
import { Badges } from './Badges';
|
import { Badges } from './Badges';
|
||||||
import { DeprecatedNode } from './DeprecatedNode';
|
import { DeprecatedNode } from './DeprecatedNode';
|
||||||
import { ExcerptNode } from './ExcerptNode';
|
import { ExcerptNode } from './ExcerptNode';
|
||||||
@@ -21,8 +21,8 @@ export async function PropertyNode({
|
|||||||
readonly version: string;
|
readonly version: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Collapsible className="flex flex-col gap-8" defaultOpen>
|
<Collapsible className="flex flex-col gap-4" defaultOpen>
|
||||||
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-neutral-200 dark:hover:bg-neutral-800">
|
<CollapsibleTrigger className="group flex place-content-between place-items-center rounded-md p-2 hover:bg-[#e7e7e9] dark:hover:bg-[#242428]">
|
||||||
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
<h2 className="flex place-items-center gap-2 text-xl font-bold">
|
||||||
<VscSymbolProperty aria-hidden className="flex-shrink-0" size={24} />
|
<VscSymbolProperty aria-hidden className="flex-shrink-0" size={24} />
|
||||||
Properties
|
Properties
|
||||||
@@ -32,19 +32,19 @@ export async function PropertyNode({
|
|||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
|
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-4">
|
||||||
{node.map((property: any, idx: number) => (
|
{node.map((property: any, idx: number) => (
|
||||||
<Fragment key={`${property.displayName}-${idx}`}>
|
<Fragment key={`${property.displayName}-${idx}`}>
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="flex place-content-between place-items-center">
|
<div className="flex place-content-between place-items-center gap-1">
|
||||||
<h3
|
<h3
|
||||||
className={`${ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-16' : 'scroll-mt-8'} group flex flex-col gap-2 break-words font-mono font-semibold`}
|
className={`${ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-16' : 'scroll-mt-8'} group flex flex-col gap-2 px-2 font-mono font-semibold break-words`}
|
||||||
id={property.displayName}
|
id={property.displayName}
|
||||||
>
|
>
|
||||||
<Badges node={property} />
|
<Badges node={property} />
|
||||||
<span>
|
<span>
|
||||||
<Link
|
<Link
|
||||||
className="float-left -ml-6 hidden pb-2 pr-2 group-hover:block"
|
className="float-left -ml-6 hidden pr-2 pb-2 group-hover:block"
|
||||||
href={`#${property.displayName}`}
|
href={`#${property.displayName}`}
|
||||||
>
|
>
|
||||||
<LinkIcon aria-hidden size={16} />
|
<LinkIcon aria-hidden size={16} />
|
||||||
@@ -91,7 +91,7 @@ export async function PropertyNode({
|
|||||||
<SeeNode node={property.summary.seeBlocks} padding version={version} />
|
<SeeNode node={property.summary.seeBlocks} padding version={version} />
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div aria-hidden className="px-4">
|
<div aria-hidden className="p-4">
|
||||||
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|||||||
@@ -2,24 +2,29 @@
|
|||||||
|
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import { Command, Search } from 'lucide-react';
|
import { Command, Search } from 'lucide-react';
|
||||||
import { isCmdKOpenAtom } from '~/stores/cmdk';
|
import { isCmdKOpenAtom } from '@/stores/cmdk';
|
||||||
|
import { useSidebar } from './ui/Sidebar';
|
||||||
|
|
||||||
export function SearchButton() {
|
export function SearchButton() {
|
||||||
|
const { setOpenMobile } = useSidebar();
|
||||||
const setIsOpen = useSetAtom(isCmdKOpenAtom);
|
const setIsOpen = useSetAtom(isCmdKOpenAtom);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
aria-label="Open search"
|
aria-label="Open search"
|
||||||
className="flex place-content-between place-items-center rounded-md bg-neutral-200 p-2 dark:bg-neutral-800"
|
className="bg-base-neutral-100 dark:bg-base-neutral-900 flex place-content-between place-items-center rounded-sm p-2"
|
||||||
onClick={() => setIsOpen(true)}
|
onClick={() => {
|
||||||
|
setOpenMobile(false);
|
||||||
|
setIsOpen(true);
|
||||||
|
}}
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
<span className="flex place-items-center gap-2">
|
<span className="flex place-items-center gap-2">
|
||||||
<Search aria-hidden size={20} />
|
<Search aria-hidden size={18} />
|
||||||
Search...
|
Search...
|
||||||
</span>
|
</span>
|
||||||
<span className="hidden place-items-center gap-1 md:flex">
|
<span className="hidden place-items-center gap-1 md:flex">
|
||||||
<Command aria-hidden size={20} /> K
|
<Command aria-hidden size={18} /> K
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
@@ -1,11 +1,4 @@
|
|||||||
import { getHighlighterCore } from 'shiki/core';
|
import { codeToHtml } from '@/util/shiki.bundle';
|
||||||
import getWasm from 'shiki/wasm';
|
|
||||||
|
|
||||||
const highlighter = await getHighlighterCore({
|
|
||||||
themes: [import('shiki/themes/github-light.mjs'), import('shiki/themes/github-dark-dimmed.mjs')],
|
|
||||||
langs: [import('shiki/langs/typescript.mjs'), import('shiki/langs/javascript.mjs')],
|
|
||||||
loadWasm: getWasm,
|
|
||||||
});
|
|
||||||
|
|
||||||
export async function SyntaxHighlighter({
|
export async function SyntaxHighlighter({
|
||||||
lang,
|
lang,
|
||||||
@@ -16,7 +9,7 @@ export async function SyntaxHighlighter({
|
|||||||
readonly code: string;
|
readonly code: string;
|
||||||
readonly lang: string;
|
readonly lang: string;
|
||||||
}) {
|
}) {
|
||||||
const codeHTML = highlighter.codeToHtml(code.trim(), {
|
const codeHTML = await codeToHtml(code.trim(), {
|
||||||
lang,
|
lang,
|
||||||
themes: {
|
themes: {
|
||||||
light: 'github-light',
|
light: 'github-light',
|
||||||
|
|||||||
21
apps/website/src/components/ThemeSwitch.tsx
Normal file
21
apps/website/src/components/ThemeSwitch.tsx
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { VscColorMode } from '@react-icons/all-files/vsc/VscColorMode';
|
||||||
|
import dynamic from 'next/dynamic';
|
||||||
|
import { useTheme } from 'next-themes';
|
||||||
|
import { Button } from '@/components/ui/Button';
|
||||||
|
|
||||||
|
export function ThemeSwitch() {
|
||||||
|
const { resolvedTheme, setTheme } = useTheme();
|
||||||
|
const toggleTheme = () => setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button aria-label="Toggle theme" onPress={() => toggleTheme()} size="icon-sm" variant="filled">
|
||||||
|
<VscColorMode aria-hidden data-slot="icon" size={18} />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ThemeSwitchNoSRR = dynamic(async () => ThemeSwitch, {
|
||||||
|
ssr: false,
|
||||||
|
});
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { LinkIcon } from 'lucide-react';
|
import { LinkIcon } from 'lucide-react';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { ENV } from '~/util/env';
|
import { cx } from '@/styles/cva';
|
||||||
|
import { ENV } from '@/util/env';
|
||||||
import { Badges } from './Badges';
|
import { Badges } from './Badges';
|
||||||
import { DocNode } from './DocNode';
|
import { DocNode } from './DocNode';
|
||||||
import { ExcerptNode } from './ExcerptNode';
|
import { ExcerptNode } from './ExcerptNode';
|
||||||
@@ -16,18 +17,22 @@ export async function TypeParameterNode({
|
|||||||
readonly version: string;
|
readonly version: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className={`${description ? 'flex flex-col gap-8' : 'inline-block'}`}>
|
<div className={`${description ? 'flex flex-col gap-4' : 'inline-block'}`}>
|
||||||
{node.map((typeParameter: any, idx: number) => (
|
{node.map((typeParameter: any, idx: number) => (
|
||||||
<Fragment key={`${typeParameter.name}-${idx}`}>
|
<Fragment key={`${typeParameter.name}-${idx}`}>
|
||||||
<div className={description ? '' : 'inline after:content-[",_"] last-of-type:after:content-none'}>
|
<div className={description ? '' : 'inline after:content-[",_"] last-of-type:after:content-none'}>
|
||||||
<h3
|
<h3
|
||||||
className={`${ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-16' : 'scroll-mt-8'} group inline break-words font-mono font-semibold`}
|
className={cx(
|
||||||
|
ENV.IS_LOCAL_DEV || ENV.IS_PREVIEW ? 'scroll-mt-16' : 'scroll-mt-8',
|
||||||
|
'group inline font-mono font-semibold break-words',
|
||||||
|
description ? 'inline-block px-2' : '',
|
||||||
|
)}
|
||||||
id={typeParameter.name}
|
id={typeParameter.name}
|
||||||
>
|
>
|
||||||
{description ? <Badges node={typeParameter} /> : null}
|
{description ? <Badges node={typeParameter} /> : null}
|
||||||
<span>
|
<span>
|
||||||
{description ? (
|
{description ? (
|
||||||
<Link className="float-left -ml-6 hidden pb-2 pr-2 group-hover:block" href={`#${typeParameter.name}`}>
|
<Link className="float-left -ml-6 hidden pr-2 pb-2 group-hover:block" href={`#${typeParameter.name}`}>
|
||||||
<LinkIcon aria-hidden size={16} />
|
<LinkIcon aria-hidden size={16} />
|
||||||
</Link>
|
</Link>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -57,7 +62,7 @@ export async function TypeParameterNode({
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
{description ? (
|
{description ? (
|
||||||
<div aria-hidden className="px-4">
|
<div aria-hidden className="p-4">
|
||||||
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
<div className="h-[2px] bg-neutral-300 dark:bg-neutral-700" role="separator" />
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import { ExcerptNode } from './ExcerptNode';
|
|||||||
|
|
||||||
export async function UnionMember({ node, version }: { readonly node: any; readonly version: string }) {
|
export async function UnionMember({ node, version }: { readonly node: any; readonly version: string }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-8">
|
<div className="flex flex-col gap-4">
|
||||||
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">Union Members</h2>
|
<h2 className="flex place-items-center gap-2 p-2 text-xl font-bold">Union Members</h2>
|
||||||
|
|
||||||
<span className="flex flex-col gap-4 break-words font-mono text-sm">
|
<span className="flex flex-col gap-4 px-2 font-mono text-sm break-words">
|
||||||
<ExcerptNode node={node} version={version} />
|
<ExcerptNode node={node} version={version} />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
35
apps/website/src/components/VersionSelect.tsx
Normal file
35
apps/website/src/components/VersionSelect.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { use } from 'react';
|
||||||
|
import { Select, SelectList, SelectOption, SelectTrigger } from './ui/Select';
|
||||||
|
|
||||||
|
export function VersionSelect({
|
||||||
|
versionsPromise,
|
||||||
|
}: {
|
||||||
|
readonly versionsPromise: Promise<{ readonly version: string }[]>;
|
||||||
|
}) {
|
||||||
|
const router = useRouter();
|
||||||
|
const params = useParams();
|
||||||
|
const versions = use(versionsPromise);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select aria-label="Select a version" defaultSelectedKey={params.version as string}>
|
||||||
|
<SelectTrigger className="bg-[#f3f3f4] dark:bg-[#121214]" />
|
||||||
|
<SelectList classNames={{ popover: 'bg-[#f3f3f4] dark:bg-[#28282d]' }} items={versions}>
|
||||||
|
{(item) => (
|
||||||
|
<SelectOption
|
||||||
|
className="dark:pressed:bg-[#313135] bg-[#f3f3f4] dark:bg-[#28282d] dark:hover:bg-[#313135]"
|
||||||
|
href={`/docs/packages/${params.packageName}/${item.version}`}
|
||||||
|
id={item.version}
|
||||||
|
key={item.version}
|
||||||
|
onHoverStart={() => router.prefetch(`/docs/packages/${params.packageName}/${item.version}`)}
|
||||||
|
textValue={item.version}
|
||||||
|
>
|
||||||
|
{item.version}
|
||||||
|
</SelectOption>
|
||||||
|
)}
|
||||||
|
</SelectList>
|
||||||
|
</Select>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -48,18 +48,18 @@ export async function Alert({ title, type, children }: PropsWithChildren<IAlert>
|
|||||||
const { text, border, icon } = resolveType(type);
|
const { text, border, icon } = resolveType(type);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-4 mt-6" role="alert">
|
<div className="mt-6 mb-4" role="alert">
|
||||||
<div className="relative flex">
|
<div className="relative flex">
|
||||||
<div className="p-4">{children}</div>
|
<div className="p-4">{children}</div>
|
||||||
<div className="pointer-events-none absolute flex h-full w-full">
|
<div className="pointer-events-none absolute flex h-full w-full">
|
||||||
<div className={`w-4 shrink-0 rounded-bl-md rounded-tl-md border-b-2 border-l-2 border-t-2 ${border}`} />
|
<div className={`w-4 shrink-0 rounded-tl-md rounded-bl-md border-t-2 border-b-2 border-l-2 ${border}`} />
|
||||||
<div className={`relative border-b-2 ${border}`}>
|
<div className={`relative border-b-2 ${border}`}>
|
||||||
<div className={`pointer-events-auto flex -translate-y-1/2 place-items-center gap-2 px-2 ${text}`}>
|
<div className={`pointer-events-auto flex -translate-y-1/2 place-items-center gap-2 px-2 ${text}`}>
|
||||||
{icon}
|
{icon}
|
||||||
{title ? <span className={`font-semibold ${text}`}>{title}</span> : null}
|
{title ? <span className={`font-semibold ${text}`}>{title}</span> : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={`flex-1 rounded-br-md rounded-tr-md border-b-2 border-r-2 border-t-2 ${border}`} />
|
<div className={`flex-1 rounded-tr-md rounded-br-md border-t-2 border-r-2 border-b-2 ${border}`} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,28 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
export { Button } from 'react-aria-components';
|
import type { VariantProps } from 'cva';
|
||||||
|
import { Button as RACButton, composeRenderProps } from 'react-aria-components';
|
||||||
|
import type { ButtonProps as RACButtonProps } from 'react-aria-components';
|
||||||
|
import { buttonStyles } from '@/styles/ui/button';
|
||||||
|
|
||||||
|
export type ButtonProps = RACButtonProps & VariantProps<typeof buttonStyles>;
|
||||||
|
|
||||||
|
export function Button(props: ButtonProps) {
|
||||||
|
return (
|
||||||
|
<RACButton
|
||||||
|
{...props}
|
||||||
|
className={composeRenderProps(props.className, (className, renderProps) =>
|
||||||
|
buttonStyles({
|
||||||
|
...renderProps,
|
||||||
|
variant: props.variant,
|
||||||
|
size: props.size,
|
||||||
|
isDestructive: props.isDestructive,
|
||||||
|
isDark: props.isDark,
|
||||||
|
className,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{(values) => <>{typeof props.children === 'function' ? props.children(values) : props.children}</>}
|
||||||
|
</RACButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
175
apps/website/src/components/ui/Dialog.tsx
Normal file
175
apps/website/src/components/ui/Dialog.tsx
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { XIcon } from 'lucide-react';
|
||||||
|
import { useEffect, useRef, type ComponentProps, type ComponentPropsWithoutRef, type Ref } from 'react';
|
||||||
|
import type { HeadingProps as RACHeadingProps } from 'react-aria-components';
|
||||||
|
import { Dialog as RACDialog, Heading as RACHeading, Text as RACText } from 'react-aria-components';
|
||||||
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
|
import { Button, type ButtonProps } from '@/components/ui/Button';
|
||||||
|
import { cx } from '@/styles/cva';
|
||||||
|
|
||||||
|
export function Dialog(props: ComponentProps<typeof RACDialog>) {
|
||||||
|
return (
|
||||||
|
<RACDialog
|
||||||
|
{...props}
|
||||||
|
className={cx(
|
||||||
|
'peer/dialog group/dialog relative flex max-h-[inherit] flex-col overflow-hidden outline-hidden [scrollbar-width:thin]',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
role={props.role!}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DialogHeaderProps = ComponentPropsWithoutRef<'div'> & {
|
||||||
|
readonly description?: string;
|
||||||
|
readonly hasBorder?: boolean;
|
||||||
|
readonly title?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DialogHeader({ hasBorder = false, ...props }: DialogHeaderProps) {
|
||||||
|
const headerRef = useRef<HTMLHeadingElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const header = headerRef.current;
|
||||||
|
if (!header) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
header.parentElement?.style.setProperty('--dialog-header-height', `${entry.target.clientHeight}px`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(header);
|
||||||
|
return () => observer.unobserve(header);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
'relative flex flex-col gap-1 p-6 pb-4 [&[data-slot=dialog-header]:has(+[data-slot=dialog-footer])]:pb-0',
|
||||||
|
hasBorder &&
|
||||||
|
'border-base-neutral-100 dark:border-base-neutral-700 border-b [&[data-slot=dialog-header]:has(+[data-slot=dialog-footer])]:border-b',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
data-slot="dialog-header"
|
||||||
|
ref={headerRef}
|
||||||
|
>
|
||||||
|
{props.title && <DialogTitle>{props.title}</DialogTitle>}
|
||||||
|
{props.description && <DialogDescription>{props.description}</DialogDescription>}
|
||||||
|
{!props.title && typeof props.children === 'string' ? <DialogTitle {...props} /> : props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DialogTitleProps = RACHeadingProps & {
|
||||||
|
readonly ref?: Ref<HTMLHeadingElement>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DialogTitle({ level = 2, ...props }: DialogTitleProps) {
|
||||||
|
return (
|
||||||
|
<RACHeading
|
||||||
|
{...props}
|
||||||
|
className={cx('text-base-label-md flex flex-1 place-items-center', props.className)}
|
||||||
|
level={level}
|
||||||
|
slot="title"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DialogDescriptionProps = ComponentProps<'div'>;
|
||||||
|
|
||||||
|
export function DialogDescription(props: DialogDescriptionProps) {
|
||||||
|
return <RACText {...props} className={cx('text-sm', props.className)} slot="description" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DialogBodyProps = ComponentProps<'div'>;
|
||||||
|
|
||||||
|
export function DialogBody(props: DialogBodyProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={cx(
|
||||||
|
'isolate flex max-h-[calc(var(--visual-viewport-height)-var(--visual-viewport-vertical-padding)-var(--dialog-header-height,0px)-var(--dialog-footer-height,0px))] flex-1 flex-col gap-6 overflow-auto px-6 py-1',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
data-slot="dialog-body"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DialogFooterProps = ComponentProps<'div'> & {
|
||||||
|
readonly hasBorder?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DialogFooter({ hasBorder = false, ...props }: DialogFooterProps) {
|
||||||
|
const footerRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const footer = footerRef.current;
|
||||||
|
|
||||||
|
if (!footer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new ResizeObserver((entries) => {
|
||||||
|
for (const entry of entries) {
|
||||||
|
footer.parentElement?.style.setProperty('--dialog-footer-height', `${entry.target.clientHeight}px`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(footer);
|
||||||
|
return () => {
|
||||||
|
observer.unobserve(footer);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={cx(
|
||||||
|
'isolate mt-auto flex flex-col-reverse place-content-between gap-3 p-6 sm:flex-row sm:place-content-end sm:place-items-center',
|
||||||
|
hasBorder && 'border-base-neutral-100 dark:border-base-neutral-700 border-t',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
data-slot="dialog-footer"
|
||||||
|
ref={footerRef}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DialogCloseProps = ButtonProps;
|
||||||
|
|
||||||
|
export function DialogClose(props: DialogCloseProps) {
|
||||||
|
return <Button {...props} slot="close" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CloseButtonIndicatorProps = Omit<ButtonProps, 'children'> & {
|
||||||
|
readonly className?: string;
|
||||||
|
readonly isDismissable?: boolean | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DialogCloseIndicator(props: CloseButtonIndicatorProps) {
|
||||||
|
const isMobile = useMediaQuery('(max-width: 600px)', { initializeWithValue: false });
|
||||||
|
|
||||||
|
return props.isDismissable ? (
|
||||||
|
<Button
|
||||||
|
{...props}
|
||||||
|
{...(isMobile ? { autoFocus: true } : {})}
|
||||||
|
aria-label="Close"
|
||||||
|
className={cx(
|
||||||
|
'close text-base-neutral-500 hover:text-base-neutral-700 focus-visible:text-base-neutral-700 pressed:text-base-neutral-900 dark:text-base-neutral-400 dark:hover:text-base-neutral-200 dark:focus-visible:text-base-neutral-200 dark:pressed:text-base-neutral-500 disabled:text-base-neutral-300 dark:disabled:text-base-neutral-300 absolute top-3 right-4 z-50 rounded-full',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
size="icon-xs"
|
||||||
|
slot="close"
|
||||||
|
variant="unset"
|
||||||
|
>
|
||||||
|
<XIcon aria-hidden className="size-4.5 stroke-[1.5]" />
|
||||||
|
</Button>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Button as DialogTrigger } from '@/components/ui/Button';
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { useAtom } from 'jotai';
|
|
||||||
import { ChevronUp } from 'lucide-react';
|
|
||||||
import { useEffect, type PropsWithChildren } from 'react';
|
|
||||||
import { useMediaQuery } from 'usehooks-ts';
|
|
||||||
import { Drawer as Vaul } from 'vaul';
|
|
||||||
import { isDrawerOpenAtom } from '~/stores/drawer';
|
|
||||||
|
|
||||||
export function Drawer({ children }: PropsWithChildren) {
|
|
||||||
const [open, setOpen] = useAtom(isDrawerOpenAtom);
|
|
||||||
const isMedium = useMediaQuery('(min-width: 768px)');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isMedium) {
|
|
||||||
setOpen(false);
|
|
||||||
}
|
|
||||||
}, [isMedium, setOpen]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Vaul.Root onOpenChange={setOpen} open={open}>
|
|
||||||
<Vaul.Trigger
|
|
||||||
aria-label="Open navigation"
|
|
||||||
className="flex h-12 w-full place-content-center place-items-center rounded-t-lg border-t border-neutral-300 bg-neutral-100 p-2 dark:border-neutral-700 dark:bg-neutral-900"
|
|
||||||
>
|
|
||||||
<ChevronUp aria-hidden size={28} />
|
|
||||||
</Vaul.Trigger>
|
|
||||||
<Vaul.Portal>
|
|
||||||
<Vaul.Overlay className="fixed inset-0 bg-black/40" />
|
|
||||||
<Vaul.Content className="fixed bottom-0 left-0 right-0 flex max-h-[86%] flex-col rounded-t-lg bg-neutral-100 p-4 dark:bg-neutral-900">
|
|
||||||
<div className="mx-auto mb-8 h-1.5 w-12 flex-shrink-0 rounded-full bg-neutral-400" />
|
|
||||||
{children}
|
|
||||||
</Vaul.Content>
|
|
||||||
</Vaul.Portal>
|
|
||||||
</Vaul.Root>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
166
apps/website/src/components/ui/Dropdown.tsx
Normal file
166
apps/website/src/components/ui/Dropdown.tsx
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { CheckIcon } from 'lucide-react';
|
||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
import {
|
||||||
|
Collection as RACCollection,
|
||||||
|
composeRenderProps,
|
||||||
|
Header as RACHeader,
|
||||||
|
ListBoxItem as RACListBoxItem,
|
||||||
|
ListBoxSection as RACListBoxSection,
|
||||||
|
Separator as RACSeparator,
|
||||||
|
Text as RACText,
|
||||||
|
} from 'react-aria-components';
|
||||||
|
import type {
|
||||||
|
SectionProps as RACDropdownSectionProps,
|
||||||
|
ListBoxItemProps as RACListBoxItemProps,
|
||||||
|
TextProps as RACTextProps,
|
||||||
|
SeparatorProps as RACSeparatorProps,
|
||||||
|
} from 'react-aria-components';
|
||||||
|
import { Keyboard } from '@/components/ui/Keyboard';
|
||||||
|
import { cva, cx } from '@/styles/cva';
|
||||||
|
|
||||||
|
export type DropdownSectionProps<Type extends object> = RACDropdownSectionProps<Type> & {
|
||||||
|
readonly title?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DropdownSection<Type extends object>(props: DropdownSectionProps<Type>) {
|
||||||
|
return (
|
||||||
|
<RACListBoxSection
|
||||||
|
{...props}
|
||||||
|
className={cx(
|
||||||
|
'col-span-full grid grid-cols-[auto_1fr] supports-[grid-template-columns:subgrid]:grid-cols-subgrid',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{props.title && (
|
||||||
|
<RACHeader className="text-base-label-sm text-base-neutral-600 dark:text-base-neutral-300 col-span-full px-3 py-2">
|
||||||
|
{props.title}
|
||||||
|
</RACHeader>
|
||||||
|
)}
|
||||||
|
<RACCollection items={props.items!}>{props.children}</RACCollection>
|
||||||
|
</RACListBoxSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DropdownLabel(props: RACTextProps) {
|
||||||
|
return <RACText {...props} className={cx('col-start-1', props.className)} slot="label" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const dropdownItemStyles = cva({
|
||||||
|
base: [
|
||||||
|
'col-span-full grid grid-cols-[auto_1fr_1.5rem_0.5rem_auto] not-has-data-[slot=dropdown-item-details]:items-center has-data-[slot=dropdown-item-details]:**:data-[slot=checked-icon]:mt-[1.5px] supports-[grid-template-columns:subgrid]:grid-cols-subgrid',
|
||||||
|
'group forced-color:text-[Highlight] text-base-md relative h-10 cursor-default rounded-sm px-3 py-2.5 outline-0 forced-color-adjust-none select-none has-data-[slot=dropdown-item-details]:h-[inherit] has-data-[slot=dropdown-item-details]:place-content-center forced-colors:text-[LinkText]',
|
||||||
|
'text-base-neutral-900 dark:text-base-neutral-40 bg-base-neutral-0 dark:bg-base-neutral-800',
|
||||||
|
'hover:bg-base-neutral-80 dark:hover:bg-base-neutral-700/72',
|
||||||
|
'focus-visible:bg-base-neutral-80 dark:focus-visible:bg-base-neutral-700/72',
|
||||||
|
'pressed:bg-base-neutral-100 dark:pressed:bg-base-neutral-700/72',
|
||||||
|
'**:data-[slot=avatar]:mr-2 **:data-[slot=avatar]:size-6 **:data-[slot=avatar]:*:mr-2 **:data-[slot=avatar]:*:size-6 sm:**:data-[slot=avatar]:size-5 sm:**:data-[slot=avatar]:*:size-5',
|
||||||
|
'**:data-[slot=icon]:size-4.5 **:data-[slot=icon]:shrink-0',
|
||||||
|
'data-destructive:text-base-sunset-600 dark:data-destructive:text-base-sunset-400 data-destructive:hover:bg-base-sunset-100 dark:data-destructive:hover:bg-base-sunset-800 data-destructive:hover:text-base-sunset-700 dark:data-destructive:hover:text-base-sunset-300 data-destructive:focus-visible:bg-base-sunset-100 dark:data-destructive:focus-visible:bg-base-sunset-800 data-destructive:focus-visible:hover:text-base-sunset-700 dark:data-destructive:focus-visible:text-base-sunset-300 data-destructive:pressed:bg-base-sunset-200 dark:data-destructive:pressed:bg-base-sunset-700 data-destructive:pressed:text-base-sunset-800 dark:data-destructive:pressed:text-base-sunset-100',
|
||||||
|
'*:data-[slot=icon]:mr-2 data-[slot=menu-radio]:*:data-[slot=icon]:size-4.5',
|
||||||
|
'forced-colors:**:data-[slot=icon]:text-[CanvasText] forced-colors:group-data-focused:**:data-[slot=icon]:text-[Canvas]',
|
||||||
|
'[&>[slot=label]+[data-slot=icon]]:absolute [&>[slot=label]+[data-slot=icon]]:right-0',
|
||||||
|
],
|
||||||
|
variants: {
|
||||||
|
isFocused: {
|
||||||
|
true: 'forced-colors:bg-[Highlight] forced-colors:text-[HighlightText]',
|
||||||
|
false: '',
|
||||||
|
},
|
||||||
|
isDisabled: {
|
||||||
|
true: 'opacity-50 forced-colors:text-[GrayText]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type DropdownItemProps = RACListBoxItemProps & {
|
||||||
|
readonly classNames?: {
|
||||||
|
readonly selected?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DropdownItem(props: DropdownItemProps) {
|
||||||
|
const textValue = props.textValue ?? (typeof props.children === 'string' ? props.children : undefined);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RACListBoxItem
|
||||||
|
{...props}
|
||||||
|
className={composeRenderProps(props.className, (className, renderProps) =>
|
||||||
|
dropdownItemStyles({
|
||||||
|
...renderProps,
|
||||||
|
className,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
textValue={textValue!}
|
||||||
|
>
|
||||||
|
{composeRenderProps(props.children, (children, { isSelected }) => (
|
||||||
|
<>
|
||||||
|
{typeof children === 'string' ? <DropdownLabel>{children}</DropdownLabel> : children}
|
||||||
|
{isSelected && (
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
'bg-base-blurple-400 dark:bg-base-blurple-400 flex size-[18px] place-content-center place-items-center place-self-start rounded-full',
|
||||||
|
props.classNames?.selected,
|
||||||
|
)}
|
||||||
|
data-slot="checked-icon"
|
||||||
|
>
|
||||||
|
<CheckIcon aria-hidden className="text-base-neutral-40 size-3.5" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</RACListBoxItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DropdownItemDetailProps = RACTextProps & {
|
||||||
|
readonly classNames?: {
|
||||||
|
readonly description?: RACTextProps['className'];
|
||||||
|
readonly label?: RACTextProps['className'];
|
||||||
|
};
|
||||||
|
readonly description?: RACTextProps['children'];
|
||||||
|
readonly label?: RACTextProps['children'];
|
||||||
|
};
|
||||||
|
|
||||||
|
export function DropdownItemDetails(props: DropdownItemDetailProps) {
|
||||||
|
return (
|
||||||
|
<div {...props} className="col-start-1 flex flex-col gap-1" data-slot="dropdown-item-details">
|
||||||
|
{props.label && (
|
||||||
|
<RACText
|
||||||
|
{...props}
|
||||||
|
className={cx('text-base-md max-w-[25ch] truncate', props.classNames?.label)}
|
||||||
|
slot={props.slot ?? 'label'}
|
||||||
|
>
|
||||||
|
{props.label}
|
||||||
|
</RACText>
|
||||||
|
)}
|
||||||
|
{props.description && (
|
||||||
|
<RACText
|
||||||
|
{...props}
|
||||||
|
className={cx('text-base-sm text-base-neutral-600 dark:text-base-neutral-300', props.classNames?.description)}
|
||||||
|
slot={props.slot ?? 'description'}
|
||||||
|
>
|
||||||
|
{props.description}
|
||||||
|
</RACText>
|
||||||
|
)}
|
||||||
|
{!props.title && props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DropdownSeparator(props: RACSeparatorProps) {
|
||||||
|
return (
|
||||||
|
<RACSeparator
|
||||||
|
{...props}
|
||||||
|
className={cx(
|
||||||
|
'bg-base-neutral-100 dark:bg-base-neutral-700 col-span-full mx-3 my-2 h-px forced-colors:bg-[ButtonBorder]',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
orientation="horizontal"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DropdownKeyboard(props: ComponentProps<typeof Keyboard>) {
|
||||||
|
return <Keyboard {...props} className={cx('absolute right-2 pl-2', props.className)} />;
|
||||||
|
}
|
||||||
117
apps/website/src/components/ui/Field.tsx
Normal file
117
apps/website/src/components/ui/Field.tsx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { AlertCircleIcon } from 'lucide-react';
|
||||||
|
import type { ReactNode } from 'react';
|
||||||
|
import type {
|
||||||
|
LabelProps as RACLabelProps,
|
||||||
|
TextProps as RACTextProps,
|
||||||
|
FieldErrorProps as RACFieldErrorProps,
|
||||||
|
GroupProps as RACGroupProps,
|
||||||
|
InputProps as RACInputProps,
|
||||||
|
} from 'react-aria-components';
|
||||||
|
import {
|
||||||
|
Label as RACLabel,
|
||||||
|
Text as RACText,
|
||||||
|
FieldError as RACFieldError,
|
||||||
|
Group as RACGroup,
|
||||||
|
Input as RACInput,
|
||||||
|
composeRenderProps,
|
||||||
|
} from 'react-aria-components';
|
||||||
|
import { compose, cva, cx } from '@/styles/cva';
|
||||||
|
import { focusRing } from '@/styles/ui/focusRing';
|
||||||
|
import { composeTailwindRenderProps } from '@/styles/util';
|
||||||
|
|
||||||
|
export function Label(props: RACLabelProps) {
|
||||||
|
return (
|
||||||
|
<RACLabel
|
||||||
|
{...props}
|
||||||
|
className={cx(
|
||||||
|
'text-base-neutral-800 dark:text-base-neutral-100 group-invalid:text-base-sunset-500 group-disabled:text-base-neutral-900 dark:group-disabled:text-base-neutral-40 text-base-label-md w-fit group-disabled:opacity-38',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Description(props: RACTextProps) {
|
||||||
|
return (
|
||||||
|
<RACText
|
||||||
|
{...props}
|
||||||
|
className={cx('text-base-neutral-600 dark:text-base-neutral-300 text-base-sm text-pretty', props.className)}
|
||||||
|
slot="description"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function FieldError(props: RACFieldErrorProps) {
|
||||||
|
return (
|
||||||
|
<RACFieldError
|
||||||
|
{...props}
|
||||||
|
className={composeTailwindRenderProps(
|
||||||
|
props.className,
|
||||||
|
'text-base-sunset-500 text-base-sm flex place-items-center gap-1.5 forced-colors:text-[Mark]',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<AlertCircleIcon aria-hidden className="shrink-0" data-slot="icon" size={18} strokeWidth={1.5} />
|
||||||
|
{props.children as ReactNode}
|
||||||
|
</RACFieldError>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const fieldGroupStyles = compose(
|
||||||
|
focusRing,
|
||||||
|
cva({
|
||||||
|
base: [
|
||||||
|
'group relative flex h-10 place-items-center overflow-hidden rounded-sm border transition duration-200 ease-out forced-colors:outline-[Highlight]',
|
||||||
|
'bg-base-neutral-0 border-base-neutral-300 dark:bg-base-neutral-800 dark:border-base-neutral-500',
|
||||||
|
'hover:border-base-neutral-200 dark:hover:border-base-neutral-600',
|
||||||
|
'focus-within:border-base-neutral-200 dark:focus-within:border-base-neutral-600',
|
||||||
|
'disabled:opacity-38 forced-colors:disabled:border forced-colors:disabled:border-[GrayText]',
|
||||||
|
'group-invalid:border-base-sunset-500 forced-colors:group-invalid:border-[Mark]',
|
||||||
|
'group-invalid:hover:border-base-sunset-200 dark:group-invalid:hover:border-base-sunset-700',
|
||||||
|
'group-invalid:focus-within:border-base-sunset-200 dark:group-invalid:focus-within:border-base-sunset-700',
|
||||||
|
'[&>[role=progressbar]:first-child]:ml-2 [&>[role=progressbar]:last-child]:mr-2',
|
||||||
|
'**:data-[slot=icon]:size-6 **:data-[slot=icon]:shrink-0 **:[button]:shrink-0',
|
||||||
|
'[&>button:has([data-slot=icon])]:absolute [&>button:has([data-slot=icon]):first-child]:left-0 [&>button:has([data-slot=icon]):last-child]:right-0',
|
||||||
|
'*:data-[slot=icon]:text-base-neutral-800 dark:*:data-[slot=icon]:text-base-neutral-100 *:data-[slot=icon]:pointer-events-none *:data-[slot=icon]:absolute *:data-[slot=icon]:top-[calc(var(--spacing)_*_1.7)] *:data-[slot=icon]:z-10 *:data-[slot=icon]:size-6',
|
||||||
|
'[&>[data-slot=icon]:first-child]:left-2 [&>[data-slot=icon]:last-child]:right-2',
|
||||||
|
'[&:has([data-slot=icon]+input)]:pl-7.5 [&:has(input+[data-slot=icon])]:pr-7.5',
|
||||||
|
'[&:has([data-slot=icon]+[role=group])]:pl-7.5 [&:has([role=group]+[data-slot=icon])]:pr-7.5',
|
||||||
|
'has-[[data-slot=icon]:last-child]:[&_input]:pr-7.5',
|
||||||
|
'*:[button]:size-6 *:[button]:p-0',
|
||||||
|
'[&>button:first-child]:ml-2 [&>button:last-child]:mr-2',
|
||||||
|
],
|
||||||
|
variants: {
|
||||||
|
isFocusWithin: {
|
||||||
|
true: 'outline-2',
|
||||||
|
false: 'outline-0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export function FieldGroup(props: RACGroupProps) {
|
||||||
|
return (
|
||||||
|
<RACGroup
|
||||||
|
{...props}
|
||||||
|
className={composeRenderProps(props.className, (className, renderProps) =>
|
||||||
|
fieldGroupStyles({
|
||||||
|
...renderProps,
|
||||||
|
className,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Input(props: RACInputProps) {
|
||||||
|
return (
|
||||||
|
<RACInput
|
||||||
|
{...props}
|
||||||
|
className={composeTailwindRenderProps(
|
||||||
|
props.className,
|
||||||
|
'text-base-neutral-900 placeholder:text-base-neutral-400 dark:placeholder:text-base-neutral-500 dark:text-base-neutral-40 text-base-lg sm:text-base-md w-full min-w-0 bg-transparent px-3 py-2.5 outline-hidden focus:outline-hidden [&::-ms-reveal]:hidden [&::-webkit-search-cancel-button]:hidden',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
38
apps/website/src/components/ui/Keyboard.tsx
Normal file
38
apps/website/src/components/ui/Keyboard.tsx
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
import { Keyboard as RACKeyboard } from 'react-aria-components';
|
||||||
|
import { cx } from '@/styles/cva';
|
||||||
|
|
||||||
|
type KeyboardProps = ComponentProps<'div'> & {
|
||||||
|
readonly classNames?: {
|
||||||
|
readonly base?: string;
|
||||||
|
readonly kbd?: string;
|
||||||
|
};
|
||||||
|
readonly keys: string[] | string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Keyboard(props: KeyboardProps) {
|
||||||
|
return (
|
||||||
|
<RACKeyboard
|
||||||
|
{...props}
|
||||||
|
className={cx(
|
||||||
|
'hidden group-disabled:opacity-50 lg:inline-flex forced-colors:group-focus:text-[HighlightText]',
|
||||||
|
props.classNames?.base,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{(Array.isArray(props.keys) ? props.keys : props.keys.split('')).map((char, idx) => (
|
||||||
|
<kbd
|
||||||
|
className={cx(
|
||||||
|
'inline-grid min-h-5 min-w-[2ch] place-content-center text-center font-sans text-[.75rem] uppercase',
|
||||||
|
idx > 0 && char.length > 1 && 'pl-1',
|
||||||
|
props.classNames?.kbd,
|
||||||
|
)}
|
||||||
|
key={idx}
|
||||||
|
>
|
||||||
|
{char}
|
||||||
|
</kbd>
|
||||||
|
))}
|
||||||
|
</RACKeyboard>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +1,67 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
export { ListBox, ListBoxItem } from 'react-aria-components';
|
import { CheckIcon, MenuIcon } from 'lucide-react';
|
||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
import type { ListBoxItemProps as RACListBoxItemProps, ListBoxProps as RACListBoxProps } from 'react-aria-components';
|
||||||
|
import { ListBoxItem as RACListBoxItem, ListBox as RACListBox, composeRenderProps } from 'react-aria-components';
|
||||||
|
import { DropdownLabel, DropdownSection, dropdownItemStyles } from '@/components/ui/Dropdown';
|
||||||
|
import { cx } from '@/styles/cva';
|
||||||
|
|
||||||
|
export function ListBox<Type extends object>(props: RACListBoxProps<Type>) {
|
||||||
|
return (
|
||||||
|
<RACListBox
|
||||||
|
{...props}
|
||||||
|
className={composeRenderProps(props.className, (className) =>
|
||||||
|
cx(
|
||||||
|
[
|
||||||
|
'border-base-neutral-200 dark:border-base-neutral-600 shadow-base-sm flex max-h-96 w-full min-w-40 flex-col gap-x-1 overflow-y-auto rounded-sm border p-2 outline-hidden [scrollbar-width:thin]',
|
||||||
|
"grid grid-cols-[1fr_auto] overflow-auto *:[[role='group']+[role=group]]:mt-4 *:[[role='group']+[role=separator]]:mt-1",
|
||||||
|
],
|
||||||
|
className,
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ListBoxItemProps<Type extends object> = RACListBoxItemProps<Type> & {
|
||||||
|
readonly className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ListBoxItem<Type extends object>(props: ListBoxItemProps<Type>) {
|
||||||
|
const textValue = props.textValue ?? (typeof props.children === 'string' ? props.children : undefined);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RACListBoxItem
|
||||||
|
{...props}
|
||||||
|
className={composeRenderProps(props.className, (className, renderProps) =>
|
||||||
|
dropdownItemStyles({
|
||||||
|
...renderProps,
|
||||||
|
className,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
textValue={textValue!}
|
||||||
|
>
|
||||||
|
{({ allowsDragging, isSelected, isFocused, isDragging }) => (
|
||||||
|
<>
|
||||||
|
{allowsDragging && (
|
||||||
|
<MenuIcon
|
||||||
|
className={cx('size-4 shrink-0 transition', isFocused && '', isDragging && '', isSelected && '')}
|
||||||
|
size={16}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isSelected && <CheckIcon className="-mx-0.5 mr-2" data-slot="checked-icon" size={16} />}
|
||||||
|
{typeof props.children === 'string' ? <DropdownLabel>{props.children}</DropdownLabel> : props.children}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</RACListBoxItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ListBoxSectionProps = ComponentProps<typeof DropdownSection>;
|
||||||
|
|
||||||
|
export function ListBoxSection(props: ListBoxSectionProps) {
|
||||||
|
return <DropdownSection {...props} className={cx(props.className, 'gap-1')} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { DropdownItemDetails as ListBoxItemDetails } from '@/components/ui/Dropdown';
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { ChevronsUpDown } from 'lucide-react';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import type { Key } from 'react-aria-components';
|
|
||||||
import { useMediaQuery } from 'usehooks-ts';
|
|
||||||
import { Drawer as Vaul } from 'vaul';
|
|
||||||
import { PACKAGES } from '~/util/constants';
|
|
||||||
import { Button } from './Button';
|
|
||||||
import { ListBox, ListBoxItem } from './ListBox';
|
|
||||||
import { Popover } from './Popover';
|
|
||||||
import { Select, SelectValue } from './Select';
|
|
||||||
|
|
||||||
export function PackageSelect({ packageName }: { readonly packageName: string }) {
|
|
||||||
const [selectedPackage, setSelectedPackage] = useState<Key>(packageName);
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const isMedium = useMediaQuery('(min-width: 768px)');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isMedium) {
|
|
||||||
setOpen(false);
|
|
||||||
}
|
|
||||||
}, [isMedium, setOpen]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Select
|
|
||||||
aria-label="Select a package"
|
|
||||||
className="hidden md:block"
|
|
||||||
onSelectionChange={(selected) => {
|
|
||||||
setSelectedPackage(selected);
|
|
||||||
}}
|
|
||||||
selectedKey={selectedPackage}
|
|
||||||
>
|
|
||||||
<Button className="flex w-full place-content-between place-items-center rounded-md bg-neutral-200 p-2 dark:bg-neutral-800">
|
|
||||||
<SelectValue className="font-medium" />
|
|
||||||
<ChevronsUpDown aria-hidden size={20} />
|
|
||||||
</Button>
|
|
||||||
<Popover className="max-h-60 w-[--trigger-width] overflow-auto rounded-md border border-neutral-300 bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800">
|
|
||||||
<ListBox items={PACKAGES} shouldFocusWrap>
|
|
||||||
{(item) => (
|
|
||||||
<ListBoxItem
|
|
||||||
className="flex p-2 outline-none data-[focus-visible]:bg-neutral-300 data-[hovered]:bg-neutral-300 data-[selected]:bg-blurple data-[selected]:data-[focus-visible]:bg-blurple-500 data-[selected]:data-[hovered]:bg-blurple-500 data-[selected]:text-white dark:data-[focus-visible]:bg-neutral-700 dark:data-[hovered]:bg-neutral-700 dark:data-[selected]:data-[focus-visible]:bg-blurple-500 dark:data-[selected]:data-[hovered]:bg-blurple-500"
|
|
||||||
href={`/docs/packages/${item.name}/stable`}
|
|
||||||
id={item.name}
|
|
||||||
textValue={item.name}
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</ListBoxItem>
|
|
||||||
)}
|
|
||||||
</ListBox>
|
|
||||||
</Popover>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<Vaul.NestedRoot onOpenChange={setOpen} open={open}>
|
|
||||||
<Vaul.Trigger
|
|
||||||
aria-label="Open package select"
|
|
||||||
className="flex w-full place-content-between place-items-center rounded-md bg-neutral-200 p-2 dark:bg-neutral-800 md:hidden"
|
|
||||||
>
|
|
||||||
<span className="font-medium">{selectedPackage}</span>
|
|
||||||
<ChevronsUpDown aria-hidden size={20} />
|
|
||||||
</Vaul.Trigger>
|
|
||||||
<Vaul.Portal>
|
|
||||||
<Vaul.Overlay className="fixed inset-0 bg-black/40" />
|
|
||||||
<Vaul.Content className="fixed bottom-0 left-0 right-0 flex max-h-[80%] flex-col rounded-t-lg bg-neutral-100 p-4 dark:bg-neutral-900">
|
|
||||||
<div className="mx-auto mb-8 h-1.5 w-12 flex-shrink-0 rounded-full bg-neutral-400" />
|
|
||||||
<ListBox
|
|
||||||
aria-label="Select a package"
|
|
||||||
className="flex flex-col gap-2 overflow-auto"
|
|
||||||
items={PACKAGES}
|
|
||||||
onSelectionChange={(selected) => {
|
|
||||||
const [val] = selected;
|
|
||||||
setSelectedPackage(val as Key);
|
|
||||||
}}
|
|
||||||
selectedKeys={[selectedPackage]}
|
|
||||||
selectionMode="single"
|
|
||||||
shouldFocusWrap
|
|
||||||
>
|
|
||||||
{(item) => (
|
|
||||||
<ListBoxItem
|
|
||||||
className="rounded-md p-2 outline-none data-[focus-visible]:bg-neutral-300 data-[hovered]:bg-neutral-300 data-[selected]:bg-blurple data-[selected]:data-[focus-visible]:bg-blurple-500 data-[selected]:data-[hovered]:bg-blurple-500 data-[selected]:text-white dark:data-[focus-visible]:bg-neutral-700 dark:data-[hovered]:bg-neutral-700 dark:data-[selected]:data-[focus-visible]:bg-blurple-500 dark:data-[selected]:data-[hovered]:bg-blurple-500"
|
|
||||||
href={`/docs/packages/${item.name}/stable`}
|
|
||||||
id={item.name}
|
|
||||||
textValue={item.name}
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</ListBoxItem>
|
|
||||||
)}
|
|
||||||
</ListBox>
|
|
||||||
</Vaul.Content>
|
|
||||||
</Vaul.Portal>
|
|
||||||
</Vaul.NestedRoot>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,3 +1,159 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
export { Popover } from 'react-aria-components';
|
import type { CSSProperties, ReactNode } from 'react';
|
||||||
|
import type {
|
||||||
|
DialogProps as RACDialogProps,
|
||||||
|
PopoverProps as RACPopoverProps,
|
||||||
|
ModalOverlayProps as RACModalOverlayProps,
|
||||||
|
DialogTriggerProps as RACDialogTriggerProps,
|
||||||
|
} from 'react-aria-components';
|
||||||
|
import {
|
||||||
|
Modal as RACModal,
|
||||||
|
ModalOverlay as RACModalOverlay,
|
||||||
|
OverlayArrow as RACOverlayArrow,
|
||||||
|
PopoverContext as RACPopoverContext,
|
||||||
|
DialogTrigger as RACDialogTrigger,
|
||||||
|
Popover as RACPopover,
|
||||||
|
composeRenderProps,
|
||||||
|
useSlottedContext,
|
||||||
|
} from 'react-aria-components';
|
||||||
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
|
import type { DialogBodyProps, DialogFooterProps, DialogHeaderProps, DialogTitleProps } from '@/components/ui/Dialog';
|
||||||
|
import { Dialog, DialogBody, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/Dialog';
|
||||||
|
import { cva, cx } from '@/styles/cva';
|
||||||
|
|
||||||
|
export function Popover(props: RACDialogTriggerProps) {
|
||||||
|
return <RACDialogTrigger {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PopoverTitle({ level = 2, ...props }: DialogTitleProps) {
|
||||||
|
return <DialogTitle {...props} className={cx('sm:leading-none', level === 2 && 'sm:text-lg', props.className)} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PopoverHeader(props: DialogHeaderProps) {
|
||||||
|
return <DialogHeader {...props} className={cx('sm:p-4', props.className)} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PopoverBody(props: DialogBodyProps) {
|
||||||
|
return <DialogBody {...props} className={cx('gap-0 sm:px-4 sm:pt-0', props.className)} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PopoverFooter(props: DialogFooterProps) {
|
||||||
|
return <DialogFooter {...props} className={cx('sm:p-4', props.className)} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentStyles = cva({
|
||||||
|
base: 'peer/popover-content border-base-neutral-200 dark:border-base-neutral-600 shadow-base-sm bg-base-neutral-0 dark:bg-base-neutral-800 text-base-md max-w-xs rounded-sm border bg-clip-padding transition-transform [scrollbar-width:thin] sm:max-w-3xl dark:backdrop-saturate-200 forced-colors:bg-[Canvas]',
|
||||||
|
variants: {
|
||||||
|
isPicker: {
|
||||||
|
true: 'max-h-72 min-w-(--trigger-width) overflow-y-auto',
|
||||||
|
false: 'min-w-80',
|
||||||
|
},
|
||||||
|
isMenu: {
|
||||||
|
true: '',
|
||||||
|
},
|
||||||
|
isEntering: {
|
||||||
|
true: 'fade-in animate-in data-[placement=left]:slide-in-from-right-1 data-[placement=right]:slide-in-from-left-1 data-[placement=top]:slide-in-from-bottom-1 data-[placement=bottom]:slide-in-from-top-1 duration-150 ease-out',
|
||||||
|
},
|
||||||
|
isExiting: {
|
||||||
|
true: 'fade-out animate-out data-[placement=left]:slide-out-to-right-1 data-[placement=right]:slide-out-to-left-1 data-[placement=top]:slide-out-to-bottom-1 data-[placement=bottom]:slide-out-to-top-1 duration-100 ease-in',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const drawerStyles = cva({
|
||||||
|
base: 'fixed top-auto bottom-0 z-50 max-h-full w-full max-w-2xl border border-b-transparent bg-neutral-100 outline-hidden dark:bg-neutral-900',
|
||||||
|
variants: {
|
||||||
|
isMenu: {
|
||||||
|
true: 'p-0 [&_[role=dialog]]:*:not-has-[[data-slot=dialog-body]]:px-1',
|
||||||
|
false: '',
|
||||||
|
},
|
||||||
|
isEntering: {
|
||||||
|
true: [
|
||||||
|
'[will-change:transform] [transition:transform_0.5s_cubic-bezier(0.32,_0.72,_0,_1)]',
|
||||||
|
'fade-in-0 slide-in-from-bottom-56 animate-in duration-200',
|
||||||
|
'[transition:translate3d(0,_100%,_0)]',
|
||||||
|
'sm:slide-in-from-bottom-auto sm:slide-in-from-top-[20%]',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
isExiting: {
|
||||||
|
true: 'slide-out-to-bottom-56 animate-out duration-200 ease-in',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type PopoverContentProps = Omit<RACModalOverlayProps, 'className'> &
|
||||||
|
Omit<RACPopoverProps, 'children' | 'className'> &
|
||||||
|
Pick<RACDialogProps, 'aria-label' | 'aria-labelledby'> & {
|
||||||
|
readonly children: ReactNode;
|
||||||
|
readonly className?: string | ((values: { defaultClassName?: string }) => string);
|
||||||
|
readonly respectScreen?: boolean;
|
||||||
|
readonly showArrow?: boolean;
|
||||||
|
readonly style?: CSSProperties;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function PopoverContent({ respectScreen = true, showArrow = true, ...props }: PopoverContentProps) {
|
||||||
|
const isMobile = useMediaQuery('(max-width: 600px)', { initializeWithValue: false });
|
||||||
|
const popoverContext = useSlottedContext(RACPopoverContext);
|
||||||
|
const isMenuTrigger = popoverContext?.trigger === 'MenuTrigger';
|
||||||
|
const isSubmenuTrigger = popoverContext?.trigger === 'SubmenuTrigger';
|
||||||
|
const isMenu = isMenuTrigger || isSubmenuTrigger;
|
||||||
|
const isComboBoxTrigger = popoverContext?.trigger === 'ComboBox';
|
||||||
|
const offset = props.offset ?? (showArrow ? 6 : 4);
|
||||||
|
const effectiveOffset = isSubmenuTrigger ? offset - 2 : offset;
|
||||||
|
|
||||||
|
return isMobile && respectScreen ? (
|
||||||
|
<RACModalOverlay
|
||||||
|
{...props}
|
||||||
|
className="fixed top-0 left-0 isolate z-50 h-(--visual-viewport-height) w-full [--visual-viewport-vertical-padding:16px]"
|
||||||
|
isDismissable
|
||||||
|
>
|
||||||
|
<RACModal
|
||||||
|
className={composeRenderProps(props.className as string, (className, renderProps) =>
|
||||||
|
drawerStyles({ ...renderProps, isMenu, className }),
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<Dialog aria-label={props['aria-label'] ?? 'List item'} role="dialog">
|
||||||
|
{props.children}
|
||||||
|
</Dialog>
|
||||||
|
</RACModal>
|
||||||
|
</RACModalOverlay>
|
||||||
|
) : (
|
||||||
|
<RACPopover
|
||||||
|
{...props}
|
||||||
|
className={composeRenderProps(props.className as string, (className, renderProps) =>
|
||||||
|
contentStyles({
|
||||||
|
...renderProps,
|
||||||
|
className,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
offset={effectiveOffset}
|
||||||
|
>
|
||||||
|
{showArrow && (
|
||||||
|
<RACOverlayArrow className="group">
|
||||||
|
<svg
|
||||||
|
className="fill-base-neutral-0 dark:fill-base-neutral-800 stroke-base-neutral-200 dark:stroke-base-neutral-600 block group-data-[placement=bottom]:rotate-180 group-data-[placement=left]:-rotate-90 group-data-[placement=right]:rotate-90 forced-colors:fill-[Canvas] forced-colors:stroke-[ButtonBorder]"
|
||||||
|
height={12}
|
||||||
|
viewBox="0 0 12 12"
|
||||||
|
width={12}
|
||||||
|
>
|
||||||
|
<path d="M0 0 L6 6 L12 0" />
|
||||||
|
</svg>
|
||||||
|
</RACOverlayArrow>
|
||||||
|
)}
|
||||||
|
{isComboBoxTrigger ? (
|
||||||
|
props.children
|
||||||
|
) : (
|
||||||
|
<Dialog aria-label={props['aria-label'] ?? 'List item'} role="dialog">
|
||||||
|
{props.children}
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
|
</RACPopover>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
DialogTrigger as PopoverTrigger,
|
||||||
|
DialogDescription as PopoverDescription,
|
||||||
|
DialogClose as PopoverClose,
|
||||||
|
} from '@/components/ui/Dialog';
|
||||||
|
|||||||
@@ -1,3 +1,133 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
export { Select, SelectValue } from 'react-aria-components';
|
import { ChevronDownIcon } from 'lucide-react';
|
||||||
|
import type { ComponentProps, ReactNode } from 'react';
|
||||||
|
import type {
|
||||||
|
ListBoxProps as RACListBoxProps,
|
||||||
|
SelectProps as RACSelectProps,
|
||||||
|
ValidationResult as RACValidationResult,
|
||||||
|
} from 'react-aria-components';
|
||||||
|
import {
|
||||||
|
Button as RACButton,
|
||||||
|
Select as RACSelect,
|
||||||
|
SelectValue as RACSelectValue,
|
||||||
|
composeRenderProps,
|
||||||
|
} from 'react-aria-components';
|
||||||
|
import type { Button } from '@/components/ui/Button';
|
||||||
|
import { Description, FieldError, Label } from '@/components/ui/Field';
|
||||||
|
import { ListBox } from '@/components/ui/ListBox';
|
||||||
|
import { PopoverContent, type PopoverContentProps } from '@/components/ui/Popover';
|
||||||
|
import { compose, cva, cx } from '@/styles/cva';
|
||||||
|
import { focusRing } from '@/styles/ui/focusRing';
|
||||||
|
import { composeTailwindRenderProps } from '@/styles/util';
|
||||||
|
|
||||||
|
const selectTriggerStyles = compose(
|
||||||
|
focusRing,
|
||||||
|
cva({
|
||||||
|
base: [
|
||||||
|
'relative flex h-10 w-full place-items-center overflow-hidden rounded-sm border transition duration-200 ease-out forced-colors:outline-[Highlight]',
|
||||||
|
'bg-base-neutral-0 border-base-neutral-300 dark:bg-base-neutral-800 dark:border-base-neutral-500',
|
||||||
|
'hover:border-base-neutral-200 dark:hover:border-base-neutral-600',
|
||||||
|
'focus-visible:border-base-neutral-200 dark:focus-visible:border-base-neutral-600',
|
||||||
|
'group-open:border-base-neutral-200 dark:group-open:border-base-neutral-600 group-open:outline-2',
|
||||||
|
'group-disabled:bg-base-neutral-100 group-disabled:border-base-neutral-100 dark:group-disabled:border-base-neutral-400 dark:group-disabled:bg-base-neutral-400 group-disabled:opacity-38 group-disabled:forced-colors:border group-disabled:forced-colors:border-[GrayText]',
|
||||||
|
'group-invalid:border-base-sunset-500 forced-colors:group-invalid:border-[Mark]',
|
||||||
|
'group-invalid:hover:border-base-sunset-200 dark:group-invalid:hover:border-base-sunset-700',
|
||||||
|
'group-invalid:focus-visible:border-base-sunset-200 dark:group-invalid:focus-visible:border-base-sunset-700',
|
||||||
|
'**:data-[slot=icon]:size-6 **:data-[slot=icon]:shrink-0 **:[button]:shrink-0',
|
||||||
|
'[&>button:has([data-slot=icon])]:absolute [&>button:has([data-slot=icon]):first-child]:left-0 [&>button:has([data-slot=icon]):last-child]:right-0',
|
||||||
|
'*:data-[slot=icon]:text-base-neutral-800 dark:*:data-[slot=icon]:text-base-neutral-100 *:data-[slot=icon]:pointer-events-none *:data-[slot=icon]:absolute *:data-[slot=icon]:top-[calc(var(--spacing)_*_1.7)] *:data-[slot=icon]:z-10 *:data-[slot=icon]:size-6',
|
||||||
|
'[&>[data-slot=icon]:first-child]:left-2 [&>[data-slot=icon]:last-child]:right-2',
|
||||||
|
'[&:has([data-slot=icon]+input)]:pl-7.5 [&:has(input+[data-slot=icon])]:pr-7.5',
|
||||||
|
'[&:has([data-slot=icon]+[role=group])]:pl-7.5 [&:has([role=group]+[data-slot=icon])]:pr-7.5',
|
||||||
|
'has-[[data-slot=icon]:last-child]:[&_input]:pr-7.5',
|
||||||
|
'*:[button]:size-6 *:[button]:p-0',
|
||||||
|
'[&>button:first-child]:ml-2 [&>button:last-child]:mr-2',
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
export type SelectProps<Type extends object> = RACSelectProps<Type> & {
|
||||||
|
readonly className?: string;
|
||||||
|
readonly description?: string;
|
||||||
|
readonly errorMessage?: string | ((validation: RACValidationResult) => string) | undefined;
|
||||||
|
readonly items?: Iterable<Type>;
|
||||||
|
readonly label?: ReactNode | string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Select<Type extends object>(props: SelectProps<Type>) {
|
||||||
|
return (
|
||||||
|
<RACSelect {...props} className={composeTailwindRenderProps(props.className, 'group flex w-full flex-col gap-1.5')}>
|
||||||
|
{(values) => (
|
||||||
|
<>
|
||||||
|
{props.label && <Label>{props.label}</Label>}
|
||||||
|
{typeof props.children === 'function' ? props.children(values) : props.children}
|
||||||
|
{props.description && <Description>{props.description}</Description>}
|
||||||
|
<FieldError>{props.errorMessage}</FieldError>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</RACSelect>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SelectListProps<Type extends object> = Pick<PopoverContentProps, 'placement'> &
|
||||||
|
RACListBoxProps<Type> & {
|
||||||
|
readonly classNames?: {
|
||||||
|
readonly popover?: PopoverContentProps['className'];
|
||||||
|
};
|
||||||
|
readonly items?: Iterable<Type>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SelectList<Type extends object>(props: SelectListProps<Type>) {
|
||||||
|
return (
|
||||||
|
<PopoverContent
|
||||||
|
className={cx('w-(--trigger-width)', props.classNames?.popover)}
|
||||||
|
placement={props.placement!}
|
||||||
|
respectScreen={false}
|
||||||
|
showArrow={false}
|
||||||
|
>
|
||||||
|
<ListBox {...props} className={cx('border-0', props.classNames?.popover)} items={props.items!}>
|
||||||
|
{props.children}
|
||||||
|
</ListBox>
|
||||||
|
</PopoverContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SelectTriggerProps = ComponentProps<typeof Button> & {
|
||||||
|
readonly className?: string;
|
||||||
|
readonly prefix?: ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SelectTrigger(props: SelectTriggerProps) {
|
||||||
|
return (
|
||||||
|
<RACButton
|
||||||
|
className={composeRenderProps(props.className, (className, renderProps) =>
|
||||||
|
selectTriggerStyles({
|
||||||
|
...renderProps,
|
||||||
|
className,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{props.prefix && <span className="-mr-1 ml-2 *:data-[slot=icon]:size-5.5">{props.prefix}</span>}
|
||||||
|
<RACSelectValue
|
||||||
|
className="text-base-neutral-900 group-disabled:data-placeholder:text-base-neutral-900 dark:group-disabled:data-placeholder:text-base-neutral-40 dark:data-placeholder:text-base-neutral-500 dark:text-base-neutral-40 data-placeholder:text-base-neutral-400 text-base-lg sm:text-base-md grid flex-1 grid-cols-[auto_1fr] place-items-start items-center px-3 py-2.5 *:data-[slot=avatar]:*:-mx-0.5 *:data-[slot=avatar]:-mx-0.5 *:data-[slot=avatar]:*:mr-2 *:data-[slot=avatar]:mr-2 *:data-[slot=icon]:-mx-0.5 *:data-[slot=icon]:mr-1 *:data-[slot=icon]:size-5.5 [&_[slot=description]]:hidden *:[span]:col-start-2"
|
||||||
|
data-slot="select-value"
|
||||||
|
/>
|
||||||
|
<ChevronDownIcon
|
||||||
|
aria-hidden
|
||||||
|
className="size-6 shrink-0 duration-200 group-open:rotate-180 forced-colors:text-[ButtonText] forced-colors:group-disabled:text-[GrayText]"
|
||||||
|
data-slot="icon"
|
||||||
|
size={24}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
/>
|
||||||
|
</RACButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
DropdownSection as SelectSection,
|
||||||
|
DropdownSeparator as SelectSeparator,
|
||||||
|
DropdownLabel as SelectLabel,
|
||||||
|
DropdownItemDetails as SelectOptionDetails,
|
||||||
|
DropdownItem as SelectOption,
|
||||||
|
} from '@/components/ui/Dropdown';
|
||||||
|
|||||||
148
apps/website/src/components/ui/Sheet.tsx
Normal file
148
apps/website/src/components/ui/Sheet.tsx
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
import type { VariantProps } from 'cva';
|
||||||
|
import type { ComponentProps } from 'react';
|
||||||
|
import type {
|
||||||
|
DialogProps as RACDialogProps,
|
||||||
|
DialogTriggerProps as RACDialogTriggerProps,
|
||||||
|
ModalOverlayProps as RACModalOverlayProps,
|
||||||
|
} from 'react-aria-components';
|
||||||
|
import {
|
||||||
|
DialogTrigger as RACDialogTrigger,
|
||||||
|
Modal as RACModal,
|
||||||
|
ModalOverlay as RACModalOverlay,
|
||||||
|
composeRenderProps,
|
||||||
|
} from 'react-aria-components';
|
||||||
|
import { Dialog, DialogCloseIndicator } from '@/components/ui/Dialog';
|
||||||
|
import { cva } from '@/styles/cva';
|
||||||
|
|
||||||
|
const overlayStyles = cva({
|
||||||
|
base: 'fixed top-0 left-0 isolate z-50 flex h-(--visual-viewport-height) w-full place-content-center place-items-center bg-neutral-900/15 p-4 dark:bg-neutral-900/40',
|
||||||
|
variants: {
|
||||||
|
isBlurred: {
|
||||||
|
true: 'supports-backdrop-filter:backdrop-blur',
|
||||||
|
},
|
||||||
|
isEntering: {
|
||||||
|
true: 'fade-in animate-in duration-300 ease-out',
|
||||||
|
},
|
||||||
|
isExiting: {
|
||||||
|
true: 'fade-out animate-out duration-200 ease-in',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
type Sides = 'bottom' | 'left' | 'right' | 'top';
|
||||||
|
const generateCompoundVariants = (sides: Sides[]) =>
|
||||||
|
sides.map((side) => ({
|
||||||
|
side,
|
||||||
|
isFloat: true,
|
||||||
|
className:
|
||||||
|
side === 'top'
|
||||||
|
? 'top-2 inset-x-2 border-b-0'
|
||||||
|
: side === 'bottom'
|
||||||
|
? 'bottom-2 inset-x-2 border-t-0'
|
||||||
|
: side === 'left'
|
||||||
|
? 'left-2 inset-y-2 border-r-0'
|
||||||
|
: 'right-2 inset-y-2 border-l-0',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const contentStyles = cva({
|
||||||
|
base: 'shadow-base-md border-base-neutral-200 dark:border-base-neutral-600 fixed z-50 grid gap-4 bg-neutral-100 transition ease-in-out dark:bg-neutral-900',
|
||||||
|
variants: {
|
||||||
|
isEntering: {
|
||||||
|
true: 'animate-in duration-300',
|
||||||
|
},
|
||||||
|
isExiting: {
|
||||||
|
true: 'animate-out duration-200',
|
||||||
|
},
|
||||||
|
side: {
|
||||||
|
top: 'entering:slide-in-from-top exiting:slide-out-to-top inset-x-0 top-0 border-b',
|
||||||
|
bottom: 'entering:slide-in-from-bottom exiting:slide-out-to-bottom inset-x-0 bottom-0 border-t',
|
||||||
|
left: 'entering:slide-in-from-left exiting:slide-out-to-left inset-y-0 left-0 h-auto w-full max-w-xs overflow-y-auto border-r',
|
||||||
|
right:
|
||||||
|
'entering:slide-in-from-right exiting:slide-out-to-right inset-y-0 right-0 h-auto w-full max-w-xs overflow-y-auto border-l',
|
||||||
|
},
|
||||||
|
isFloat: {
|
||||||
|
true: '',
|
||||||
|
false: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compoundVariants: generateCompoundVariants(['top', 'bottom', 'left', 'right']),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SheetProps = RACDialogTriggerProps;
|
||||||
|
|
||||||
|
export function Sheet(props: SheetProps) {
|
||||||
|
return <RACDialogTrigger {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SheetContentProps = Omit<ComponentProps<typeof RACModal>, 'children' | 'className'> &
|
||||||
|
Omit<RACModalOverlayProps, 'className'> &
|
||||||
|
VariantProps<typeof overlayStyles> & {
|
||||||
|
readonly 'aria-label'?: RACDialogProps['aria-label'];
|
||||||
|
readonly 'aria-labelledby'?: RACDialogProps['aria-labelledby'];
|
||||||
|
readonly classNames?: {
|
||||||
|
content?: RACModalOverlayProps['className'];
|
||||||
|
overlay?: RACModalOverlayProps['className'];
|
||||||
|
};
|
||||||
|
readonly closeButton?: boolean;
|
||||||
|
readonly isBlurred?: boolean;
|
||||||
|
readonly isFloat?: boolean;
|
||||||
|
readonly role?: RACDialogProps['role'];
|
||||||
|
readonly side?: Sides;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SheetContent({
|
||||||
|
isBlurred = false,
|
||||||
|
isDismissable = true,
|
||||||
|
side = 'right',
|
||||||
|
role = 'dialog',
|
||||||
|
closeButton = true,
|
||||||
|
isFloat = true,
|
||||||
|
...props
|
||||||
|
}: SheetContentProps) {
|
||||||
|
const _isDismissable = role === 'alertdialog' ? false : isDismissable;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RACModalOverlay
|
||||||
|
{...props}
|
||||||
|
className={composeRenderProps(props.classNames?.overlay, (className, renderProps) =>
|
||||||
|
overlayStyles({
|
||||||
|
...renderProps,
|
||||||
|
isBlurred,
|
||||||
|
className,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
isDismissable={_isDismissable}
|
||||||
|
>
|
||||||
|
<RACModal
|
||||||
|
{...props}
|
||||||
|
className={composeRenderProps(props.classNames?.content, (className, renderProps) =>
|
||||||
|
contentStyles({
|
||||||
|
...renderProps,
|
||||||
|
side,
|
||||||
|
isFloat,
|
||||||
|
className,
|
||||||
|
}),
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{(values) => (
|
||||||
|
<Dialog aria-label={props['aria-label'] ?? undefined!} className="h-full" role={role}>
|
||||||
|
<>
|
||||||
|
{typeof props.children === 'function' ? props.children(values) : props.children}
|
||||||
|
{closeButton && <DialogCloseIndicator className="top-2.5 right-2.5" isDismissable={_isDismissable} />}
|
||||||
|
</>
|
||||||
|
</Dialog>
|
||||||
|
)}
|
||||||
|
</RACModal>
|
||||||
|
</RACModalOverlay>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
DialogTrigger as SheetTrigger,
|
||||||
|
DialogBody as SheetBody,
|
||||||
|
DialogClose as SheetClose,
|
||||||
|
DialogDescription as SheetDescription,
|
||||||
|
DialogFooter as SheetFooter,
|
||||||
|
DialogHeader as SheetHeader,
|
||||||
|
DialogTitle as SheetTitle,
|
||||||
|
} from '@/components/ui/Dialog';
|
||||||
485
apps/website/src/components/ui/Sidebar.tsx
Normal file
485
apps/website/src/components/ui/Sidebar.tsx
Normal file
@@ -0,0 +1,485 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { MenuIcon, SidebarIcon, XIcon } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
use,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
type ComponentProps,
|
||||||
|
} from 'react';
|
||||||
|
import { chain } from 'react-aria';
|
||||||
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
|
import { Button, type ButtonProps } from '@/components/ui/Button';
|
||||||
|
import { SheetBody, SheetContent, type SheetContentProps } from '@/components/ui/Sheet';
|
||||||
|
import { cva, cx } from '@/styles/cva';
|
||||||
|
|
||||||
|
const SIDEBAR_COOKIE_NAME = 'sidebar:state';
|
||||||
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
||||||
|
|
||||||
|
interface SidebarContextProps {
|
||||||
|
isMobile: boolean;
|
||||||
|
open: boolean;
|
||||||
|
openMobile: boolean;
|
||||||
|
setOpen(open: boolean | ((open: boolean) => boolean)): void;
|
||||||
|
setOpenMobile(open: boolean | ((open: boolean) => boolean)): void;
|
||||||
|
state: 'collapsed' | 'expanded';
|
||||||
|
}
|
||||||
|
|
||||||
|
const SidebarContext = createContext<SidebarContextProps | null>(null);
|
||||||
|
|
||||||
|
export function useSidebar() {
|
||||||
|
const context = use(SidebarContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error('useSidebar must be used within a Sidebar.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SidebarProviderProps = ComponentProps<'div'> & {
|
||||||
|
readonly defaultOpen?: boolean;
|
||||||
|
readonly isOpen?: boolean;
|
||||||
|
onOpenChange?(open: boolean): void;
|
||||||
|
readonly shortcut?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SidebarProvider({
|
||||||
|
defaultOpen = false,
|
||||||
|
isOpen: openProp,
|
||||||
|
onOpenChange: setOpenProp,
|
||||||
|
shortcut = 'b',
|
||||||
|
...props
|
||||||
|
}: SidebarProviderProps) {
|
||||||
|
const isMobile = useMediaQuery('(max-width: 767px)', { initializeWithValue: false });
|
||||||
|
const [openMobile, setOpenMobile] = useState(false);
|
||||||
|
|
||||||
|
const [internalOpenState, setInternalOpenState] = useState(defaultOpen);
|
||||||
|
const open = openProp ?? internalOpenState;
|
||||||
|
const setOpen = useCallback(
|
||||||
|
(value: boolean | ((value: boolean) => boolean)) => {
|
||||||
|
const openState = typeof value === 'function' ? value(open) : value;
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
setOpenMobile((open) => !open);
|
||||||
|
} else if (setOpenProp) {
|
||||||
|
setOpenProp(openState);
|
||||||
|
} else {
|
||||||
|
setInternalOpenState(openState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line react-compiler/react-compiler
|
||||||
|
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
|
||||||
|
},
|
||||||
|
[setOpenProp, open, isMobile, setOpenMobile],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
if (event.key === shortcut && (event.metaKey || event.ctrlKey)) {
|
||||||
|
event.preventDefault();
|
||||||
|
setOpen((open) => !open);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
}, [shortcut, setOpen]);
|
||||||
|
|
||||||
|
const state = open ? 'expanded' : 'collapsed';
|
||||||
|
|
||||||
|
const contextValue = useMemo<SidebarContextProps>(
|
||||||
|
() => ({
|
||||||
|
state,
|
||||||
|
open,
|
||||||
|
setOpen,
|
||||||
|
openMobile,
|
||||||
|
setOpenMobile,
|
||||||
|
isMobile,
|
||||||
|
}),
|
||||||
|
[state, open, setOpen, openMobile, setOpenMobile, isMobile],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SidebarContext value={contextValue}>
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={cx(
|
||||||
|
'@container/sidebar **:data-[slot=icon]:shrink-0',
|
||||||
|
'[--sidebar-width-dock:3.25rem] [--sidebar-width-mobile:20rem] [--sidebar-width:20rem]',
|
||||||
|
'[--sidebar-border:var(--color-base-neutral-200)]',
|
||||||
|
'dark:[--sidebar-border:var(--color-base-neutral-600)]',
|
||||||
|
'flex min-h-dvh w-full',
|
||||||
|
'group/sidebar-root has-data-[sidebar-intent=inset]:bg-[#f3f3f4] dark:has-data-[sidebar-intent=inset]:bg-[#121214]',
|
||||||
|
'[@-moz-document_url-prefix()]:overflow-x-hidden',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
</SidebarContext>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sidebarGapStyles = cva({
|
||||||
|
base: [
|
||||||
|
'w-(--sidebar-width) group-data-[sidebar-collapsible=hidden]/sidebar-container:w-0',
|
||||||
|
'relative h-dvh bg-transparent transition-[width] duration-100 ease-linear',
|
||||||
|
'group-data-[sidebar-side=right]/sidebar-container:rotate-180',
|
||||||
|
],
|
||||||
|
variants: {
|
||||||
|
intent: {
|
||||||
|
default: 'group-data-[sidebar-collapsible=dock]/sidebar-container:w-(--sidebar-width-dock)',
|
||||||
|
fleet: 'group-data-[sidebar-collapsible=dock]/sidebar-container:w-(--sidebar-width-dock)',
|
||||||
|
float:
|
||||||
|
'group-data-[sidebar-collapsible=dock]/sidebar-container:w-[calc(var(--sidebar-width-dock)+theme(spacing.4))]',
|
||||||
|
inset:
|
||||||
|
'group-data-[sidebar-collapsible=dock]/sidebar-container:w-[calc(var(--sidebar-width-dock)+theme(spacing.2))]',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const sidebarStyles = cva({
|
||||||
|
base: [
|
||||||
|
'fixed inset-y-0 z-10 hidden h-dvh w-(--sidebar-width) transition-[left,right,width] duration-100 ease-linear md:flex',
|
||||||
|
'min-h-dvh bg-[#f3f3f4] dark:bg-[#121214]',
|
||||||
|
'**:data-[slot=disclosure]:border-0 **:data-[slot=disclosure]:px-2.5',
|
||||||
|
'has-data-[sidebar-intent=default]:shadow-base-md',
|
||||||
|
'[@-moz-document_url-prefix()]:h-full [@-moz-document_url-prefix()]:min-h-full',
|
||||||
|
],
|
||||||
|
variants: {
|
||||||
|
side: {
|
||||||
|
left: 'left-0 group-data-[sidebar-collapsible=hidden]/sidebar-container:left-[calc(var(--sidebar-width)*-1)]',
|
||||||
|
right: 'right-0 group-data-[sidebar-collapsible=hidden]/sidebar-container:right-[calc(var(--sidebar-width)*-1)]',
|
||||||
|
},
|
||||||
|
intent: {
|
||||||
|
default: [
|
||||||
|
'group-data-[sidebar-collapsible=dock]/sidebar-container:w-(--sidebar-width-dock) group-data-[sidebar-side=left]/sidebar-container:border-(--sidebar-border) group-data-[sidebar-side=right]/sidebar-container:border-(--sidebar-border)',
|
||||||
|
'group-data-[sidebar-side=left]/sidebar-container:border-r group-data-[sidebar-side=right]/sidebar-container:border-l',
|
||||||
|
],
|
||||||
|
fleet: [
|
||||||
|
'group-data-[sidebar-collapsible=dock]/sidebar-container:w-(--sidebar-width-dock)',
|
||||||
|
'**:data-sidebar-disclosure:gap-y-0 **:data-sidebar-disclosure:px-0 **:data-sidebar-section:gap-y-0 **:data-sidebar-section:px-0',
|
||||||
|
'group-data-[sidebar-side=left]/sidebar-container:border-r group-data-[sidebar-side=right]/sidebar-container:border-l',
|
||||||
|
],
|
||||||
|
float: 'bg-bg p-2 group-data-[sidebar-collapsible=dock]/sidebar-container:w-[calc(var+theme(spacing.4)+2px)]',
|
||||||
|
inset: [
|
||||||
|
'p-2 group-data-[sidebar-collapsible=dock]/sidebar-container:w-[calc(var(--sidebar-width-dock)+theme(spacing.2)+2px)]',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SidebarProps = ComponentProps<'div'> &
|
||||||
|
SheetContentProps & {
|
||||||
|
readonly closeButton?: boolean;
|
||||||
|
readonly collapsible?: 'dock' | 'hidden' | 'none';
|
||||||
|
readonly intent?: 'default' | 'fleet' | 'float' | 'inset';
|
||||||
|
readonly side?: 'left' | 'right';
|
||||||
|
};
|
||||||
|
|
||||||
|
export function Sidebar({
|
||||||
|
closeButton = true,
|
||||||
|
collapsible = 'hidden',
|
||||||
|
side = 'left',
|
||||||
|
intent = 'default',
|
||||||
|
...props
|
||||||
|
}: SidebarProps) {
|
||||||
|
const { isMobile, state, open, openMobile, setOpenMobile } = useSidebar();
|
||||||
|
const [needsScrollbarGutter, setNeedsScrollbarGutter] = useState(false);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (collapsible === 'none' || isMobile || side !== 'right') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scrollbarVisible = (element: HTMLElement) => element.scrollHeight > element.clientHeight;
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
if (mutations[0]?.type === 'attributes' && scrollbarVisible(document.documentElement) && open) {
|
||||||
|
if (getComputedStyle(document.documentElement).paddingRight === '0px') {
|
||||||
|
setNeedsScrollbarGutter(false);
|
||||||
|
} else {
|
||||||
|
setNeedsScrollbarGutter(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setNeedsScrollbarGutter(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.documentElement, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ['style'],
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
observer.disconnect();
|
||||||
|
};
|
||||||
|
}, [collapsible, isMobile, open, side]);
|
||||||
|
|
||||||
|
if (collapsible === 'none') {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={cx('flex h-full w-(--sidebar-width) flex-col border-r border-(--sidebar-border)', props.className)}
|
||||||
|
data-sidebar-collapsible="none"
|
||||||
|
data-sidebar-intent={intent}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<SheetContent
|
||||||
|
{...props}
|
||||||
|
aria-label="Sidebar"
|
||||||
|
closeButton={closeButton}
|
||||||
|
data-sidebar-intent="default"
|
||||||
|
isFloat={intent === 'float'}
|
||||||
|
isOpen={openMobile}
|
||||||
|
onOpenChange={setOpenMobile}
|
||||||
|
side={side}
|
||||||
|
>
|
||||||
|
<SheetBody className="gap-0 p-0">{props.children}</SheetBody>
|
||||||
|
</SheetContent>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className="group/sidebar-container peer hidden md:block"
|
||||||
|
data-sidebar-collapsible={state === 'collapsed' ? collapsible : ''}
|
||||||
|
data-sidebar-intent={intent}
|
||||||
|
data-sidebar-side={side}
|
||||||
|
data-sidebar-state={state}
|
||||||
|
>
|
||||||
|
<div aria-hidden className={sidebarGapStyles({ intent })} />
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={sidebarStyles({
|
||||||
|
side,
|
||||||
|
intent,
|
||||||
|
className: cx(props.className, needsScrollbarGutter && 'right-[11px]', 'transition-[left,width]'),
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cx(
|
||||||
|
'flex h-full w-full flex-col',
|
||||||
|
'group-data-[sidebar-intent=inset]/sidebar-container:bg-sidebar dark:group-data-[sidebar-intent=inset]/sidebar-container:bg-bg',
|
||||||
|
'group-data-[sidebar-intent=float]/sidebar-container:bg-sidebar group-data-[sidebar-intent=float]/sidebar-container:shadow-base-md group-data-[sidebar-intent=float]/sidebar-container:rounded-lg group-data-[sidebar-intent=float]/sidebar-container:border group-data-[sidebar-intent=float]/sidebar-container:border-(--sidebar-border)',
|
||||||
|
)}
|
||||||
|
data-sidebar="default"
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SidebarInsetAnchor({
|
||||||
|
collapsible = 'hidden',
|
||||||
|
side = 'left',
|
||||||
|
intent = 'default',
|
||||||
|
...props
|
||||||
|
}: SidebarProps) {
|
||||||
|
const { state } = useSidebar();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className="group/sidebar-container peer hidden"
|
||||||
|
data-sidebar-collapsible={state === 'collapsed' ? collapsible : ''}
|
||||||
|
data-sidebar-intent={intent}
|
||||||
|
data-sidebar-side={side}
|
||||||
|
data-sidebar-state={state}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sidebarHeaderStyles = cva({
|
||||||
|
base: 'dark:bg-base-neutral-800 bg-base-neutral-0 flex flex-col **:data-[slot=sidebar-label-mask]:hidden',
|
||||||
|
variants: {
|
||||||
|
collapsed: {
|
||||||
|
true: 'mt-2 p-12 group-data-[sidebar-intent=float]/sidebar-container:mt-2 md:mx-auto md:size-9 md:items-center md:justify-center md:rounded-lg md:p-0 md:hover:bg-(--sidebar-accent)',
|
||||||
|
false: 'px-6 pt-8 pb-4',
|
||||||
|
},
|
||||||
|
hasBorder: {
|
||||||
|
true: 'border-base-neutral-100 dark:border-base-neutral-700 border-b',
|
||||||
|
false: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SidebarHeaderProps = ComponentProps<'div'> & {
|
||||||
|
readonly hasBorder?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SidebarHeader({ hasBorder = false, ...props }: SidebarHeaderProps) {
|
||||||
|
const { state } = use(SidebarContext)!;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={sidebarHeaderStyles({ collapsed: state === 'collapsed', hasBorder, className: props.className })}
|
||||||
|
data-sidebar-header="true"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sidebarFooterStyles = cva({
|
||||||
|
base: [
|
||||||
|
'flex flex-col p-6',
|
||||||
|
'in-data-[sidebar-intent=fleet]:mt-0 in-data-[sidebar-intent=fleet]:p-0',
|
||||||
|
'in-data-[sidebar-intent=fleet]:**:data-[slot=menu-trigger]:rounded-none',
|
||||||
|
'**:data-[slot=menu-trigger]:relative **:data-[slot=menu-trigger]:overflow-hidden',
|
||||||
|
'**:data-[slot=menu-trigger]:rounded-lg',
|
||||||
|
'sm:**:data-[slot=menu-trigger]:text-base-md **:data-[slot=menu-trigger]:flex **:data-[slot=menu-trigger]:cursor-default **:data-[slot=menu-trigger]:items-center **:data-[slot=menu-trigger]:p-2 **:data-[slot=menu-trigger]:outline-hidden',
|
||||||
|
'**:data-[slot=menu-trigger]:hover:text-fg **:data-[slot=menu-trigger]:hover:bg-(--sidebar-accent)',
|
||||||
|
],
|
||||||
|
variants: {
|
||||||
|
collapsed: {
|
||||||
|
true: [
|
||||||
|
'**:data-[slot=avatar]:size-6 **:data-[slot=avatar]:*:size-6',
|
||||||
|
'**:data-[slot=chevron]:hidden **:data-[slot=menu-label]:hidden',
|
||||||
|
'**:data-[slot=menu-trigger]:grid **:data-[slot=menu-trigger]:size-8 **:data-[slot=menu-trigger]:place-content-center',
|
||||||
|
],
|
||||||
|
false: [
|
||||||
|
'**:data-[slot=avatar]:size-8 **:data-[slot=avatar]:*:size-8 **:data-[slot=menu-trigger]:**:data-[slot=avatar]:mr-2',
|
||||||
|
'**:data-[slot=menu-trigger]:pressed:**:data-[slot=chevron]:rotate-180 **:data-[slot=menu-trigger]:w-full **:data-[slot=menu-trigger]:**:data-[slot=chevron]:ml-auto **:data-[slot=menu-trigger]:**:data-[slot=chevron]:transition-transform',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
hasBorder: {
|
||||||
|
true: 'border-base-neutral-100 dark:border-base-neutral-700 border-t',
|
||||||
|
false: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export type SidebarFooterProps = ComponentProps<'div'> & {
|
||||||
|
readonly hasBorder?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SidebarFooter({ hasBorder = false, ...props }: SidebarFooterProps) {
|
||||||
|
const { state, isMobile } = useSidebar();
|
||||||
|
const collapsed = state === 'collapsed' && !isMobile;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={sidebarFooterStyles({ collapsed, hasBorder, className: props.className })}
|
||||||
|
data-sidebar-footer="true"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SidebarContent(props: ComponentProps<'div'>) {
|
||||||
|
const { state } = useSidebar();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
className={cx(
|
||||||
|
'dark:bg-base-neutral-800 bg-base-neutral-0 flex min-h-0 flex-1 scroll-mb-96 flex-col overflow-auto p-6 *:data-sidebar-section:border-l-0',
|
||||||
|
state === 'collapsed' && 'place-items-center',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
data-sidebar-content="true"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SidebarInset(props: ComponentProps<'main'>) {
|
||||||
|
return (
|
||||||
|
<main
|
||||||
|
{...props}
|
||||||
|
className={cx(
|
||||||
|
'relative flex min-h-dvh w-full flex-1 flex-col peer-data-[sidebar-intent=inset]:border peer-data-[sidebar-intent=inset]:border-transparent',
|
||||||
|
'bg-bg dark:peer-data-[sidebar-intent=inset]:bg-sidebar peer-data-[sidebar-intent=inset]:overflow-hidden',
|
||||||
|
'md:peer-data-[sidebar-intent=inset]:shadow-base-md peer-data-[sidebar-intent=inset]:min-h-[calc(100dvh-theme(spacing.4))] md:peer-data-[sidebar-intent=inset]:m-2 md:peer-data-[sidebar-intent=inset]:rounded-xl md:peer-data-[sidebar-intent=inset]:peer-data-[sidebar-side=left]:ml-0 md:peer-data-[sidebar-intent=inset]:peer-data-[sidebar-side=right]:mr-0 md:peer-data-[sidebar-state=collapsed]:peer-data-[sidebar-intent=inset]:peer-data-[sidebar-side=left]:ml-2 md:peer-data-[sidebar-state=collapsed]:peer-data-[sidebar-intent=inset]:peer-data-[sidebar-side=right]:mr-2',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SidebarTrigger({ onPress, children, ...props }: ButtonProps) {
|
||||||
|
const { setOpen } = useSidebar();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
{...props}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||||
|
aria-label={props['aria-label'] || 'Toggle Sidebar'}
|
||||||
|
data-sidebar-trigger="true"
|
||||||
|
onPress={(event) => {
|
||||||
|
onPress?.(event);
|
||||||
|
setOpen((open) => !open);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing */}
|
||||||
|
{children || (
|
||||||
|
<>
|
||||||
|
<SidebarIcon aria-hidden className="hidden md:inline" data-slot="icon" size={18} />
|
||||||
|
<MenuIcon aria-hidden className="inline md:hidden" data-slot="icon" size={18} />
|
||||||
|
<span className="sr-only">Toggle Sidebar</span>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CloseButtonIndicatorProps = Omit<ButtonProps, 'children'> & {
|
||||||
|
readonly className?: string;
|
||||||
|
readonly isDismissable?: boolean | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SidebarCloseIndicator({ isDismissable = true, ...props }: CloseButtonIndicatorProps) {
|
||||||
|
const { setOpen } = useSidebar();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
{...props}
|
||||||
|
aria-label="Close"
|
||||||
|
className={cx(
|
||||||
|
'close text-base-neutral-500 hover:text-base-neutral-700 focus-visible:text-base-neutral-700 pressed:text-base-neutral-900 dark:text-base-neutral-400 dark:hover:text-base-neutral-200 dark:focus-visible:text-base-neutral-200 dark:pressed:text-base-neutral-500 disabled:text-base-neutral-300 dark:disabled:text-base-neutral-300 z-50 rounded-full',
|
||||||
|
props.className,
|
||||||
|
)}
|
||||||
|
onPress={isDismissable ? chain(() => setOpen((open) => !open), props.onPress) : props.onPress!}
|
||||||
|
size="icon-xs"
|
||||||
|
slot={props.slot === null ? null : (props.slot ?? 'close')}
|
||||||
|
variant="unset"
|
||||||
|
>
|
||||||
|
<XIcon aria-hidden className="size-4.5 stroke-[1.5]" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SidebarRail({ className, ref, ...props }: ComponentProps<'button'>) {
|
||||||
|
const { setOpen } = useSidebar();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
aria-label="Toggle Sidebar"
|
||||||
|
className={cx(
|
||||||
|
'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 outline-hidden transition-all ease-linear group-data-[sidebar-side=left]/sidebar-container:-right-4 group-data-[sidebar-side=right]/sidebar-container:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] data-hovered:after:bg-transparent sm:flex',
|
||||||
|
'in-data-[sidebar-side=left]:cursor-w-resize in-data-[sidebar-side=right]:cursor-e-resize',
|
||||||
|
'[[data-sidebar-side=left][data-sidebar-state=collapsed]_&]:cursor-e-resize [[data-sidebar-side=right][data-sidebar-state=collapsed]_&]:cursor-w-resize',
|
||||||
|
'group-data-[sidebar-collapsible=hidden]/sidebar-container:hover:bg-secondary group-data-[sidebar-collapsible=hidden]/sidebar-container:translate-x-0 group-data-[sidebar-collapsible=hidden]/sidebar-container:after:left-full',
|
||||||
|
'[[data-sidebar-side=left][data-sidebar-collapsible=hidden]_&]:-right-2 [[data-sidebar-side=right][data-sidebar-collapsible=hidden]_&]:-left-2',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
data-sidebar="rail"
|
||||||
|
onClick={() => setOpen((open) => !open)}
|
||||||
|
ref={ref}
|
||||||
|
tabIndex={-1}
|
||||||
|
title="Toggle Sidebar"
|
||||||
|
type="button"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { VscColorMode } from '@react-icons/all-files/vsc/VscColorMode';
|
|
||||||
import { useTheme } from 'next-themes';
|
|
||||||
import { Button } from './Button';
|
|
||||||
|
|
||||||
export function ThemeSwitch() {
|
|
||||||
const { resolvedTheme, setTheme } = useTheme();
|
|
||||||
const toggleTheme = () => setTheme(resolvedTheme === 'light' ? 'dark' : 'light');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button aria-label="Toggle theme" className="rounded-full" onPress={() => toggleTheme()}>
|
|
||||||
<VscColorMode aria-hidden size={24} />
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
'use client';
|
|
||||||
|
|
||||||
import { ChevronsUpDown } from 'lucide-react';
|
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
import type { Key } from 'react-aria-components';
|
|
||||||
import { useMediaQuery } from 'usehooks-ts';
|
|
||||||
import { Drawer as Vaul } from 'vaul';
|
|
||||||
import { Button } from './Button';
|
|
||||||
import { ListBox, ListBoxItem } from './ListBox';
|
|
||||||
import { Popover } from './Popover';
|
|
||||||
import { Select, SelectValue } from './Select';
|
|
||||||
|
|
||||||
export function VersionSelect({
|
|
||||||
packageName,
|
|
||||||
version,
|
|
||||||
versions,
|
|
||||||
}: {
|
|
||||||
readonly packageName: string;
|
|
||||||
readonly version: string;
|
|
||||||
readonly versions: { readonly version: string }[];
|
|
||||||
}) {
|
|
||||||
const [selectedVersion, setSelectedVersion] = useState<Key>(version);
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const isMedium = useMediaQuery('(min-width: 768px)');
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (isMedium) {
|
|
||||||
setOpen(false);
|
|
||||||
}
|
|
||||||
}, [isMedium, setOpen]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Select
|
|
||||||
aria-label="Select a version"
|
|
||||||
className="hidden md:block"
|
|
||||||
onSelectionChange={(selected) => {
|
|
||||||
setSelectedVersion(selected);
|
|
||||||
}}
|
|
||||||
selectedKey={selectedVersion}
|
|
||||||
>
|
|
||||||
<Button className="flex w-full place-content-between place-items-center rounded-md bg-neutral-200 p-2 dark:bg-neutral-800">
|
|
||||||
<SelectValue className="font-medium" />
|
|
||||||
<ChevronsUpDown aria-hidden size={20} />
|
|
||||||
</Button>
|
|
||||||
<Popover className="max-h-60 w-[--trigger-width] overflow-auto rounded-md border border-neutral-300 bg-neutral-200 dark:border-neutral-700 dark:bg-neutral-800">
|
|
||||||
<ListBox items={versions} shouldFocusWrap>
|
|
||||||
{(item) => (
|
|
||||||
<ListBoxItem
|
|
||||||
className="flex p-2 outline-none data-[focus-visible]:bg-neutral-300 data-[hovered]:bg-neutral-300 data-[selected]:bg-blurple data-[selected]:data-[focus-visible]:bg-blurple-500 data-[selected]:data-[hovered]:bg-blurple-500 data-[selected]:text-white dark:data-[focus-visible]:bg-neutral-700 dark:data-[hovered]:bg-neutral-700 dark:data-[selected]:data-[focus-visible]:bg-blurple-500 dark:data-[selected]:data-[hovered]:bg-blurple-500"
|
|
||||||
href={`/docs/packages/${packageName}/${item.version}`}
|
|
||||||
id={item.version}
|
|
||||||
textValue={item.version}
|
|
||||||
>
|
|
||||||
{item.version}
|
|
||||||
</ListBoxItem>
|
|
||||||
)}
|
|
||||||
</ListBox>
|
|
||||||
</Popover>
|
|
||||||
</Select>
|
|
||||||
|
|
||||||
<Vaul.NestedRoot onOpenChange={setOpen} open={open}>
|
|
||||||
<Vaul.Trigger
|
|
||||||
aria-label="Open version select"
|
|
||||||
className="flex w-full place-content-between place-items-center rounded-md bg-neutral-200 p-2 dark:bg-neutral-800 md:hidden"
|
|
||||||
>
|
|
||||||
<span className="font-medium">{selectedVersion}</span>
|
|
||||||
<ChevronsUpDown aria-hidden size={20} />
|
|
||||||
</Vaul.Trigger>
|
|
||||||
<Vaul.Portal>
|
|
||||||
<Vaul.Overlay className="fixed inset-0 bg-black/40" />
|
|
||||||
<Vaul.Content className="fixed bottom-0 left-0 right-0 flex max-h-[80%] flex-col rounded-t-lg bg-neutral-100 p-4 dark:bg-neutral-900">
|
|
||||||
<div className="mx-auto mb-8 h-1.5 w-12 flex-shrink-0 rounded-full bg-neutral-400" />
|
|
||||||
<ListBox
|
|
||||||
aria-label="Select a version"
|
|
||||||
className="flex flex-col gap-2 overflow-auto"
|
|
||||||
items={versions}
|
|
||||||
onSelectionChange={(selected) => {
|
|
||||||
const [val] = selected;
|
|
||||||
setSelectedVersion(val as Key);
|
|
||||||
}}
|
|
||||||
selectedKeys={[selectedVersion]}
|
|
||||||
selectionMode="single"
|
|
||||||
shouldFocusWrap
|
|
||||||
>
|
|
||||||
{(item) => (
|
|
||||||
<ListBoxItem
|
|
||||||
className="rounded-md p-2 outline-none data-[focus-visible]:bg-neutral-300 data-[hovered]:bg-neutral-300 data-[selected]:bg-blurple data-[selected]:data-[focus-visible]:bg-blurple-500 data-[selected]:data-[hovered]:bg-blurple-500 data-[selected]:text-white dark:data-[focus-visible]:bg-neutral-700 dark:data-[hovered]:bg-neutral-700 dark:data-[selected]:data-[focus-visible]:bg-blurple-500 dark:data-[selected]:data-[hovered]:bg-blurple-500"
|
|
||||||
href={`/docs/packages/${packageName}/${item.version}`}
|
|
||||||
id={item.version}
|
|
||||||
textValue={item.version}
|
|
||||||
>
|
|
||||||
{item.version}
|
|
||||||
</ListBoxItem>
|
|
||||||
)}
|
|
||||||
</ListBox>
|
|
||||||
</Vaul.Content>
|
|
||||||
</Vaul.Portal>
|
|
||||||
</Vaul.NestedRoot>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
228
apps/website/src/styles/base.css
Normal file
228
apps/website/src/styles/base.css
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
@import 'tailwindcss';
|
||||||
|
|
||||||
|
@plugin 'tailwindcss-react-aria-components';
|
||||||
|
@import 'tw-animate-css';
|
||||||
|
@plugin '@tailwindcss/typography';
|
||||||
|
|
||||||
|
@custom-variant dark (&:where(.dark, .dark *));
|
||||||
|
|
||||||
|
@theme {
|
||||||
|
--font-sans: var(--font-geist-sans);
|
||||||
|
--font-mono: var(--font-geist-mono);
|
||||||
|
|
||||||
|
--color-base-neutral-0: #ffffff;
|
||||||
|
--color-base-neutral-40: #fcfcfc;
|
||||||
|
--color-base-neutral-60: #fafafa;
|
||||||
|
--color-base-neutral-80: #f0f0f0;
|
||||||
|
--color-base-neutral-100: #e6e6e6;
|
||||||
|
--color-base-neutral-200: #cccccc;
|
||||||
|
--color-base-neutral-300: #b3b3b3;
|
||||||
|
--color-base-neutral-400: #999999;
|
||||||
|
--color-base-neutral-500: #808080;
|
||||||
|
--color-base-neutral-600: #666666;
|
||||||
|
--color-base-neutral-700: #4c4c4c;
|
||||||
|
--color-base-neutral-800: #333333;
|
||||||
|
--color-base-neutral-900: #191919;
|
||||||
|
|
||||||
|
--color-base-blurple-50: #e0e3ff;
|
||||||
|
--color-base-blurple-100: #cdd2ff;
|
||||||
|
--color-base-blurple-200: #9ea7ff;
|
||||||
|
--color-base-blurple-300: #7782fa;
|
||||||
|
--color-base-blurple-400: #5865f2;
|
||||||
|
--color-base-blurple-500: #3d48c3;
|
||||||
|
--color-base-blurple-600: #293294;
|
||||||
|
--color-base-blurple-700: #1a2165;
|
||||||
|
--color-base-blurple-800: #0e1137;
|
||||||
|
--color-base-blurple-900: #020208;
|
||||||
|
|
||||||
|
--color-base-sunset-100: #ffe1df;
|
||||||
|
--color-base-sunset-200: #ffc3bf;
|
||||||
|
--color-base-sunset-300: #ffa69e;
|
||||||
|
--color-base-sunset-400: #ff887e;
|
||||||
|
--color-base-sunset-500: #ff6a5e;
|
||||||
|
--color-base-sunset-600: #cc554b;
|
||||||
|
--color-base-sunset-700: #994038;
|
||||||
|
--color-base-sunset-800: #662a26;
|
||||||
|
--color-base-sunset-900: #331513;
|
||||||
|
|
||||||
|
--color-base-tangerine-100: #fdefd2;
|
||||||
|
--color-base-tangerine-200: #fce0a5;
|
||||||
|
--color-base-tangerine-300: #fad078;
|
||||||
|
--color-base-tangerine-400: #f9c14b;
|
||||||
|
--color-base-tangerine-500: #f7b11e;
|
||||||
|
--color-base-tangerine-600: #c68e18;
|
||||||
|
--color-base-tangerine-700: #946a12;
|
||||||
|
--color-base-tangerine-800: #63470c;
|
||||||
|
--color-base-tangerine-900: #312306;
|
||||||
|
|
||||||
|
--color-base-green-lime-100: #ecefcc;
|
||||||
|
--color-base-green-lime-200: #d9df99;
|
||||||
|
--color-base-green-lime-300: #c6cf66;
|
||||||
|
--color-base-green-lime-400: #b3bf33;
|
||||||
|
--color-base-green-lime-500: #a0af00;
|
||||||
|
--color-base-green-lime-600: #808c00;
|
||||||
|
--color-base-green-lime-700: #606900;
|
||||||
|
--color-base-green-lime-800: #404600;
|
||||||
|
--color-base-green-lime-900: #202300;
|
||||||
|
|
||||||
|
--color-base-crystal-100: #dfedff;
|
||||||
|
--color-base-crystal-200: #bfdbff;
|
||||||
|
--color-base-crystal-300: #9ec8ff;
|
||||||
|
--color-base-crystal-400: #7eb6ff;
|
||||||
|
--color-base-crystal-500: #5ea4ff;
|
||||||
|
--color-base-crystal-600: #4b83cc;
|
||||||
|
--color-base-crystal-700: #386299;
|
||||||
|
--color-base-crystal-800: #264266;
|
||||||
|
--color-base-crystal-900: #132133;
|
||||||
|
|
||||||
|
--text-base-heading-xl: 2.813rem;
|
||||||
|
--text-base-heading-xl--line-height: 3.25rem;
|
||||||
|
--text-base-heading-xl--letter-spacing: 0;
|
||||||
|
--text-base-heading-xl--font-weight: 400;
|
||||||
|
--text-base-heading-lg: 2.25rem;
|
||||||
|
--text-base-heading-lg--line-height: 2.75rem;
|
||||||
|
--text-base-heading-lg--letter-spacing: 0;
|
||||||
|
--text-base-heading-lg--font-weight: 400;
|
||||||
|
--text-base-heading-md: 2rem;
|
||||||
|
--text-base-heading-md--line-height: 2.5rem;
|
||||||
|
--text-base-heading-md--letter-spacing: 0;
|
||||||
|
--text-base-heading-md--font-weight: 400;
|
||||||
|
--text-base-heading-sm: 1.75rem;
|
||||||
|
--text-base-heading-sm--line-height: 2.25rem;
|
||||||
|
--text-base-heading-sm--letter-spacing: 0;
|
||||||
|
--text-base-heading-sm--font-weight: 400;
|
||||||
|
--text-base-heading-xs: 1.5rem;
|
||||||
|
--text-base-heading-xs--line-height: 2rem;
|
||||||
|
--text-base-heading-xs--letter-spacing: 0;
|
||||||
|
--text-base-heading-xs--font-weight: 400;
|
||||||
|
--text-base-label-xl: 1.125rem;
|
||||||
|
--text-base-label-xl--line-height: 1.75rem;
|
||||||
|
--text-base-label-xl--letter-spacing: 0.5px;
|
||||||
|
--text-base-label-xl--font-weight: 500;
|
||||||
|
--text-base-label-lg: 1rem;
|
||||||
|
--text-base-label-lg--line-height: 1.5rem;
|
||||||
|
--text-base-label-lg--letter-spacing: 0.5px;
|
||||||
|
--text-base-label-lg--font-weight: 500;
|
||||||
|
--text-base-label-md: 1rem;
|
||||||
|
--text-base-label-md--line-height: 1.5rem;
|
||||||
|
--text-base-label-md--letter-spacing: 0.5px;
|
||||||
|
--text-base-label-md--font-weight: 500;
|
||||||
|
/* --text-base-label-md: 0.875rem;
|
||||||
|
--text-base-label-md--line-height: 1.25rem;
|
||||||
|
--text-base-label-md--letter-spacing: 0.1px;
|
||||||
|
--text-base-label-md--font-weight: 500; */
|
||||||
|
--text-base-label-sm: 0.75rem;
|
||||||
|
--text-base-label-sm--line-height: 1rem;
|
||||||
|
--text-base-label-sm--letter-spacing: 0.5px;
|
||||||
|
--text-base-label-sm--font-weight: 500;
|
||||||
|
--text-base-label-xs: 0.688rem;
|
||||||
|
--text-base-label-xs--line-height: 1rem;
|
||||||
|
--text-base-label-xs--letter-spacing: 0.5px;
|
||||||
|
--text-base-label-xs--font-weight: 500;
|
||||||
|
--text-base-xl: 1.125rem;
|
||||||
|
--text-base-xl--line-height: 1.75rem;
|
||||||
|
--text-base-xl--letter-spacing: 0.5px;
|
||||||
|
--text-base-xl--font-weight: 400;
|
||||||
|
--text-base-lg: 1rem;
|
||||||
|
--text-base-lg--line-height: 1.5rem;
|
||||||
|
--text-base-lg--letter-spacing: 0.5px;
|
||||||
|
--text-base-lg--font-weight: 400;
|
||||||
|
--text-base-md: 1rem;
|
||||||
|
--text-base-md--line-height: 1.5rem;
|
||||||
|
--text-base-md--letter-spacing: 0.5px;
|
||||||
|
--text-base-md--font-weight: 400;
|
||||||
|
/* --text-base-md: 0.875rem;
|
||||||
|
--text-base-md--line-height: 1.25rem;
|
||||||
|
--text-base-md--letter-spacing: 0.25px;
|
||||||
|
--text-base-md--font-weight: 400; */
|
||||||
|
--text-base-sm: 0.75rem;
|
||||||
|
--text-base-sm--line-height: 1rem;
|
||||||
|
--text-base-sm--letter-spacing: 0.4px;
|
||||||
|
--text-base-sm--font-weight: 400;
|
||||||
|
--text-base-xs: 0.688rem;
|
||||||
|
--text-base-xs--line-height: 1rem;
|
||||||
|
--text-base-xs--letter-spacing: 0.5px;
|
||||||
|
--text-base-xs--font-weight: 400;
|
||||||
|
|
||||||
|
--shadow-base-sm: 0 1px 4px 0 #19191929;
|
||||||
|
--shadow-base-md: 0 3px 3px 0 #19191929;
|
||||||
|
--shadow-base-lg: 0 3px 6px 0 #1919193d;
|
||||||
|
--shadow-base-xl: 0 6px 6px 0 #1919193d;
|
||||||
|
--shadow-base-2xl: 0 8px 8px 0 #19191952;
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
font-family: var(--font-roboto);
|
||||||
|
text-rendering: optimizeLegibility;
|
||||||
|
|
||||||
|
scrollbar-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .os-scrollbar-handle {
|
||||||
|
--os-handle-bg: rgba(255, 255, 255, 0.5);
|
||||||
|
--os-handle-bg-hover: rgba(255, 255, 255, 0.7);
|
||||||
|
--os-handle-bg-active: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.os-scrollbar-handle {
|
||||||
|
--os-handle-bg: rgba(0, 0, 0, 0.5);
|
||||||
|
--os-handle-bg-hover: rgba(0, 0, 0, 0.7);
|
||||||
|
--os-handle-bg-active: rgba(0, 0, 0, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
html.dark .shiki,
|
||||||
|
html.dark .shiki span {
|
||||||
|
color: var(--shiki-dark) !important;
|
||||||
|
font-style: var(--shiki-dark-font-style) !important;
|
||||||
|
font-weight: var(--shiki-dark-font-weight) !important;
|
||||||
|
text-decoration: var(--shiki-dark-text-decoration) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
@apply bg-[#f3f3f4]! dark:bg-[#121214]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: var(--font-geist-mono);
|
||||||
|
}
|
||||||
|
|
||||||
|
code > .line {
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@utility scrollbar-hidden {
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[cmdk-overlay] {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
height: 100dvh;
|
||||||
|
width: 100vw;
|
||||||
|
@apply bg-base-neutral-900/72 z-30;
|
||||||
|
}
|
||||||
|
|
||||||
|
[cmdk-dialog] {
|
||||||
|
position: fixed;
|
||||||
|
left: 50%;
|
||||||
|
top: 0;
|
||||||
|
z-index: 50;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 536px;
|
||||||
|
height: 100dvh;
|
||||||
|
@apply h-auto outline-0 md:top-16 md:p-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
[cmdk-list-sizer] {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
[cmdk-group-heading] {
|
||||||
|
@apply text-base-label-sm text-base-neutral-600 dark:text-base-neutral-300 h-8 px-3 py-2;
|
||||||
|
}
|
||||||
34
apps/website/src/styles/cva.ts
Normal file
34
apps/website/src/styles/cva.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { defineConfig } from 'cva';
|
||||||
|
import { extendTailwindMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
|
const twMergeConfig = {
|
||||||
|
classGroups: {
|
||||||
|
'font-size': [
|
||||||
|
'text-base-xs',
|
||||||
|
'text-base-sm',
|
||||||
|
'text-base-md',
|
||||||
|
'text-base-lg',
|
||||||
|
'text-base-xl',
|
||||||
|
'text-base-label-xs',
|
||||||
|
'text-base-label-sm',
|
||||||
|
'text-base-label-md',
|
||||||
|
'text-base-label-lg',
|
||||||
|
'text-base-label-xl',
|
||||||
|
'text-base-heading-xs',
|
||||||
|
'text-base-heading-sm',
|
||||||
|
'text-base-heading-md',
|
||||||
|
'text-base-heading-lg',
|
||||||
|
'text-base-heading-xl',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const twMerge = extendTailwindMerge({
|
||||||
|
extend: twMergeConfig,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const { cva, cx, compose } = defineConfig({
|
||||||
|
hooks: {
|
||||||
|
onComplete: (className) => twMerge(className),
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
|
|
||||||
* {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: var(--font-geist-sans);
|
|
||||||
min-height: 100dvh;
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark .os-scrollbar-handle {
|
|
||||||
--os-handle-bg: rgba(255, 255, 255, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark .os-scrollbar-handle:hover {
|
|
||||||
--os-handle-bg-hover: rgba(255, 255, 255, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
.os-scrollbar-handle {
|
|
||||||
--os-handle-bg: rgba(0, 0, 0, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
.os-scrollbar-handle:hover {
|
|
||||||
--os-handle-bg-hover: rgba(0, 0, 0, 0.7);
|
|
||||||
}
|
|
||||||
|
|
||||||
html.dark .shiki,
|
|
||||||
html.dark .shiki span {
|
|
||||||
color: var(--shiki-dark) !important;
|
|
||||||
/* background-color: var(--shiki-dark-bg) !important; */
|
|
||||||
background-color: transparent !important;
|
|
||||||
font-style: var(--shiki-dark-font-style) !important;
|
|
||||||
font-weight: var(--shiki-dark-font-weight) !important;
|
|
||||||
text-decoration: var(--shiki-dark-text-decoration) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background-color: transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
|
||||||
font-family: var(--font-geist-mono);
|
|
||||||
}
|
|
||||||
|
|
||||||
code > .line {
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[cmdk-overlay] {
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
height: 100dvh;
|
|
||||||
width: 100vw;
|
|
||||||
background-color: rgb(0 0 0 / 80%);
|
|
||||||
}
|
|
||||||
|
|
||||||
[cmdk-dialog] {
|
|
||||||
position: fixed;
|
|
||||||
left: 50%;
|
|
||||||
top: 20%;
|
|
||||||
z-index: 50;
|
|
||||||
transform: translate(-50%, 0);
|
|
||||||
width: 100%;
|
|
||||||
max-width: 640px;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
[cmdk-list-sizer] {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
193
apps/website/src/styles/ui/button.ts
Normal file
193
apps/website/src/styles/ui/button.ts
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import { compose, cva } from '@/styles/cva';
|
||||||
|
import { focusRing } from '@/styles/ui/focusRing';
|
||||||
|
|
||||||
|
export const buttonStyles = compose(
|
||||||
|
focusRing,
|
||||||
|
cva({
|
||||||
|
base: [
|
||||||
|
'text-base-label-md relative inline-flex place-content-center place-items-center gap-2 border border-transparent',
|
||||||
|
'*:data-[slot=icon]:size-4.5 *:data-[slot=icon]:shrink-0 print:hidden',
|
||||||
|
],
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
unset: null,
|
||||||
|
outline: [
|
||||||
|
'h-10 rounded-sm px-4 py-2.5',
|
||||||
|
'border-base-neutral-300 text-base-neutral-800 bg-base-neutral-0 dark:bg-base-neutral-800 dark:border-base-neutral-500 dark:text-base-neutral-100',
|
||||||
|
'hover:bg-base-neutral-700 hover:text-base-neutral-40 dark:hover:bg-base-neutral-100 dark:hover:text-base-neutral-900 hover:border-transparent',
|
||||||
|
'focus-visible:bg-base-neutral-700 focus-visible:text-base-neutral-40 dark:focus-visible:bg-base-neutral-100 dark:focus-visible:text-base-neutral-900 focus-visible:border-transparent',
|
||||||
|
'pressed:bg-base-neutral-800 pressed:text-base-neutral-40 pressed:border-transparent dark:pressed:bg-base-neutral-60 dark:pressed:text-base-neutral-900',
|
||||||
|
'disabled:bg-base-neutral-40 disabled:text-base-neutral-900 disabled:border-base-neutral-300 dark:disabled:bg-base-neutral-800 dark:disabled:text-base-neutral-40',
|
||||||
|
],
|
||||||
|
discreet: [
|
||||||
|
'h-10 rounded-sm px-4 py-2.5',
|
||||||
|
'text-base-neutral-800 dark:text-base-neutral-100 bg-transparent',
|
||||||
|
'hover:bg-base-neutral-100 dark:hover:bg-base-neutral-700',
|
||||||
|
'focus-visible:bg-base-neutral-100 dark:focus-visible:bg-base-neutral-700',
|
||||||
|
'pressed:bg-base-neutral-200 dark:pressed:bg-base-neutral-600',
|
||||||
|
'disabled:text-base-neutral-900 dark:disabled:text-base-neutral-40',
|
||||||
|
],
|
||||||
|
filled: [
|
||||||
|
'h-10 rounded-sm px-4 py-2.5',
|
||||||
|
'bg-base-neutral-700 text-base-neutral-40 dark:bg-base-neutral-100 dark:text-base-neutral-900',
|
||||||
|
'hover:bg-base-neutral-500 dark:hover:bg-base-neutral-300',
|
||||||
|
'focus-visible:bg-base-neutral-500 dark:focus-visible:bg-base-neutral-300',
|
||||||
|
'pressed:bg-base-neutral-400 pressed:text-base-neutral-800 dark:pressed:bg-base-neutral-400',
|
||||||
|
'disabled:bg-base-neutral-200 disabled:text-base-neutral-900 dark:disabled:bg-base-neutral-400 dark:disabled:text-base-neutral-40',
|
||||||
|
],
|
||||||
|
tonal: [
|
||||||
|
'h-10 rounded-sm px-4 py-2.5',
|
||||||
|
'bg-base-neutral-500 text-base-neutral-40 dark:bg-base-neutral-400 dark:text-base-neutral-900',
|
||||||
|
'hover:bg-base-neutral-700 dark:hover:bg-base-neutral-200',
|
||||||
|
'focus-visible:bg-base-neutral-700 dark:focus-visible:bg-base-neutral-200',
|
||||||
|
'pressed:bg-base-neutral-800 dark:pressed:bg-base-neutral-100',
|
||||||
|
'disabled:bg-base-neutral-200 disabled:text-base-neutral-900 dark:disabled:bg-base-neutral-700 dark:disabled:text-base-neutral-40',
|
||||||
|
],
|
||||||
|
'secondary-outline': [
|
||||||
|
'h-10 rounded-sm px-4 py-2.5',
|
||||||
|
'border-base-blurple-200 text-base-neutral-800 bg-base-neutral-0 dark:bg-base-neutral-800 dark:border-base-blurple-500 dark:text-base-neutral-40',
|
||||||
|
'hover:bg-base-blurple-400 hover:text-base-neutral-900 hover:border-transparent',
|
||||||
|
'focus-visible:bg-base-blurple-400 focus-visible:text-base-neutral-900 focus-visible:border-transparent',
|
||||||
|
'pressed:bg-base-blurple-500 pressed:text-base-neutral-40 pressed:border-transparent dark:pressed:bg-base-blurple-300 dark:pressed:text-base-neutral-900',
|
||||||
|
'disabled:bg-base-neutral-0 disabled:text-base-neutral-900 disabled:border-base-neutral-200 dark:text-base-neutral-40 dark:disabled:bg-base-neutral-800 dark:disabled:border-base-neutral-700',
|
||||||
|
],
|
||||||
|
'secondary-discreet': [
|
||||||
|
'h-10 rounded-sm px-4 py-2.5',
|
||||||
|
'text-base-blurple-500 dark:text-base-blurple-300 bg-transparent',
|
||||||
|
'hover:bg-base-blurple-50 hover:text-base-blurple-600 dark:hover:bg-base-blurple-700 dark:hover:text-base-blurple-200',
|
||||||
|
'focus-visible:bg-base-blurple-50 focus-visible:text-base-blurple-600 dark:focus-visible:bg-base-blurple-700 dark:focus-visible:text-base-blurple-200',
|
||||||
|
'pressed:bg-base-blurple-100 pressed:text-base-blurple-700 dark:pressed:bg-base-blurple-600 dark:pressed:text-base-blurple-50',
|
||||||
|
'disabled:text-base-neutral-900 dark:disabled:text-base-neutral-40',
|
||||||
|
],
|
||||||
|
'secondary-filled': [
|
||||||
|
'h-10 rounded-sm px-4 py-2.5',
|
||||||
|
'bg-base-blurple-400 text-base-neutral-900',
|
||||||
|
'hover:bg-base-blurple-200 dark:hover:bg-base-blurple-600 dark:hover:text-base-neutral-200',
|
||||||
|
'focus-visible:bg-base-blurple-200 dark:focus-visible:bg-base-blurple-600 dark:focus-visible:text-base-neutral-200',
|
||||||
|
'pressed:bg-base-blurple-100 dark:pressed:bg-base-blurple-700 dark:pressed:text-base-neutral-100',
|
||||||
|
'disabled:bg-base-neutral-200 disabled:text-base-neutral-900 dark:disabled:bg-base-neutral-700 dark:disabled:text-base-neutral-40',
|
||||||
|
],
|
||||||
|
'secondary-tonal': [
|
||||||
|
'h-10 rounded-sm px-4 py-2.5',
|
||||||
|
'bg-base-blurple-200 text-base-neutral-900 dark:bg-base-blurple-600 dark:text-base-neutral-40',
|
||||||
|
'hover:bg-base-blurple-400 dark:hover:text-base-neutral-900',
|
||||||
|
'focus-visible:bg-base-blurple-400 dark:focus-visible:text-base-neutral-900',
|
||||||
|
'pressed:bg-base-blurple-500 pressed:text-base-neutral-40 dark:pressed:bg-base-blurple-300 dark:pressed:text-base-neutral-900',
|
||||||
|
'disabled:bg-base-neutral-200 disabled:text-base-neutral-900 dark:disabled:bg-base-neutral-700 dark:disabled:text-base-neutral-40',
|
||||||
|
],
|
||||||
|
'crystal-tonal': [
|
||||||
|
'h-10 rounded-sm px-4 py-2.5',
|
||||||
|
'bg-base-crystal-300 text-base-neutral-900 dark:bg-base-crystal-700 dark:text-base-neutral-40',
|
||||||
|
'hover:bg-base-crystal-500 dark:hover:text-base-neutral-900',
|
||||||
|
'focus-visible:bg-base-crystal-500 dark:focus-visible:text-base-neutral-900',
|
||||||
|
'pressed:bg-base-crystal-600 pressed:text-base-neutral-40 dark:pressed:bg-base-crystal-400 dark:pressed:text-base-neutral-900',
|
||||||
|
'disabled:bg-base-neutral-200 disabled:text-base-neutral-900 dark:disabled:bg-base-neutral-700 dark:disabled:text-base-neutral-40',
|
||||||
|
],
|
||||||
|
tooltip: [
|
||||||
|
'size-6 shrink-0 rounded-full p-0.5',
|
||||||
|
'text-base-neutral-800 dark:text-base-neutral-100',
|
||||||
|
'hover:text-base-crystal-500 dark:hover:text-base-crystal-300',
|
||||||
|
'focus-visible:text-base-crystal-500 dark:focus-visible:text-base-crystal-300',
|
||||||
|
'disabled:text-base-neutral-900 dark:disabled:text-base-neutral-40',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: null,
|
||||||
|
icon: null,
|
||||||
|
sm: null,
|
||||||
|
xs: null,
|
||||||
|
'icon-sm': null,
|
||||||
|
'icon-xs': null,
|
||||||
|
},
|
||||||
|
isDestructive: {
|
||||||
|
true: null,
|
||||||
|
},
|
||||||
|
isDark: {
|
||||||
|
true: null,
|
||||||
|
},
|
||||||
|
isDisabled: {
|
||||||
|
true: 'cursor-default opacity-38 forced-colors:text-[GrayText] forced-colors:group-disabled:text-[GrayText] forced-colors:disabled:text-[GrayText]',
|
||||||
|
false: 'cursor-pointer',
|
||||||
|
},
|
||||||
|
isPending: {
|
||||||
|
true: 'cursor-default',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
compoundVariants: [
|
||||||
|
{
|
||||||
|
variant: 'discreet',
|
||||||
|
isDestructive: true,
|
||||||
|
className: [
|
||||||
|
'text-base-sunset-600 dark:text-base-sunset-400',
|
||||||
|
'hover:bg-base-sunset-100 hover:text-base-sunset-700 dark:hover:bg-base-sunset-800 dark:hover:text-base-sunset-300',
|
||||||
|
'focus-visible:bg-base-sunset-100 focus-visible:text-base-sunset-700 dark:focus-visible:bg-base-sunset-700 dark:focus-visible:text-base-sunset-300',
|
||||||
|
'pressed:bg-base-sunset-200 pressed:text-base-sunset-800 dark:pressed:bg-base-sunset-700 dark:pressed:text-base-sunset-100',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: [
|
||||||
|
'filled',
|
||||||
|
'outline',
|
||||||
|
'discreet',
|
||||||
|
'tonal',
|
||||||
|
'secondary-filled',
|
||||||
|
'secondary-outline',
|
||||||
|
'secondary-discreet',
|
||||||
|
'secondary-tonal',
|
||||||
|
'crystal-tonal',
|
||||||
|
'unset',
|
||||||
|
],
|
||||||
|
size: 'icon',
|
||||||
|
className: 'size-10 shrink-0 rounded-full p-2.5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: ['filled', 'outline'],
|
||||||
|
size: 'sm',
|
||||||
|
className: 'h-8 px-3 py-1.5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'filled',
|
||||||
|
size: 'sm',
|
||||||
|
isDark: true,
|
||||||
|
className: [
|
||||||
|
'h-8 px-3 py-1.5',
|
||||||
|
'bg-base-neutral-900 dark:bg-base-neutral-40',
|
||||||
|
'hover:bg-base-neutral-700 dark:hover:bg-base-neutral-200',
|
||||||
|
'focus-visible:bg-base-neutral-700 dark:focus-visible:bg-base-neutral-200',
|
||||||
|
'pressed:bg-base-neutral-600 pressed:text-base-neutral-40 dark:pressed:bg-base-neutral-300 dark:pressed:text-base-neutral-900',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'filled',
|
||||||
|
size: 'xs',
|
||||||
|
isDark: true,
|
||||||
|
className: [
|
||||||
|
'h-6 gap-1 px-2 py-1',
|
||||||
|
'bg-base-neutral-900 dark:bg-base-neutral-40',
|
||||||
|
'hover:bg-base-neutral-700 dark:hover:bg-base-neutral-200',
|
||||||
|
'focus-visible:bg-base-neutral-700 dark:focus-visible:bg-base-neutral-200',
|
||||||
|
'pressed:bg-base-neutral-600 pressed:text-base-neutral-40 dark:pressed:bg-base-neutral-300 dark:pressed:text-base-neutral-900',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: ['filled', 'discreet', 'secondary-tonal', 'crystal-tonal', 'unset'],
|
||||||
|
size: 'icon-sm',
|
||||||
|
className: 'size-8 shrink-0 rounded-full p-1.5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: 'outline',
|
||||||
|
size: 'icon-sm',
|
||||||
|
className: 'size-8 shrink-0 rounded-sm p-1.5',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
variant: ['discreet', 'unset'],
|
||||||
|
size: 'icon-xs',
|
||||||
|
className: 'size-6 shrink-0 rounded-full p-0.5',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
defaultVariants: {
|
||||||
|
variant: 'outline',
|
||||||
|
size: 'default',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
11
apps/website/src/styles/ui/focusRing.ts
Normal file
11
apps/website/src/styles/ui/focusRing.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { cva } from 'cva';
|
||||||
|
|
||||||
|
export const focusRing = cva({
|
||||||
|
base: 'outline-base-blurple-400 dark:outline-base-blurple-400 outline-offset-2 forced-colors:outline-[Highlight]',
|
||||||
|
variants: {
|
||||||
|
isFocusVisible: {
|
||||||
|
true: 'outline-2',
|
||||||
|
false: 'outline-0',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
9
apps/website/src/styles/util.ts
Normal file
9
apps/website/src/styles/util.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { composeRenderProps } from 'react-aria-components';
|
||||||
|
import { cx } from '@/styles/cva';
|
||||||
|
|
||||||
|
export function composeTailwindRenderProps<Type>(
|
||||||
|
className: string | ((v: Type) => string) | undefined,
|
||||||
|
tw: string,
|
||||||
|
): string | ((v: Type) => string) {
|
||||||
|
return composeRenderProps(className, (className) => cx(tw, className));
|
||||||
|
}
|
||||||
@@ -27,10 +27,10 @@ export async function fetchDependencies({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const isMainVersion = version === 'main';
|
const isMain = version === 'main';
|
||||||
const fileContent = await fetch(
|
const fileContent = await fetch(
|
||||||
`${process.env.BLOB_STORAGE_URL}/rewrite/${packageName}/${version}.dependencies.api.json`,
|
`${process.env.BLOB_STORAGE_URL}/rewrite/${packageName}/${version}.dependencies.api.json`,
|
||||||
{ next: isMainVersion ? { revalidate: 0 } : { revalidate: 604_800 } },
|
{ next: { revalidate: isMain ? 0 : 604_800 } },
|
||||||
);
|
);
|
||||||
const parsedDependencies = await fileContent.json();
|
const parsedDependencies = await fileContent.json();
|
||||||
|
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ export async function fetchNode({
|
|||||||
return JSON.parse(fileContent);
|
return JSON.parse(fileContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMainVersion = version === 'main';
|
const isMain = version === 'main';
|
||||||
const fileContent = await fetch(
|
const fileContent = await fetch(
|
||||||
`${process.env.BLOB_STORAGE_URL}/rewrite/${packageName}/${version}.${normalizeItem}.api.json`,
|
`${process.env.BLOB_STORAGE_URL}/rewrite/${packageName}/${version}.${normalizeItem}.api.json`,
|
||||||
{ next: isMainVersion ? { revalidate: 0 } : { revalidate: 604_800 } },
|
{ next: { revalidate: isMain ? 0 : 604_800 } },
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!fileContent.ok) {
|
if (!fileContent.ok) {
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ export async function fetchSitemap({
|
|||||||
return JSON.parse(fileContent);
|
return JSON.parse(fileContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isMainVersion = version === 'main';
|
const isMain = version === 'main';
|
||||||
const fileContent = await fetch(
|
const fileContent = await fetch(
|
||||||
`${process.env.BLOB_STORAGE_URL}/rewrite/${packageName}/${version}.sitemap.api.json`,
|
`${process.env.BLOB_STORAGE_URL}/rewrite/${packageName}/${version}.sitemap.api.json`,
|
||||||
{ next: isMainVersion ? { revalidate: 0 } : { revalidate: 604_800 } },
|
{
|
||||||
|
next: { revalidate: isMain ? 0 : 604_800 },
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!fileContent.ok) {
|
if (!fileContent.ok) {
|
||||||
|
|||||||
59
apps/website/src/util/shiki.bundle.ts
Normal file
59
apps/website/src/util/shiki.bundle.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/* Generate by @shikijs/codegen */
|
||||||
|
import type {
|
||||||
|
DynamicImportLanguageRegistration,
|
||||||
|
DynamicImportThemeRegistration,
|
||||||
|
HighlighterGeneric,
|
||||||
|
} from 'shiki/types';
|
||||||
|
import { createSingletonShorthands, createdBundledHighlighter } from 'shiki/core';
|
||||||
|
import { createJavaScriptRegexEngine } from 'shiki/engine-javascript.mjs';
|
||||||
|
|
||||||
|
type BundledLanguage = 'typescript' | 'ts' | 'javascript' | 'js' | 'shellscript' | 'bash' | 'sh' | 'shell' | 'zsh';
|
||||||
|
type BundledTheme = 'github-light' | 'github-dark-dimmed';
|
||||||
|
type Highlighter = HighlighterGeneric<BundledLanguage, BundledTheme>;
|
||||||
|
|
||||||
|
const bundledLanguages = {
|
||||||
|
typescript: () => import('shiki/langs/typescript.mjs'),
|
||||||
|
ts: () => import('shiki/langs/typescript.mjs'),
|
||||||
|
javascript: () => import('shiki/langs/javascript.mjs'),
|
||||||
|
js: () => import('shiki/langs/javascript.mjs'),
|
||||||
|
shellscript: () => import('shiki/langs/shellscript.mjs'),
|
||||||
|
bash: () => import('shiki/langs/shellscript.mjs'),
|
||||||
|
sh: () => import('shiki/langs/shellscript.mjs'),
|
||||||
|
shell: () => import('shiki/langs/shellscript.mjs'),
|
||||||
|
zsh: () => import('shiki/langs/shellscript.mjs'),
|
||||||
|
} as Record<BundledLanguage, DynamicImportLanguageRegistration>;
|
||||||
|
|
||||||
|
const bundledThemes = {
|
||||||
|
'github-light': () => import('shiki/themes/github-light.mjs'),
|
||||||
|
'github-dark-dimmed': () => import('shiki/themes/github-dark-dimmed.mjs'),
|
||||||
|
} as Record<BundledTheme, DynamicImportThemeRegistration>;
|
||||||
|
|
||||||
|
const createHighlighter = /* @__PURE__ */ createdBundledHighlighter<BundledLanguage, BundledTheme>({
|
||||||
|
langs: bundledLanguages,
|
||||||
|
themes: bundledThemes,
|
||||||
|
engine: () => createJavaScriptRegexEngine(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const {
|
||||||
|
codeToHtml,
|
||||||
|
codeToHast,
|
||||||
|
codeToTokensBase,
|
||||||
|
codeToTokens,
|
||||||
|
codeToTokensWithThemes,
|
||||||
|
getSingletonHighlighter,
|
||||||
|
getLastGrammarState,
|
||||||
|
} = /* @__PURE__ */ createSingletonShorthands<BundledLanguage, BundledTheme>(createHighlighter);
|
||||||
|
|
||||||
|
export {
|
||||||
|
bundledLanguages,
|
||||||
|
bundledThemes,
|
||||||
|
codeToHast,
|
||||||
|
codeToHtml,
|
||||||
|
codeToTokens,
|
||||||
|
codeToTokensBase,
|
||||||
|
codeToTokensWithThemes,
|
||||||
|
createHighlighter,
|
||||||
|
getLastGrammarState,
|
||||||
|
getSingletonHighlighter,
|
||||||
|
};
|
||||||
|
export type { BundledLanguage, BundledTheme, Highlighter };
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import typographyPlugin from '@tailwindcss/typography';
|
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
export default {
|
|
||||||
content: ['./src/**/*.{js,ts,jsx,tsx}'],
|
|
||||||
darkMode: 'class',
|
|
||||||
theme: {
|
|
||||||
extend: {
|
|
||||||
colors: {
|
|
||||||
blurple: {
|
|
||||||
50: '#e0e3ff',
|
|
||||||
100: '#cdd2ff',
|
|
||||||
200: '#9ea7ff',
|
|
||||||
300: '#7782fa',
|
|
||||||
DEFAULT: '#5865F2',
|
|
||||||
500: '#3d48c3',
|
|
||||||
600: '#293294',
|
|
||||||
700: '#1a2165',
|
|
||||||
800: '#0e1137',
|
|
||||||
900: '#020208',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
fontFamily: {
|
|
||||||
sans: 'var(--font-geist-sans)',
|
|
||||||
mono: 'var(--font-geist-mono)',
|
|
||||||
},
|
|
||||||
typography: {
|
|
||||||
DEFAULT: {
|
|
||||||
css: {
|
|
||||||
pre: {
|
|
||||||
padding: '12px 0px',
|
|
||||||
'line-height': '1.5',
|
|
||||||
'border-radius': '6px',
|
|
||||||
'border-width': '1px',
|
|
||||||
'border-color': 'rgb(212, 212, 212)',
|
|
||||||
},
|
|
||||||
'.dark pre': {
|
|
||||||
'border-color': 'rgb(64, 64, 64)',
|
|
||||||
},
|
|
||||||
code: {
|
|
||||||
'font-size': '1em',
|
|
||||||
'font-weight': 'unset',
|
|
||||||
},
|
|
||||||
'code span:last-of-type:empty': {
|
|
||||||
display: 'none',
|
|
||||||
},
|
|
||||||
a: {
|
|
||||||
color: '#5865F2',
|
|
||||||
'text-decoration': 'none',
|
|
||||||
},
|
|
||||||
'a:hover': {
|
|
||||||
color: '#3d48c3',
|
|
||||||
},
|
|
||||||
'.dark a:hover': {
|
|
||||||
color: '#7782fa',
|
|
||||||
},
|
|
||||||
'a > img': {
|
|
||||||
display: 'inline-block',
|
|
||||||
margin: '0',
|
|
||||||
},
|
|
||||||
'a > img[height="44"]': {
|
|
||||||
height: '44px',
|
|
||||||
},
|
|
||||||
'div[align="center"] > p > a + a': {
|
|
||||||
'margin-left': '0.5em',
|
|
||||||
},
|
|
||||||
h1: {
|
|
||||||
display: 'flex',
|
|
||||||
'place-items': 'center',
|
|
||||||
'scroll-margin-top': '6.5rem',
|
|
||||||
},
|
|
||||||
h2: {
|
|
||||||
display: 'flex',
|
|
||||||
'place-items': 'center',
|
|
||||||
'margin-top': '1.25em',
|
|
||||||
'scroll-margin-top': '6.5rem',
|
|
||||||
},
|
|
||||||
h3: {
|
|
||||||
display: 'flex',
|
|
||||||
'place-items': 'center',
|
|
||||||
'margin-top': '1.25em',
|
|
||||||
'scroll-margin-top': '6.5rem',
|
|
||||||
},
|
|
||||||
h4: {
|
|
||||||
display: 'flex',
|
|
||||||
'place-items': 'center',
|
|
||||||
'margin-top': '1.25em',
|
|
||||||
'scroll-margin-top': '6.5rem',
|
|
||||||
},
|
|
||||||
// eslint-disable-next-line id-length
|
|
||||||
p: {
|
|
||||||
margin: '.5em 0',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
plugins: [typographyPlugin],
|
|
||||||
};
|
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
],
|
],
|
||||||
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ export default tseslint.config(
|
|||||||
'**/coverage/',
|
'**/coverage/',
|
||||||
'**/storybook-static/',
|
'**/storybook-static/',
|
||||||
'**/.next/',
|
'**/.next/',
|
||||||
|
'**/shiki.bundle.ts',
|
||||||
'packages/discord.js/',
|
'packages/discord.js/',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ export enum Meaning {
|
|||||||
*
|
*
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface IApiItemOptions {}
|
export interface IApiItemOptions {}
|
||||||
|
|
||||||
export interface IApiItemJson {
|
export interface IApiItemJson {
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ export class ApiModel extends ApiItemContainerMixin(ApiItem) {
|
|||||||
/**
|
/**
|
||||||
* @override
|
* @override
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public override get containerKey(): string {
|
public override get containerKey(): string {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
diff --git a/lib/TSDocConfigFile.js b/lib/TSDocConfigFile.js
|
diff --git a/lib/TSDocConfigFile.js b/lib/TSDocConfigFile.js
|
||||||
index caf3515d60fd386c5909db5a0aa8b4180b10d602..f8a7ff05d972e8dca4c5a485feb6ef37388274ac 100644
|
index caf3515d60fd386c5909db5a0aa8b4180b10d602..5f7cfed7611e3fe660b5265ff99c5da0beb7caec 100644
|
||||||
--- a/lib/TSDocConfigFile.js
|
--- a/lib/TSDocConfigFile.js
|
||||||
+++ b/lib/TSDocConfigFile.js
|
+++ b/lib/TSDocConfigFile.js
|
||||||
@@ -31,8 +31,7 @@ const ajv_1 = __importDefault(require("ajv"));
|
@@ -31,8 +31,7 @@ const ajv_1 = __importDefault(require("ajv"));
|
||||||
4907
pnpm-lock.yaml
generated
4907
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ onlyBuiltDependencies:
|
|||||||
- '@discordjs/opus'
|
- '@discordjs/opus'
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- esbuild
|
- esbuild
|
||||||
|
- msw
|
||||||
- protobufjs
|
- protobufjs
|
||||||
- sharp
|
- sharp
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
@@ -16,9 +17,6 @@ packages:
|
|||||||
- apps/*
|
- apps/*
|
||||||
- packages/*
|
- packages/*
|
||||||
|
|
||||||
patchedDependencies:
|
|
||||||
'@microsoft/tsdoc-config': patches/@microsoft__tsdoc-config.patch
|
|
||||||
|
|
||||||
peerDependencyRules:
|
peerDependencyRules:
|
||||||
ignoreMissing:
|
ignoreMissing:
|
||||||
- '*'
|
- '*'
|
||||||
@@ -30,3 +28,6 @@ publicHoistPattern:
|
|||||||
- '*prettier*'
|
- '*prettier*'
|
||||||
- '*@rushstack/node-core-library*'
|
- '*@rushstack/node-core-library*'
|
||||||
- '*jju*'
|
- '*jju*'
|
||||||
|
|
||||||
|
patchedDependencies:
|
||||||
|
'@microsoft/tsdoc-config@0.16.2': patches/@microsoft__tsdoc-config@0.16.2.patch
|
||||||
|
|||||||
34
turbo.json
34
turbo.json
@@ -44,7 +44,22 @@
|
|||||||
"outputLogs": "full"
|
"outputLogs": "full"
|
||||||
},
|
},
|
||||||
"@discordjs/website#build:local": {
|
"@discordjs/website#build:local": {
|
||||||
"env": ["VERCEL_ENV", "NEXT_PUBLIC_LOCAL_DEV"],
|
"env": [
|
||||||
|
"VERCEL_ENV",
|
||||||
|
"NEXT_PUBLIC_LOCAL_DEV",
|
||||||
|
"EDGE_CONFIG",
|
||||||
|
"DATABASE_URL",
|
||||||
|
"MAX_FETCH_SIZE",
|
||||||
|
"POSTGRES_URL",
|
||||||
|
"POSTGRES_URL_NON_POOLING",
|
||||||
|
"POSTGRES_PRISMA_URL",
|
||||||
|
"POSTGRES_USER",
|
||||||
|
"POSTGRES_PASSWORD",
|
||||||
|
"POSTGRES_HOST",
|
||||||
|
"POSTGRES_DATABASE",
|
||||||
|
"BLOB_READ_WRITE_TOKEN",
|
||||||
|
"BLOB_STORAGE_URL"
|
||||||
|
],
|
||||||
"dependsOn": ["^build", "^docs"],
|
"dependsOn": ["^build", "^docs"],
|
||||||
"inputs": [
|
"inputs": [
|
||||||
"../../packages/*/README.md",
|
"../../packages/*/README.md",
|
||||||
@@ -61,7 +76,22 @@
|
|||||||
"outputLogs": "full"
|
"outputLogs": "full"
|
||||||
},
|
},
|
||||||
"@discordjs/website#build:prod": {
|
"@discordjs/website#build:prod": {
|
||||||
"env": ["VERCEL_ENV", "NEXT_PUBLIC_LOCAL_DEV"],
|
"env": [
|
||||||
|
"VERCEL_ENV",
|
||||||
|
"NEXT_PUBLIC_LOCAL_DEV",
|
||||||
|
"EDGE_CONFIG",
|
||||||
|
"DATABASE_URL",
|
||||||
|
"MAX_FETCH_SIZE",
|
||||||
|
"POSTGRES_URL",
|
||||||
|
"POSTGRES_URL_NON_POOLING",
|
||||||
|
"POSTGRES_PRISMA_URL",
|
||||||
|
"POSTGRES_USER",
|
||||||
|
"POSTGRES_PASSWORD",
|
||||||
|
"POSTGRES_HOST",
|
||||||
|
"POSTGRES_DATABASE",
|
||||||
|
"BLOB_READ_WRITE_TOKEN",
|
||||||
|
"BLOB_STORAGE_URL"
|
||||||
|
],
|
||||||
"dependsOn": ["^build"],
|
"dependsOn": ["^build"],
|
||||||
"inputs": [
|
"inputs": [
|
||||||
"../../packages/*/README.md",
|
"../../packages/*/README.md",
|
||||||
|
|||||||
Reference in New Issue
Block a user