mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat(website): include loading indicators when data is fetching
This commit is contained in:
@@ -12,7 +12,7 @@ export default async function Layout({ children }: PropsWithChildren) {
|
|||||||
<>
|
<>
|
||||||
<Sidebar closeButton={false} intent="inset">
|
<Sidebar closeButton={false} intent="inset">
|
||||||
<SidebarHeader />
|
<SidebarHeader />
|
||||||
<SidebarContent className="bg-[#f3f3f4] p-0 py-4 pl-4 dark:bg-[#121214]">
|
<SidebarContent className="bg-[#f3f3f4] p-0 pb-4 pl-4 dark:bg-[#121214]">
|
||||||
<Scrollbars>
|
<Scrollbars>
|
||||||
<Navigation />
|
<Navigation />
|
||||||
</Scrollbars>
|
</Scrollbars>
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { Loader2Icon } from 'lucide-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
|
import { Select, SelectList, SelectOption, SelectTrigger } from '@/components/ui/Select';
|
||||||
import { parseDocsPathParams } from '@/util/parseDocsPathParams';
|
import { parseDocsPathParams } from '@/util/parseDocsPathParams';
|
||||||
import { Select, SelectList, SelectOption, SelectTrigger } from './ui/Select';
|
|
||||||
|
|
||||||
export function EntryPointSelect({ entryPoints }: { readonly entryPoints: { readonly entryPoint: string }[] }) {
|
export function EntryPointSelect({
|
||||||
|
entryPoints,
|
||||||
|
isLoading,
|
||||||
|
}: {
|
||||||
|
readonly entryPoints: { readonly entryPoint: string }[];
|
||||||
|
readonly isLoading: boolean;
|
||||||
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams<{
|
const params = useParams<{
|
||||||
item?: string[] | undefined;
|
item?: string[] | undefined;
|
||||||
@@ -16,11 +23,24 @@ export function EntryPointSelect({ entryPoints }: { readonly entryPoints: { read
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
aria-label="Select an entrypoint"
|
aria-label={isLoading ? 'Loading entrypoints...' : 'Select an entrypoint'}
|
||||||
defaultSelectedKey={parsedEntrypoints.join('/')}
|
defaultSelectedKey={parsedEntrypoints.join('/')}
|
||||||
key={parsedEntrypoints.join('/')}
|
key={parsedEntrypoints.join('/')}
|
||||||
|
placeholder={isLoading ? 'Loading entrypoints...' : 'Select an entrypoint'}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="bg-[#f3f3f4] dark:bg-[#121214]" />
|
<SelectTrigger
|
||||||
|
className="bg-[#f3f3f4] dark:bg-[#121214]"
|
||||||
|
suffix={
|
||||||
|
isLoading ? (
|
||||||
|
<Loader2Icon
|
||||||
|
aria-hidden
|
||||||
|
className="size-6 shrink-0 animate-spin duration-200 forced-colors:text-[ButtonText] forced-colors:group-disabled:text-[GrayText]"
|
||||||
|
size={24}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
<SelectList classNames={{ popover: 'bg-[#f3f3f4] dark:bg-[#28282d]' }} items={entryPoints}>
|
<SelectList classNames={{ popover: 'bg-[#f3f3f4] dark:bg-[#28282d]' }} items={entryPoints}>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<SelectOption
|
<SelectOption
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { ChevronDown, ChevronUp } from 'lucide-react';
|
import { ChevronDown, ChevronUp, Loader2Icon } from 'lucide-react';
|
||||||
import { notFound, useParams } from 'next/navigation';
|
import { notFound, useParams } from 'next/navigation';
|
||||||
import { parseDocsPathParams } from '@/util/parseDocsPathParams';
|
import { parseDocsPathParams } from '@/util/parseDocsPathParams';
|
||||||
import { resolveNodeKind } from './DocKind';
|
import { resolveNodeKind } from './DocKind';
|
||||||
@@ -17,7 +17,11 @@ export function Navigation() {
|
|||||||
|
|
||||||
const { entryPoints: parsedEntrypoints } = parseDocsPathParams(params.item);
|
const { entryPoints: parsedEntrypoints } = parseDocsPathParams(params.item);
|
||||||
|
|
||||||
const { data: node, status } = useQuery({
|
const {
|
||||||
|
data: node,
|
||||||
|
status,
|
||||||
|
isLoading,
|
||||||
|
} = useQuery({
|
||||||
queryKey: ['sitemap', params.packageName, params.version, parsedEntrypoints.join('.')],
|
queryKey: ['sitemap', params.packageName, params.version, parsedEntrypoints.join('.')],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@@ -38,6 +42,10 @@ export function Navigation() {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Loader2Icon className="mx-auto h-10 w-10 animate-spin" />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="flex flex-col gap-2 pr-3">
|
<nav className="flex flex-col gap-2 pr-3">
|
||||||
{groupedNodes?.class?.length ? (
|
{groupedNodes?.class?.length ? (
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function SidebarHeader() {
|
|||||||
|
|
||||||
const hasEntryPoints = PACKAGES_WITH_ENTRY_POINTS.includes(params.packageName);
|
const hasEntryPoints = PACKAGES_WITH_ENTRY_POINTS.includes(params.packageName);
|
||||||
|
|
||||||
const { data: entryPoints } = useQuery({
|
const { data: entryPoints, isLoading: isLoadingEntryPoints } = useQuery({
|
||||||
queryKey: ['entryPoints', params.packageName, params.version],
|
queryKey: ['entryPoints', params.packageName, params.version],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`/api/docs/entrypoints?packageName=${params.packageName}&version=${params.version}`);
|
const response = await fetch(`/api/docs/entrypoints?packageName=${params.packageName}&version=${params.version}`);
|
||||||
@@ -30,7 +30,7 @@ export function SidebarHeader() {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: versions } = useQuery({
|
const { data: versions, isLoading: isLoadingVersions } = useQuery({
|
||||||
queryKey: ['versions', params.packageName],
|
queryKey: ['versions', params.packageName],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await fetch(`/api/docs/versions?packageName=${params.packageName}`);
|
const response = await fetch(`/api/docs/versions?packageName=${params.packageName}`);
|
||||||
@@ -61,8 +61,8 @@ export function SidebarHeader() {
|
|||||||
</div>
|
</div>
|
||||||
<PackageSelect />
|
<PackageSelect />
|
||||||
{/* <h3 className="p-1 text-lg font-semibold">{version}</h3> */}
|
{/* <h3 className="p-1 text-lg font-semibold">{version}</h3> */}
|
||||||
<VersionSelect versions={versions ?? []} />
|
<VersionSelect isLoading={isLoadingVersions} versions={versions ?? []} />
|
||||||
{hasEntryPoints ? <EntryPointSelect entryPoints={entryPoints ?? []} /> : null}
|
{hasEntryPoints ? <EntryPointSelect entryPoints={entryPoints ?? []} isLoading={isLoadingEntryPoints} /> : null}
|
||||||
<SearchButton />
|
<SearchButton />
|
||||||
</div>
|
</div>
|
||||||
</BasSidebarHeader>
|
</BasSidebarHeader>
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { Loader2Icon } from 'lucide-react';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
import { Select, SelectList, SelectOption, SelectTrigger } from '@/components/ui/Select';
|
import { Select, SelectList, SelectOption, SelectTrigger } from '@/components/ui/Select';
|
||||||
import { DEFAULT_ENTRY_POINT, PACKAGES_WITH_ENTRY_POINTS } from '@/util/constants';
|
import { DEFAULT_ENTRY_POINT, PACKAGES_WITH_ENTRY_POINTS } from '@/util/constants';
|
||||||
|
|
||||||
export function VersionSelect({ versions }: { readonly versions: { readonly version: string }[] }) {
|
export function VersionSelect({
|
||||||
|
versions,
|
||||||
|
isLoading,
|
||||||
|
}: {
|
||||||
|
readonly isLoading: boolean;
|
||||||
|
readonly versions: { readonly version: string }[];
|
||||||
|
}) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const params = useParams<{ packageName: string; version: string }>();
|
const params = useParams<{ packageName: string; version: string }>();
|
||||||
|
|
||||||
@@ -12,11 +19,24 @@ export function VersionSelect({ versions }: { readonly versions: { readonly vers
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
aria-label="Select a version"
|
aria-label={isLoading ? 'Loading versions...' : 'Select a version'}
|
||||||
defaultSelectedKey={params.version}
|
defaultSelectedKey={params.version}
|
||||||
key={`${params.packageName}-${params.version}`}
|
key={`${params.packageName}-${params.version}`}
|
||||||
|
placeholder={isLoading ? 'Loading versions...' : 'Select a version'}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="bg-[#f3f3f4] dark:bg-[#121214]" />
|
<SelectTrigger
|
||||||
|
className="bg-[#f3f3f4] dark:bg-[#121214]"
|
||||||
|
suffix={
|
||||||
|
isLoading ? (
|
||||||
|
<Loader2Icon
|
||||||
|
aria-hidden
|
||||||
|
className="size-6 shrink-0 animate-spin duration-200 forced-colors:text-[ButtonText] forced-colors:group-disabled:text-[GrayText]"
|
||||||
|
size={24}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
|
/>
|
||||||
<SelectList classNames={{ popover: 'bg-[#f3f3f4] dark:bg-[#28282d]' }} items={versions}>
|
<SelectList classNames={{ popover: 'bg-[#f3f3f4] dark:bg-[#28282d]' }} items={versions}>
|
||||||
{(item) => (
|
{(item) => (
|
||||||
<SelectOption
|
<SelectOption
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ export function SelectList<Type extends object>(props: SelectListProps<Type>) {
|
|||||||
export type SelectTriggerProps = ComponentProps<typeof Button> & {
|
export type SelectTriggerProps = ComponentProps<typeof Button> & {
|
||||||
readonly className?: string;
|
readonly className?: string;
|
||||||
readonly prefix?: ReactNode;
|
readonly prefix?: ReactNode;
|
||||||
|
readonly suffix?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function SelectTrigger(props: SelectTriggerProps) {
|
export function SelectTrigger(props: SelectTriggerProps) {
|
||||||
@@ -113,6 +114,7 @@ export function SelectTrigger(props: SelectTriggerProps) {
|
|||||||
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"
|
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"
|
data-slot="select-value"
|
||||||
/>
|
/>
|
||||||
|
{props.suffix && <span className="mr-10 ml-2 *:data-[slot=icon]:size-5.5">{props.suffix}</span>}
|
||||||
<ChevronDownIcon
|
<ChevronDownIcon
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className="size-6 shrink-0 duration-200 group-open:rotate-180 forced-colors:text-[ButtonText] forced-colors:group-disabled:text-[GrayText]"
|
className="size-6 shrink-0 duration-200 group-open:rotate-180 forced-colors:text-[ButtonText] forced-colors:group-disabled:text-[GrayText]"
|
||||||
|
|||||||
Reference in New Issue
Block a user