Skip to content

Commit

Permalink
fix: sidebar link should always scroll (#1258)
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity authored Aug 7, 2024
1 parent cae362d commit 55930eb
Show file tree
Hide file tree
Showing 13 changed files with 129 additions and 90 deletions.
18 changes: 17 additions & 1 deletion packages/ui/app/src/atoms/apis.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { FernNavigation } from "@fern-api/fdr-sdk";
import { atom, useAtomValue } from "jotai";
import { mapValues } from "lodash-es";
import { useMemoOne } from "use-memo-one";
import { flattenRootPackage, type FlattenedRootPackage, type ResolvedRootPackage } from "../resolver/types";
import { FEATURE_FLAGS_ATOM } from "./flags";
import { RESOLVED_PATH_ATOM } from "./navigation";
import { RESOLVED_API_DEFINITION_ATOM, RESOLVED_PATH_ATOM } from "./navigation";

export const APIS_ATOM = atom<Record<string, ResolvedRootPackage>>({});
APIS_ATOM.debugLabel = "APIS_ATOM";
Expand All @@ -28,3 +30,17 @@ const IS_API_REFERENCE_PAGINATED = atom<boolean>((get) => {
export function useIsApiReferencePaginated(): boolean {
return useAtomValue(IS_API_REFERENCE_PAGINATED);
}

export function useIsApiReferenceShallowLink(node: FernNavigation.WithApiDefinitionId): boolean {
return useAtomValue(
useMemoOne(
() =>
atom((get) => {
const isPaginated = get(IS_API_REFERENCE_PAGINATED);
const resolvedApi = get(RESOLVED_API_DEFINITION_ATOM);
return !isPaginated && resolvedApi === node.apiDefinitionId;
}),
[node.apiDefinitionId],
),
);
}
22 changes: 12 additions & 10 deletions packages/ui/app/src/sidebar/SidebarLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface SidebarSlugLinkProps {
linkClassName?: string;
title?: ReactNode;
shallow?: boolean;
scroll?: boolean;
selected?: boolean;
showIndicator?: boolean;
depth?: number;
Expand All @@ -41,7 +42,6 @@ interface SidebarSlugLinkProps {
rightElement?: ReactNode;
tooltipContent?: ReactNode;
hidden?: boolean;
scrollOnShallow?: boolean;
as?: keyof JSX.IntrinsicElements | JSXElementConstructor<any>;
}

Expand All @@ -64,6 +64,7 @@ const SidebarLinkInternal = forwardRef<HTMLDivElement, SidebarLinkProps>((props,
title,
onClick,
shallow,
scroll,
href,
selected,
showIndicator,
Expand All @@ -76,7 +77,6 @@ const SidebarLinkInternal = forwardRef<HTMLDivElement, SidebarLinkProps>((props,
target,
rel,
hidden,
scrollOnShallow,
as = "span",
} = props;

Expand All @@ -98,14 +98,11 @@ const SidebarLinkInternal = forwardRef<HTMLDivElement, SidebarLinkProps>((props,
onClick={(e) => {
onClick?.(e);
toggleExpand?.();
if (shallow && typeof href === "string") {
scrollToRoute(href);
}
}}
shallow={shallow}
scroll={scrollOnShallow || !shallow}
target={target}
rel={rel}
scroll={scroll}
>
{child}
</FernLink>
Expand Down Expand Up @@ -218,23 +215,28 @@ export const SidebarSlugLink = forwardRef<HTMLDivElement, PropsWithChildren<Side
),
);

const href = slug != null ? slugToHref(slug) : undefined;
const handleClick = useCallback<React.MouseEventHandler<HTMLAnchorElement | HTMLButtonElement>>(
(e) => {
onClick?.(e);
if (slug != null) {
if (href != null) {
closeMobileSidebar();
if (innerProps.shallow) {
scrollToRoute(href);
}
}
},
[closeMobileSidebar, onClick, slug],
[closeMobileSidebar, href, innerProps.shallow, onClick],
);

return (
<SidebarLink
{...innerProps}
ref={ref}
href={slug != null ? slugToHref(slug) : undefined}
href={href}
onClick={handleClick}
shallow={innerProps.shallow ?? innerProps.selected}
shallow={innerProps.shallow || innerProps.selected}
scroll={!innerProps.shallow}
/>
);
},
Expand Down
5 changes: 3 additions & 2 deletions packages/ui/app/src/sidebar/nodes/SidebarApiGroupNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ import { SidebarApiPackageChild } from "./SidebarApiPackageChild";

interface SidebarApiGroupNodeProps {
nodeChildren: (FernNavigation.ApiPackageChild | FernNavigation.ChangelogNode)[];
shallow: boolean;
}
export function SidebarApiGroupNode({ nodeChildren }: SidebarApiGroupNodeProps): React.ReactElement {
export function SidebarApiGroupNode({ nodeChildren, shallow }: SidebarApiGroupNodeProps): React.ReactElement {
return (
<ul className="fern-sidebar-group">
{nodeChildren.map((child) => (
<li key={child.id}>
<SidebarApiPackageChild node={child} depth={0} />
<SidebarApiPackageChild node={child} depth={0} shallow={shallow} />
</li>
))}
</ul>
Expand Down
10 changes: 4 additions & 6 deletions packages/ui/app/src/sidebar/nodes/SidebarApiLeafNode.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { FernNavigation } from "@fern-api/fdr-sdk";
import { useAtomValue } from "jotai";
import { RESOLVED_API_DEFINITION_ATOM, useIsApiReferencePaginated, useIsSelectedSidebarNode } from "../../atoms";
import { useIsSelectedSidebarNode } from "../../atoms";
import { HttpMethodTag } from "../../commons/HttpMethodTag";
import { SidebarSlugLink } from "../SidebarLink";

interface SidebarApiLeafNodeProps {
node: FernNavigation.NavigationNodeApiLeaf;
depth: number;
shallow: boolean;
}

export function SidebarApiLeafNode({ node, depth }: SidebarApiLeafNodeProps): React.ReactElement | null {
export function SidebarApiLeafNode({ node, depth, shallow }: SidebarApiLeafNodeProps): React.ReactElement | null {
const selected = useIsSelectedSidebarNode(node.id);
const isPaginated = useIsApiReferencePaginated();
const resolvedApi = useAtomValue(RESOLVED_API_DEFINITION_ATOM);

if (node.hidden && !selected) {
return null;
Expand All @@ -39,7 +37,7 @@ export function SidebarApiLeafNode({ node, depth }: SidebarApiLeafNodeProps): Re
hidden={node.hidden}
icon={renderRightElement()}
selected={selected}
shallow={!isPaginated && resolvedApi === node.apiDefinitionId}
shallow={shallow}
/>
);
}
13 changes: 7 additions & 6 deletions packages/ui/app/src/sidebar/nodes/SidebarApiPackageChild.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ import { SidebarPageNode } from "./SidebarPageNode";
interface SidebarApiPackageChild {
node: FernNavigation.ApiPackageChild | FernNavigation.ChangelogNode;
depth: number;
shallow: boolean;
}

export function SidebarApiPackageChild({ node, depth }: SidebarApiPackageChild): React.ReactElement {
export function SidebarApiPackageChild({ node, depth, shallow }: SidebarApiPackageChild): React.ReactElement {
return visitDiscriminatedUnion(node)._visit({
page: (node) => <SidebarPageNode node={node} depth={depth} />,
page: (node) => <SidebarPageNode node={node} depth={depth} shallow={shallow} />,
link: (node) => <SidebarLinkNode node={node} depth={depth} />,
endpoint: (node) => <SidebarApiLeafNode node={node} depth={depth} />,
endpointPair: (node) => <SidebarEndpointPairNode node={node} depth={depth} />,
webSocket: (node) => <SidebarApiLeafNode node={node} depth={depth} />,
webhook: (node) => <SidebarApiLeafNode node={node} depth={depth} />,
endpoint: (node) => <SidebarApiLeafNode node={node} depth={depth} shallow={shallow} />,
endpointPair: (node) => <SidebarEndpointPairNode node={node} depth={depth} shallow={shallow} />,
webSocket: (node) => <SidebarApiLeafNode node={node} depth={depth} shallow={shallow} />,
webhook: (node) => <SidebarApiLeafNode node={node} depth={depth} shallow={shallow} />,
apiPackage: (node) => <SidebarApiPackageNode node={node} depth={depth} />,
changelog: (node) => <SidebarChangelogNode node={node} depth={depth} />,
});
Expand Down
9 changes: 6 additions & 3 deletions packages/ui/app/src/sidebar/nodes/SidebarApiPackageNode.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FernNavigation } from "@fern-api/fdr-sdk";
import clsx from "clsx";
import {
useIsApiReferenceShallowLink,
useIsChildSelected,
useIsExpandedSidebarNode,
useIsSelectedSidebarNode,
Expand All @@ -24,6 +25,7 @@ export function SidebarApiPackageNode({
const handleToggleExpand = useToggleExpandedSidebarNode(node.id);
const childSelected = useIsChildSelected(node.id);
const expanded = useIsExpandedSidebarNode(node.id);
const shallow = useIsApiReferenceShallowLink(node);

if (node.children.length === 0) {
if (node.overviewPageId == null) {
Expand All @@ -44,7 +46,7 @@ export function SidebarApiPackageNode({
selected={selected}
icon={node.icon}
hidden={node.hidden}
scrollOnShallow={false}
shallow={shallow}
/>
);
}
Expand All @@ -58,7 +60,7 @@ export function SidebarApiPackageNode({
<ul className={clsx("fern-sidebar-group")}>
{node.children.map((child) => (
<li key={child.id}>
<SidebarApiPackageChild node={child} depth={depth} />
<SidebarApiPackageChild node={child} depth={depth} shallow={shallow} />
</li>
))}
</ul>
Expand All @@ -80,6 +82,7 @@ export function SidebarApiPackageNode({
hidden={node.hidden}
slug={node.overviewPageId != null ? node.slug : undefined}
selected={selected}
shallow={shallow}
>
<ul
className={clsx("fern-sidebar-group", {
Expand All @@ -88,7 +91,7 @@ export function SidebarApiPackageNode({
>
{node.children.map((child) => (
<li key={child.id}>
<SidebarApiPackageChild node={child} depth={depth + 1} />
<SidebarApiPackageChild node={child} depth={depth + 1} shallow={shallow} />
</li>
))}
</ul>
Expand Down
11 changes: 8 additions & 3 deletions packages/ui/app/src/sidebar/nodes/SidebarEndpointPairNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ import { SidebarApiLeafNode } from "./SidebarApiLeafNode";
interface SidebarEndpointPairNodeProps {
node: FernNavigation.EndpointPairNode;
depth: number;
shallow: boolean;
}

export function SidebarEndpointPairNode({ node, depth }: SidebarEndpointPairNodeProps): React.ReactElement | null {
export function SidebarEndpointPairNode({
node,
depth,
shallow,
}: SidebarEndpointPairNodeProps): React.ReactElement | null {
const isStream = useAtomValue(FERN_STREAM_ATOM);

return isStream ? (
<SidebarApiLeafNode node={node.stream} depth={depth} />
<SidebarApiLeafNode node={node.stream} depth={depth} shallow={shallow} />
) : (
<SidebarApiLeafNode node={node.nonStream} depth={depth} />
<SidebarApiLeafNode node={node.nonStream} depth={depth} shallow={shallow} />
);
}
5 changes: 3 additions & 2 deletions packages/ui/app/src/sidebar/nodes/SidebarPageNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ interface SidebarPageNodeProps {
node: FernNavigation.PageNode;
depth: number;
className?: string;
shallow?: boolean;
}

export function SidebarPageNode({ node, depth, className }: SidebarPageNodeProps): React.ReactElement | null {
export function SidebarPageNode({ node, depth, className, shallow }: SidebarPageNodeProps): React.ReactElement | null {
const selected = useIsSelectedSidebarNode(node.id);

if (node.hidden && !selected) {
Expand All @@ -25,7 +26,7 @@ export function SidebarPageNode({ node, depth, className }: SidebarPageNodeProps
selected={selected}
icon={node.icon}
hidden={node.hidden}
scrollOnShallow={false}
shallow={shallow}
/>
);
}
11 changes: 6 additions & 5 deletions packages/ui/app/src/sidebar/nodes/SidebarRootApiPackageNode.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FernNavigation } from "@fern-api/fdr-sdk";
import clsx from "clsx";
import { useIsChildSelected, useIsSelectedSidebarNode } from "../../atoms";
import { useIsApiReferenceShallowLink, useIsChildSelected, useIsSelectedSidebarNode } from "../../atoms";
import { SidebarSlugLink } from "../SidebarLink";
import { SidebarApiPackageChild } from "./SidebarApiPackageChild";
import { SidebarRootHeading } from "./SidebarRootHeading";
Expand All @@ -16,6 +16,7 @@ export function SidebarRootApiPackageNode({
}: SidebarRootApiPackageNodeProps): React.ReactElement | null {
const selected = useIsSelectedSidebarNode(node.id);
const childSelected = useIsChildSelected(node.id);
const shallow = useIsApiReferenceShallowLink(node);

if (node.children.length === 0) {
if (node.overviewPageId == null) {
Expand All @@ -36,7 +37,7 @@ export function SidebarRootApiPackageNode({
selected={selected}
icon={node.icon}
hidden={node.hidden}
scrollOnShallow={false}
shallow={shallow}
/>
);
}
Expand All @@ -47,17 +48,17 @@ export function SidebarRootApiPackageNode({

return (
<>
<SidebarRootHeading node={node} className={className} />
<SidebarRootHeading node={node} className={className} shallow={shallow} />

<ul className={clsx("fern-sidebar-group")}>
{node.children.map((child) => (
<li key={child.id}>
<SidebarApiPackageChild node={child} depth={1} />
<SidebarApiPackageChild node={child} depth={1} shallow={shallow} />
</li>
))}
{node.type === "apiReference" && node.changelog != null && (
<li>
<SidebarApiPackageChild node={node.changelog} depth={1} />
<SidebarApiPackageChild node={node.changelog} depth={1} shallow={shallow} />
</li>
)}
</ul>
Expand Down
57 changes: 57 additions & 0 deletions packages/ui/app/src/sidebar/nodes/SidebarRootApiReferenceNode.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { FernNavigation } from "@fern-api/fdr-sdk";
import { last } from "lodash-es";
import { ReactElement } from "react";
import { useIsApiReferenceShallowLink } from "../../atoms";
import { SidebarApiGroupNode } from "./SidebarApiGroupNode";
import { SidebarRootApiPackageNode } from "./SidebarRootApiPackageNode";

interface SidebarRootApiReferenceNodeProps {
node: FernNavigation.ApiReferenceNode;
}

type ApiGroupOrSection =
| { type: "apiGroup"; children: (FernNavigation.ApiPackageChild | FernNavigation.ChangelogNode)[] }
| FernNavigation.ApiPackageNode;

export function SidebarRootApiReferenceNode({ node }: SidebarRootApiReferenceNodeProps): ReactElement {
const shallow = useIsApiReferenceShallowLink(node);

if (!node.hideTitle) {
return (
<li key={node.id} className="mt-6">
<SidebarRootApiPackageNode node={node} />
</li>
);
}

const groups: ApiGroupOrSection[] = [];

[...node.children, ...(node.changelog != null ? [node.changelog] : [])].forEach((child) => {
if (child.type === "apiPackage") {
groups.push(child);
} else {
const lastGroup = last(groups);
if (lastGroup?.type === "apiGroup") {
lastGroup.children.push(child);
} else {
groups.push({ type: "apiGroup", children: [child] });
}
}
});

return (
<>
{groups.map((child, idx) =>
child.type === "apiPackage" ? (
<li key={idx} className="mt-6">
<SidebarRootApiPackageNode node={child} />
</li>
) : (
<li key={idx} className="mt-6">
<SidebarApiGroupNode nodeChildren={child.children} shallow={shallow} />
</li>
),
)}
</>
);
}
Loading

0 comments on commit 55930eb

Please sign in to comment.