Skip to content

Commit 311ab02

Browse files
committed
fix: make sidebar category transition actually work
1 parent 89dc2cd commit 311ab02

File tree

3 files changed

+65
-50
lines changed

3 files changed

+65
-50
lines changed

website/src/components/Collapsible/index.tsx

Lines changed: 55 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import React, {
1313
useRef,
1414
useCallback,
1515
useLayoutEffect,
16-
RefObject,
17-
ReactNode,
16+
type RefObject,
17+
type Dispatch,
18+
type SetStateAction,
19+
type ReactNode,
1820
} from 'react';
1921

2022
import { canUseDOM } from '@/utils/environment';
@@ -24,9 +26,18 @@ const useIsomorphicLayoutEffect = canUseDOM ? useLayoutEffect : useEffect;
2426
const DefaultAnimationEasing = 'ease-in-out';
2527

2628
/**
27-
* This hook is a very thin wrapper around a `useState`.
28-
*/
29-
export const useCollapsible = (initialState: boolean | (() => boolean) = false) => {
29+
* This hook is a very thin wrapper around a `useState`.
30+
*/
31+
export function useCollapsible({
32+
initialState,
33+
}: {
34+
/** The initial state. Will be non-collapsed by default. */
35+
initialState: boolean | (() => boolean);
36+
}): {
37+
collapsed: boolean;
38+
setCollapsed: Dispatch<SetStateAction<boolean>>;
39+
toggleCollapsed: () => void;
40+
} {
3041
const [collapsed, setCollapsed] = useState(initialState ?? false);
3142

3243
const toggleCollapsed = useCallback(() => {
@@ -52,7 +63,7 @@ const ExpandedStyles = {
5263
height: 'auto',
5364
} as const;
5465

55-
const applyCollapsedStyle = (el: HTMLElement, collapsed: boolean) => {
66+
function applyCollapsedStyle(el: HTMLElement, collapsed: boolean) {
5667
const collapsedStyles = collapsed ? CollapsedStyles : ExpandedStyles;
5768
el.style.display = collapsedStyles.display;
5869
el.style.overflow = collapsedStyles.overflow;
@@ -64,8 +75,8 @@ Lex111: Dynamic transition duration is used in Material design, this technique
6475
is good for a large number of items.
6576
https://material.io/archive/guidelines/motion/duration-easing.html#duration-easing-dynamic-durations
6677
https://github.com/mui-org/material-ui/blob/e724d98eba018e55e1a684236a2037e24bcf050c/packages/material-ui/src/styles/createTransitions.js#L40-L43
67-
*/
68-
const getAutoHeightDuration = (height: number) => {
78+
*/
79+
function getAutoHeightDuration(height: number) {
6980
const constant = height / 36;
7081
return Math.round((4 + 15 * constant ** 0.25 + constant / 5) * 10);
7182
}
@@ -75,21 +86,21 @@ type CollapsibleAnimationConfig = {
7586
easing?: string;
7687
};
7788

78-
const useCollapseAnimation = ({
89+
function useCollapseAnimation({
7990
collapsibleRef,
8091
collapsed,
8192
animation,
8293
}: {
8394
collapsibleRef: RefObject<HTMLElement>;
8495
collapsed: boolean;
8596
animation?: CollapsibleAnimationConfig;
86-
}) => {
97+
}) {
8798
const mounted = useRef(false);
8899

89100
useEffect(() => {
90101
const el = collapsibleRef.current!;
91102

92-
const getTransitionStyles = () => {
103+
function getTransitionStyles() {
93104
const height = el.scrollHeight;
94105
const duration = animation?.duration ?? getAutoHeightDuration(height);
95106
const easing = animation?.easing ?? DefaultAnimationEasing;
@@ -99,7 +110,7 @@ const useCollapseAnimation = ({
99110
};
100111
}
101112

102-
const applyTransitionStyles = () => {
113+
function applyTransitionStyles() {
103114
const transitionStyles = getTransitionStyles();
104115
el.style.transition = transitionStyles.transition;
105116
el.style.height = transitionStyles.height;
@@ -114,7 +125,7 @@ const useCollapseAnimation = ({
114125

115126
el.style.willChange = 'height';
116127

117-
const startAnimation = () => {
128+
function startAnimation() {
118129
const animationFrame = requestAnimationFrame(() => {
119130
// When collapsing
120131
if (collapsed) {
@@ -146,11 +157,11 @@ type CollapsibleElementType = React.ElementType<
146157
>;
147158

148159
/**
149-
* Prevent hydration layout shift before animations are handled imperatively
150-
* with JS
151-
*/
152-
const getSSRStyle = (collapsed: boolean) => {
153-
if (!canUseDOM) {
160+
* Prevent hydration layout shift before animations are handled imperatively
161+
* with JS
162+
*/
163+
function getSSRStyle(collapsed: boolean) {
164+
if (canUseDOM) {
154165
return undefined;
155166
}
156167
return collapsed ? CollapsedStyles : ExpandedStyles;
@@ -165,32 +176,33 @@ type CollapsibleBaseProps = {
165176
/** Configuration of animation, like `duration` and `easing` */
166177
animation?: CollapsibleAnimationConfig;
167178
/**
168-
* A callback fired when the collapse transition animation ends. Receives
169-
* the **new** collapsed state: e.g. when
170-
* expanding, `collapsed` will be `false`. You can use this for some "cleanup"
171-
* like applying new styles when the container is fully expanded.
172-
*/
179+
* A callback fired when the collapse transition animation ends. Receives
180+
* the **new** collapsed state: e.g. when
181+
* expanding, `collapsed` will be `false`. You can use this for some "cleanup"
182+
* like applying new styles when the container is fully expanded.
183+
*/
173184
onCollapseTransitionEnd?: (collapsed: boolean) => void;
174185
/** Class name for the underlying DOM element. */
175186
className?: string;
176187
/**
177-
* This is mostly useful for details/summary component where ssrStyle is not
178-
* needed (as details are hidden natively) and can mess up with the browser's
179-
* native behavior when JS fails to load or is disabled
180-
*/
188+
* This is mostly useful for details/summary component where ssrStyle is not
189+
* needed (as details are hidden natively) and can mess up with the browser's
190+
* native behavior when JS fails to load or is disabled
191+
*/
181192
disableSSRStyle?: boolean;
182193
};
183194

184-
const CollapsibleBase = ({
195+
function CollapsibleBase({
185196
as: As = 'div',
186197
collapsed,
187198
children,
188199
animation,
189200
onCollapseTransitionEnd,
190201
className,
191202
disableSSRStyle,
192-
}: CollapsibleBaseProps) => {
203+
}: CollapsibleBaseProps) {
193204
// any because TS is a pain for HTML element refs, see https://twitter.com/sebastienlorber/status/1412784677795110914
205+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
194206
const collapsibleRef = useRef<any>(null);
195207

196208
useCollapseAnimation({collapsibleRef, collapsed, animation});
@@ -215,17 +227,17 @@ const CollapsibleBase = ({
215227
);
216228
}
217229

218-
const CollapsibleLazy = ({collapsed, ...props}: CollapsibleBaseProps) => {
230+
function CollapsibleLazy({collapsed, ...props}: CollapsibleBaseProps) {
219231
const [mounted, setMounted] = useState(!collapsed);
232+
// Updated in effect so that first expansion transition can work
233+
const [lazyCollapsed, setLazyCollapsed] = useState(collapsed);
220234

221235
useIsomorphicLayoutEffect(() => {
222236
if (!collapsed) {
223237
setMounted(true);
224238
}
225239
}, [collapsed]);
226240

227-
// lazyCollapsed updated in effect so that first expansion transition can work
228-
const [lazyCollapsed, setLazyCollapsed] = useState(collapsed);
229241
useIsomorphicLayoutEffect(() => {
230242
if (mounted) {
231243
setLazyCollapsed(collapsed);
@@ -239,22 +251,20 @@ const CollapsibleLazy = ({collapsed, ...props}: CollapsibleBaseProps) => {
239251

240252
type CollapsibleProps = CollapsibleBaseProps & {
241253
/**
242-
* Delay rendering of the content till first expansion. Marked as required to
243-
* force us to think if content should be server-rendered or not. This has
244-
* perf impact since it reduces html file sizes, but could undermine SEO.
245-
* @see https://github.com/facebook/docusaurus/issues/4753
246-
*/
254+
* Delay rendering of the content till first expansion. Marked as required to
255+
* force us to think if content should be server-rendered or not. This has
256+
* perf impact since it reduces html file sizes, but could undermine SEO.
257+
* @see https://github.com/facebook/docusaurus/issues/4753
258+
*/
247259
lazy: boolean;
248260
};
249261

250262
/**
251-
* A headless component providing smooth and uniform collapsing behavior. The
252-
* component will be invisible (zero height) when collapsed. Doesn't provide
253-
* interactivity by itself: collapse state is toggled through props.
254-
*/
255-
const Collapsible = ({lazy, ...props}: CollapsibleProps): JSX.Element => {
263+
* A headless component providing smooth and uniform collapsing behavior. The
264+
* component will be invisible (zero height) when collapsed. Doesn't provide
265+
* interactivity by itself: collapse state is toggled through props.
266+
*/
267+
export function Collapsible({lazy, ...props}: CollapsibleProps): JSX.Element {
256268
const Comp = lazy ? CollapsibleLazy : CollapsibleBase;
257269
return <Comp {...props} />;
258-
}
259-
260-
export default Collapsible;
270+
}

website/src/components/Sidebar/SidebarItem.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import React from "react";
99
import Link from "next/link";
1010
import c from "clsx";
1111

12-
import Collapsible, { useCollapsible } from "@/components/Collapsible";
12+
import { Collapsible, useCollapsible } from "@/components/Collapsible";
13+
import { canUseDOM } from "@/utils/environment";
1314
import SidebarItems from "./SidebarItems";
1415

1516
import s from "./styles.module.scss";
1617

1718
import type { SidebarCategory as SidebarCategoryType, SidebarDoc as SidebarDocType, SidebarItem as SidebarItemType } from "@/lib/helpers/sidebar";
1819
import type { SidebarProps } from ".";
19-
import { canUseDOM } from "@/utils/environment";
2020

2121
interface SidebarItemProps extends Omit<SidebarProps, 'items'>, React.AnchorHTMLAttributes<HTMLAnchorElement> {
2222
item: SidebarItemType
@@ -33,8 +33,8 @@ const SidebarCategory = ({ item, activePath, ...props }: SidebarItemProps): JSX.
3333
const { items, label, href } = item as SidebarCategoryType;
3434

3535
const isActive = isActiveSidebarItem(item, activePath);
36-
const { collapsed, toggleCollapsed } = useCollapsible(!canUseDOM ? true : !isActive);
37-
36+
const { collapsed, toggleCollapsed } = useCollapsible({ initialState: !canUseDOM ? true : !isActive });
37+
3838
return (
3939
<li>
4040
<div className={s.category}>
@@ -56,7 +56,7 @@ const SidebarCategory = ({ item, activePath, ...props }: SidebarItemProps): JSX.
5656
/>
5757
</div>
5858
<Collapsible
59-
className={s.items}
59+
className={c(s.items, collapsed && s.collapsed)}
6060
as="ul"
6161
collapsed={collapsed}
6262
lazy

website/src/components/Sidebar/styles.module.scss

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ $desktop-sidebar-width: 300px;
2020
margin-top: 0.25rem;
2121
padding-left: 1rem;
2222
}
23+
24+
&.collapsed & {
25+
height: 0;
26+
overflow: hidden;
27+
}
2328
}
2429

2530
.link, .arrow, .category {

0 commit comments

Comments
 (0)