Skip to content

Commit 3e84b21

Browse files
committed
feat: Update Expandable to use native <details> element
This has some important benefits: 1. It should be covered by search etc. as this is statically rendered now and just "hidden" via CSS. 2. The browser automatically searches in it when using CMD+F - it will auto-open respective details that contain the searched content.
1 parent 04b4f75 commit 3e84b21

File tree

7 files changed

+158
-81
lines changed

7 files changed

+158
-81
lines changed

docs/contributing/pages/components.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,12 @@ Render an expandable section to provide additional information to users on deman
130130
provide optional information that can help users be more successful.
131131
</Expandable>
132132

133+
<Expandable title="Expandable with a code block">
134+
```js
135+
const foo = 'bar';
136+
```
137+
</Expandable>
138+
133139
```markdown {tabTitle:Example}
134140
<Expandable title="Here's something worth noting">
135141
This is an expandable section in an `'info'` alert style.

src/components/codeBlock/code-blocks.module.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
font-size: 0.85rem;
2020
border: 0;
2121
border-radius: 4px;
22-
margin: 0 0 1rem;
22+
margin: 0;
2323
}
2424

2525
/**

src/components/codeTabs.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,8 +134,6 @@ export function CodeTabs({children}: CodeTabProps) {
134134
}
135135

136136
const Container = styled('div')`
137-
margin-bottom: 1.5rem;
138-
139137
pre[class*='language-'] {
140138
padding: 10px 12px;
141139
border-radius: 0 0 3px 3px;

src/components/docPage/type.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,10 @@
136136
font-weight: 500;
137137
}
138138

139+
.code-tabs-wrapper:not(:last-child) {
140+
margin-bottom: 1.5rem;
141+
}
142+
139143
.anchorlink,
140144
.anchorlink.before {
141145
display: flex;

src/components/expandable.tsx

Lines changed: 0 additions & 78 deletions
This file was deleted.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
'use client';
2+
3+
import {ReactNode, useEffect, useState} from 'react';
4+
import {ChevronDownIcon, ChevronRightIcon} from '@radix-ui/react-icons';
5+
6+
// explicitly not usig CSS modules here
7+
// because there's some prerendered content that depends on these exact class names
8+
import '../callout/styles.scss';
9+
import styles from './style.module.scss';
10+
11+
type Props = {
12+
children: ReactNode;
13+
title: string;
14+
/** If defined, the expandable will be grouped with other expandables that have the same group. */
15+
group?: string;
16+
level?: 'info' | 'warning' | 'success';
17+
permalink?: boolean;
18+
};
19+
20+
function slugify(str: string) {
21+
return str
22+
.toLowerCase()
23+
.replace(/ /g, '-')
24+
.replace(/[^a-z0-9-]/g, '');
25+
}
26+
27+
export function Expandable({title, level = 'info', children, permalink, group}: Props) {
28+
const id = permalink ? slugify(title) : undefined;
29+
30+
const [isExpanded, setIsExpanded] = useState(false);
31+
32+
// Ensure we scroll to the element if the URL hash matches
33+
useEffect(() => {
34+
if (!id) {
35+
return () => {};
36+
}
37+
38+
if (window.location.hash === `#${id}`) {
39+
document.querySelector(`#${id}`)?.scrollIntoView();
40+
setIsExpanded(true);
41+
}
42+
43+
// When the hash changes (e.g. when the back/forward browser buttons are used),
44+
// we want to ensure to jump to the correct section
45+
const onHashChange = () => {
46+
if (window.location.hash === `#${id}`) {
47+
setIsExpanded(true);
48+
document.querySelector(`#${id}`)?.scrollIntoView();
49+
}
50+
};
51+
// listen for hash changes and expand the section if the hash matches the title
52+
window.addEventListener('hashchange', onHashChange);
53+
return () => {
54+
window.removeEventListener('hashchange', onHashChange);
55+
};
56+
}, [id]);
57+
58+
function toggleIsExpanded(event: React.MouseEvent<HTMLDetailsElement>) {
59+
const newVal = event.currentTarget.open;
60+
61+
if (id) {
62+
if (newVal) {
63+
window.history.pushState({}, '', `#${id}`);
64+
} else {
65+
window.history.pushState({}, '', '#');
66+
}
67+
}
68+
69+
setIsExpanded(newVal);
70+
}
71+
72+
return (
73+
<details
74+
name={group}
75+
className={`${styles.expandable} callout !block ${'callout-' + level}`}
76+
open={isExpanded}
77+
// We only need this to keep the URL hash in sync
78+
onToggle={id ? toggleIsExpanded : undefined}
79+
id={id}
80+
>
81+
<summary className={`${styles['expandable-header']} callout-header`}>
82+
<ChevronDownIcon
83+
className={`${styles['expandable-icon-expanded']} callout-icon`}
84+
/>
85+
<ChevronRightIcon
86+
className={`${styles['expandable-icon-collapsed']} callout-icon`}
87+
/>
88+
<div>{title}</div>
89+
</summary>
90+
<div className={`${styles['expandable-body']} callout-body content-flush-bottom`}>
91+
{children}
92+
</div>
93+
</details>
94+
);
95+
}
96+
97+
//
98+
//
99+
// <details name={group} className={`callout !block ${'callout-' + level}`}>
100+
// <summary className="callout-header">{title}</summary>
101+
// <div className="callout-body content-flush-bottom">{children}</div>
102+
// </details>
103+
//
104+
//
105+
106+
//
107+
// <Callout
108+
// level={level}
109+
// title={title}
110+
// Icon={isExpanded ? ChevronDownIcon : ChevronRightIcon}
111+
// id={id}
112+
// titleOnClick={toggleIsExpanded}
113+
// >
114+
// {isExpanded ? children : undefined}
115+
// </Callout>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
.expandable-header {
2+
display: flex;
3+
cursor: pointer;
4+
gap: 0.7rem;
5+
margin-bottom: 0.3rem;
6+
}
7+
8+
.expandable-body {
9+
margin-left: 1.6rem;
10+
11+
& ul, & ol {
12+
padding-left: 1.2rem;
13+
}
14+
}
15+
16+
.expandable {
17+
&:open {
18+
padding-bottom: 1rem;
19+
}
20+
21+
&:not(:open) .expandable-header {
22+
margin-bottom: 0;
23+
}
24+
25+
&:open .expandable-icon-collapsed {
26+
display: none;
27+
}
28+
29+
&:not(:open) .expandable-icon-expanded {
30+
display: none;
31+
}
32+
}

0 commit comments

Comments
 (0)