Skip to content

Commit 33d8619

Browse files
committed
feat(website): include loading indicators when data is fetching
1 parent 576443c commit 33d8619

File tree

6 files changed

+64
-14
lines changed

6 files changed

+64
-14
lines changed

apps/website/src/app/docs/packages/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default async function Layout({ children }: PropsWithChildren) {
1212
<>
1313
<Sidebar closeButton={false} intent="inset">
1414
<SidebarHeader />
15-
<SidebarContent className="bg-[#f3f3f4] p-0 py-4 pl-4 dark:bg-[#121214]">
15+
<SidebarContent className="bg-[#f3f3f4] p-0 pb-4 pl-4 dark:bg-[#121214]">
1616
<Scrollbars>
1717
<Navigation />
1818
</Scrollbars>

apps/website/src/components/EntrypointSelect.tsx

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
11
'use client';
22

3+
import { Loader2Icon } from 'lucide-react';
34
import { useParams, useRouter } from 'next/navigation';
5+
import { Select, SelectList, SelectOption, SelectTrigger } from '@/components/ui/Select';
46
import { parseDocsPathParams } from '@/util/parseDocsPathParams';
5-
import { Select, SelectList, SelectOption, SelectTrigger } from './ui/Select';
67

7-
export function EntryPointSelect({ entryPoints }: { readonly entryPoints: { readonly entryPoint: string }[] }) {
8+
export function EntryPointSelect({
9+
entryPoints,
10+
isLoading,
11+
}: {
12+
readonly entryPoints: { readonly entryPoint: string }[];
13+
readonly isLoading: boolean;
14+
}) {
815
const router = useRouter();
916
const params = useParams<{
1017
item?: string[] | undefined;
@@ -16,11 +23,24 @@ export function EntryPointSelect({ entryPoints }: { readonly entryPoints: { read
1623

1724
return (
1825
<Select
19-
aria-label="Select an entrypoint"
26+
aria-label={isLoading ? 'Loading entrypoints...' : 'Select an entrypoint'}
2027
defaultSelectedKey={parsedEntrypoints.join('/')}
2128
key={parsedEntrypoints.join('/')}
29+
placeholder={isLoading ? 'Loading entrypoints...' : 'Select an entrypoint'}
2230
>
23-
<SelectTrigger className="bg-[#f3f3f4] dark:bg-[#121214]" />
31+
<SelectTrigger
32+
className="bg-[#f3f3f4] dark:bg-[#121214]"
33+
suffix={
34+
isLoading ? (
35+
<Loader2Icon
36+
aria-hidden
37+
className="size-6 shrink-0 animate-spin duration-200 forced-colors:text-[ButtonText] forced-colors:group-disabled:text-[GrayText]"
38+
size={24}
39+
strokeWidth={1.5}
40+
/>
41+
) : null
42+
}
43+
/>
2444
<SelectList classNames={{ popover: 'bg-[#f3f3f4] dark:bg-[#28282d]' }} items={entryPoints}>
2545
{(item) => (
2646
<SelectOption

apps/website/src/components/Navigation.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22

33
import { useQuery } from '@tanstack/react-query';
4-
import { ChevronDown, ChevronUp } from 'lucide-react';
4+
import { ChevronDown, ChevronUp, Loader2Icon } from 'lucide-react';
55
import { notFound, useParams } from 'next/navigation';
66
import { parseDocsPathParams } from '@/util/parseDocsPathParams';
77
import { resolveNodeKind } from './DocKind';
@@ -17,7 +17,11 @@ export function Navigation() {
1717

1818
const { entryPoints: parsedEntrypoints } = parseDocsPathParams(params.item);
1919

20-
const { data: node, status } = useQuery({
20+
const {
21+
data: node,
22+
status,
23+
isLoading,
24+
} = useQuery({
2125
queryKey: ['sitemap', params.packageName, params.version, parsedEntrypoints.join('.')],
2226
queryFn: async () => {
2327
const response = await fetch(
@@ -38,6 +42,10 @@ export function Navigation() {
3842
return acc;
3943
}, {});
4044

45+
if (isLoading) {
46+
return <Loader2Icon className="mx-auto h-10 w-10 animate-spin" />;
47+
}
48+
4149
return (
4250
<nav className="flex flex-col gap-2 pr-3">
4351
{groupedNodes?.class?.length ? (

apps/website/src/components/Sidebar.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function SidebarHeader() {
2121

2222
const hasEntryPoints = PACKAGES_WITH_ENTRY_POINTS.includes(params.packageName);
2323

24-
const { data: entryPoints } = useQuery({
24+
const { data: entryPoints, isLoading: isLoadingEntryPoints } = useQuery({
2525
queryKey: ['entryPoints', params.packageName, params.version],
2626
queryFn: async () => {
2727
const response = await fetch(`/api/docs/entrypoints?packageName=${params.packageName}&version=${params.version}`);
@@ -30,7 +30,7 @@ export function SidebarHeader() {
3030
},
3131
});
3232

33-
const { data: versions } = useQuery({
33+
const { data: versions, isLoading: isLoadingVersions } = useQuery({
3434
queryKey: ['versions', params.packageName],
3535
queryFn: async () => {
3636
const response = await fetch(`/api/docs/versions?packageName=${params.packageName}`);
@@ -61,8 +61,8 @@ export function SidebarHeader() {
6161
</div>
6262
<PackageSelect />
6363
{/* <h3 className="p-1 text-lg font-semibold">{version}</h3> */}
64-
<VersionSelect versions={versions ?? []} />
65-
{hasEntryPoints ? <EntryPointSelect entryPoints={entryPoints ?? []} /> : null}
64+
<VersionSelect isLoading={isLoadingVersions} versions={versions ?? []} />
65+
{hasEntryPoints ? <EntryPointSelect entryPoints={entryPoints ?? []} isLoading={isLoadingEntryPoints} /> : null}
6666
<SearchButton />
6767
</div>
6868
</BasSidebarHeader>

apps/website/src/components/VersionSelect.tsx

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,42 @@
11
'use client';
22

3+
import { Loader2Icon } from 'lucide-react';
34
import { useParams, useRouter } from 'next/navigation';
45
import { Select, SelectList, SelectOption, SelectTrigger } from '@/components/ui/Select';
56
import { DEFAULT_ENTRY_POINT, PACKAGES_WITH_ENTRY_POINTS } from '@/util/constants';
67

7-
export function VersionSelect({ versions }: { readonly versions: { readonly version: string }[] }) {
8+
export function VersionSelect({
9+
versions,
10+
isLoading,
11+
}: {
12+
readonly isLoading: boolean;
13+
readonly versions: { readonly version: string }[];
14+
}) {
815
const router = useRouter();
916
const params = useParams<{ packageName: string; version: string }>();
1017

1118
const hasEntryPoints = PACKAGES_WITH_ENTRY_POINTS.includes(params.packageName);
1219

1320
return (
1421
<Select
15-
aria-label="Select a version"
22+
aria-label={isLoading ? 'Loading versions...' : 'Select a version'}
1623
defaultSelectedKey={params.version}
1724
key={`${params.packageName}-${params.version}`}
25+
placeholder={isLoading ? 'Loading versions...' : 'Select a version'}
1826
>
19-
<SelectTrigger className="bg-[#f3f3f4] dark:bg-[#121214]" />
27+
<SelectTrigger
28+
className="bg-[#f3f3f4] dark:bg-[#121214]"
29+
suffix={
30+
isLoading ? (
31+
<Loader2Icon
32+
aria-hidden
33+
className="size-6 shrink-0 animate-spin duration-200 forced-colors:text-[ButtonText] forced-colors:group-disabled:text-[GrayText]"
34+
size={24}
35+
strokeWidth={1.5}
36+
/>
37+
) : null
38+
}
39+
/>
2040
<SelectList classNames={{ popover: 'bg-[#f3f3f4] dark:bg-[#28282d]' }} items={versions}>
2141
{(item) => (
2242
<SelectOption

apps/website/src/components/ui/Select.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export function SelectList<Type extends object>(props: SelectListProps<Type>) {
9696
export type SelectTriggerProps = ComponentProps<typeof Button> & {
9797
readonly className?: string;
9898
readonly prefix?: ReactNode;
99+
readonly suffix?: ReactNode;
99100
};
100101

101102
export function SelectTrigger(props: SelectTriggerProps) {
@@ -113,6 +114,7 @@ export function SelectTrigger(props: SelectTriggerProps) {
113114
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"
114115
data-slot="select-value"
115116
/>
117+
{props.suffix && <span className="mr-10 ml-2 *:data-[slot=icon]:size-5.5">{props.suffix}</span>}
116118
<ChevronDownIcon
117119
aria-hidden
118120
className="size-6 shrink-0 duration-200 group-open:rotate-180 forced-colors:text-[ButtonText] forced-colors:group-disabled:text-[GrayText]"

0 commit comments

Comments
 (0)