mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 08:03:30 +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">
|
||||
<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>
|
||||
<Navigation />
|
||||
</Scrollbars>
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import { Loader2Icon } from 'lucide-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { Select, SelectList, SelectOption, SelectTrigger } from '@/components/ui/Select';
|
||||
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 params = useParams<{
|
||||
item?: string[] | undefined;
|
||||
@@ -16,11 +23,24 @@ export function EntryPointSelect({ entryPoints }: { readonly entryPoints: { read
|
||||
|
||||
return (
|
||||
<Select
|
||||
aria-label="Select an entrypoint"
|
||||
aria-label={isLoading ? 'Loading entrypoints...' : 'Select an entrypoint'}
|
||||
defaultSelectedKey={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}>
|
||||
{(item) => (
|
||||
<SelectOption
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
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 { parseDocsPathParams } from '@/util/parseDocsPathParams';
|
||||
import { resolveNodeKind } from './DocKind';
|
||||
@@ -17,7 +17,11 @@ export function Navigation() {
|
||||
|
||||
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('.')],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(
|
||||
@@ -38,6 +42,10 @@ export function Navigation() {
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
if (isLoading) {
|
||||
return <Loader2Icon className="mx-auto h-10 w-10 animate-spin" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className="flex flex-col gap-2 pr-3">
|
||||
{groupedNodes?.class?.length ? (
|
||||
|
||||
@@ -21,7 +21,7 @@ export function SidebarHeader() {
|
||||
|
||||
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],
|
||||
queryFn: async () => {
|
||||
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],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(`/api/docs/versions?packageName=${params.packageName}`);
|
||||
@@ -61,8 +61,8 @@ export function SidebarHeader() {
|
||||
</div>
|
||||
<PackageSelect />
|
||||
{/* <h3 className="p-1 text-lg font-semibold">{version}</h3> */}
|
||||
<VersionSelect versions={versions ?? []} />
|
||||
{hasEntryPoints ? <EntryPointSelect entryPoints={entryPoints ?? []} /> : null}
|
||||
<VersionSelect isLoading={isLoadingVersions} versions={versions ?? []} />
|
||||
{hasEntryPoints ? <EntryPointSelect entryPoints={entryPoints ?? []} isLoading={isLoadingEntryPoints} /> : null}
|
||||
<SearchButton />
|
||||
</div>
|
||||
</BasSidebarHeader>
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
'use client';
|
||||
|
||||
import { Loader2Icon } from 'lucide-react';
|
||||
import { useParams, useRouter } from 'next/navigation';
|
||||
import { Select, SelectList, SelectOption, SelectTrigger } from '@/components/ui/Select';
|
||||
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 params = useParams<{ packageName: string; version: string }>();
|
||||
|
||||
@@ -12,11 +19,24 @@ export function VersionSelect({ versions }: { readonly versions: { readonly vers
|
||||
|
||||
return (
|
||||
<Select
|
||||
aria-label="Select a version"
|
||||
aria-label={isLoading ? 'Loading versions...' : 'Select a version'}
|
||||
defaultSelectedKey={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}>
|
||||
{(item) => (
|
||||
<SelectOption
|
||||
|
||||
@@ -96,6 +96,7 @@ export function SelectList<Type extends object>(props: SelectListProps<Type>) {
|
||||
export type SelectTriggerProps = ComponentProps<typeof Button> & {
|
||||
readonly className?: string;
|
||||
readonly prefix?: ReactNode;
|
||||
readonly suffix?: ReactNode;
|
||||
};
|
||||
|
||||
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"
|
||||
data-slot="select-value"
|
||||
/>
|
||||
{props.suffix && <span className="mr-10 ml-2 *:data-[slot=icon]:size-5.5">{props.suffix}</span>}
|
||||
<ChevronDownIcon
|
||||
aria-hidden
|
||||
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