@widlarzgroup/docusaurus-ui 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -0
- package/lib/components/PlatformsList/PlatformsList.d.ts +7 -0
- package/lib/components/PlatformsList/PlatformsList.d.ts.map +1 -0
- package/lib/components/PlatformsList/PlatformsList.js +14 -0
- package/lib/components/PlatformsList/PlatformsList.js.map +1 -0
- package/lib/components/PlatformsList/PlatformsList.module.css +10 -0
- package/lib/components/PlusBadge/PlusBadge.d.ts +6 -0
- package/lib/components/PlusBadge/PlusBadge.d.ts.map +1 -0
- package/lib/components/PlusBadge/PlusBadge.js +12 -0
- package/lib/components/PlusBadge/PlusBadge.js.map +1 -0
- package/lib/components/PlusBadge/PlusBadge.module.css +17 -0
- package/lib/components/ProBadge/ProBadge.d.ts +2 -0
- package/lib/components/ProBadge/ProBadge.d.ts.map +1 -0
- package/lib/components/ProBadge/ProBadge.js +12 -0
- package/lib/components/ProBadge/ProBadge.js.map +1 -0
- package/lib/components/ProBadge/ProBadge.module.css +8 -0
- package/lib/components/ProFeature/ProFeature.d.ts +7 -0
- package/lib/components/ProFeature/ProFeature.d.ts.map +1 -0
- package/lib/components/ProFeature/ProFeature.js +12 -0
- package/lib/components/ProFeature/ProFeature.js.map +1 -0
- package/lib/components/ProFeature/ProFeature.module.css +89 -0
- package/lib/components/StatusBadge/StatusBadge.d.ts +7 -0
- package/lib/components/StatusBadge/StatusBadge.d.ts.map +1 -0
- package/lib/components/StatusBadge/StatusBadge.js +26 -0
- package/lib/components/StatusBadge/StatusBadge.js.map +1 -0
- package/lib/components/StatusBadge/StatusBadge.module.css +26 -0
- package/lib/components/TWGBadge/TWGBadge.d.ts +6 -0
- package/lib/components/TWGBadge/TWGBadge.d.ts.map +1 -0
- package/lib/components/TWGBadge/TWGBadge.js +15 -0
- package/lib/components/TWGBadge/TWGBadge.js.map +1 -0
- package/lib/components/TWGBadge/TWGBadge.module.css +107 -0
- package/lib/css/custom.css +614 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +37 -0
- package/lib/index.js.map +1 -0
- package/lib/theme/DocItem/Footer/index.d.ts +3 -0
- package/lib/theme/DocItem/Footer/index.d.ts.map +1 -0
- package/lib/theme/DocItem/Footer/index.js +24 -0
- package/lib/theme/DocItem/Footer/index.js.map +1 -0
- package/lib/theme/DocItem/TOC/Desktop/index.d.ts +3 -0
- package/lib/theme/DocItem/TOC/Desktop/index.d.ts.map +1 -0
- package/lib/theme/DocItem/TOC/Desktop/index.js +15 -0
- package/lib/theme/DocItem/TOC/Desktop/index.js.map +1 -0
- package/lib/theme/DocSidebarItem/Category/index.d.ts +5 -0
- package/lib/theme/DocSidebarItem/Category/index.d.ts.map +1 -0
- package/lib/theme/DocSidebarItem/Category/index.js +224 -0
- package/lib/theme/DocSidebarItem/Category/index.js.map +1 -0
- package/lib/theme/DocSidebarItem/Category/styles.module.css +119 -0
- package/lib/theme/DocSidebarItem/Link/index.d.ts +5 -0
- package/lib/theme/DocSidebarItem/Link/index.d.ts.map +1 -0
- package/lib/theme/DocSidebarItem/Link/index.js +35 -0
- package/lib/theme/DocSidebarItem/Link/index.js.map +1 -0
- package/lib/theme/DocSidebarItem/Link/styles.module.css +18 -0
- package/lib/theme/TOC/index.d.ts +4 -0
- package/lib/theme/TOC/index.d.ts.map +1 -0
- package/lib/theme/TOC/index.js +19 -0
- package/lib/theme/TOC/index.js.map +1 -0
- package/lib/theme/TOC/styles.module.css +16 -0
- package/lib/types/sidebar.d.ts +17 -0
- package/lib/types/sidebar.d.ts.map +1 -0
- package/lib/types/sidebar.js +3 -0
- package/lib/types/sidebar.js.map +1 -0
- package/package.json +41 -0
- package/src/components/PlatformsList/PlatformsList.module.css +10 -0
- package/src/components/PlatformsList/PlatformsList.tsx +28 -0
- package/src/components/PlusBadge/PlusBadge.module.css +17 -0
- package/src/components/PlusBadge/PlusBadge.tsx +23 -0
- package/src/components/ProBadge/ProBadge.module.css +8 -0
- package/src/components/ProBadge/ProBadge.tsx +21 -0
- package/src/components/ProFeature/ProFeature.module.css +89 -0
- package/src/components/ProFeature/ProFeature.tsx +51 -0
- package/src/components/StatusBadge/StatusBadge.module.css +26 -0
- package/src/components/StatusBadge/StatusBadge.tsx +32 -0
- package/src/components/TWGBadge/TWGBadge.module.css +107 -0
- package/src/components/TWGBadge/TWGBadge.tsx +27 -0
- package/src/css/custom.css +614 -0
- package/src/index.ts +22 -0
- package/src/theme/DocItem/Footer/index.tsx +51 -0
- package/src/theme/DocSidebarItem/Category/index.tsx +403 -0
- package/src/theme/DocSidebarItem/Category/styles.module.css +119 -0
- package/src/theme/DocSidebarItem/Link/index.tsx +85 -0
- package/src/theme/DocSidebarItem/Link/styles.module.css +18 -0
- package/src/theme/TOC/index.tsx +25 -0
- package/src/theme/TOC/styles.module.css +16 -0
- package/src/types/sidebar.ts +23 -0
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
import React, {
|
|
2
|
+
type ComponentProps,
|
|
3
|
+
type ReactNode,
|
|
4
|
+
useEffect,
|
|
5
|
+
useMemo,
|
|
6
|
+
} from 'react';
|
|
7
|
+
import clsx from 'clsx';
|
|
8
|
+
import {
|
|
9
|
+
ThemeClassNames,
|
|
10
|
+
useThemeConfig,
|
|
11
|
+
usePrevious,
|
|
12
|
+
Collapsible,
|
|
13
|
+
useCollapsible,
|
|
14
|
+
} from '@docusaurus/theme-common';
|
|
15
|
+
import { isSamePath } from '@docusaurus/theme-common/internal';
|
|
16
|
+
import {
|
|
17
|
+
isActiveSidebarItem,
|
|
18
|
+
findFirstSidebarItemLink,
|
|
19
|
+
useDocSidebarItemsExpandedState,
|
|
20
|
+
useVisibleSidebarItems,
|
|
21
|
+
} from '@docusaurus/plugin-content-docs/client';
|
|
22
|
+
import Link from '@docusaurus/Link';
|
|
23
|
+
import { translate } from '@docusaurus/Translate';
|
|
24
|
+
import useIsBrowser from '@docusaurus/useIsBrowser';
|
|
25
|
+
import DocSidebarItems from '@theme-original/DocSidebarItems';
|
|
26
|
+
import DocSidebarItemLink from '@theme-original/DocSidebarItem/Link';
|
|
27
|
+
import type { Props } from '@theme/DocSidebarItem/Category';
|
|
28
|
+
import ProBadge from '@/components/ProBadge/ProBadge';
|
|
29
|
+
import StatusBadge from '@/components/StatusBadge/StatusBadge';
|
|
30
|
+
import type {
|
|
31
|
+
WithCustomProps,
|
|
32
|
+
CustomSidebarProps,
|
|
33
|
+
} from '@/types/sidebar';
|
|
34
|
+
|
|
35
|
+
import type {
|
|
36
|
+
PropSidebarItemCategory,
|
|
37
|
+
PropSidebarItemLink,
|
|
38
|
+
} from '@docusaurus/plugin-content-docs';
|
|
39
|
+
import styles from './styles.module.css';
|
|
40
|
+
|
|
41
|
+
// If we navigate to a category and it becomes active, it should automatically
|
|
42
|
+
// expand itself
|
|
43
|
+
function useAutoExpandActiveCategory({
|
|
44
|
+
isActive,
|
|
45
|
+
collapsed,
|
|
46
|
+
updateCollapsed,
|
|
47
|
+
activePath,
|
|
48
|
+
}: {
|
|
49
|
+
isActive: boolean;
|
|
50
|
+
collapsed: boolean;
|
|
51
|
+
updateCollapsed: (b: boolean) => void;
|
|
52
|
+
activePath: string;
|
|
53
|
+
}) {
|
|
54
|
+
const wasActive = usePrevious(isActive);
|
|
55
|
+
const previousActivePath = usePrevious(activePath);
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
const justBecameActive = isActive && !wasActive;
|
|
58
|
+
const stillActiveButPathChanged =
|
|
59
|
+
isActive && wasActive && activePath !== previousActivePath;
|
|
60
|
+
if ((justBecameActive || stillActiveButPathChanged) && collapsed) {
|
|
61
|
+
updateCollapsed(false);
|
|
62
|
+
}
|
|
63
|
+
}, [
|
|
64
|
+
isActive,
|
|
65
|
+
wasActive,
|
|
66
|
+
collapsed,
|
|
67
|
+
updateCollapsed,
|
|
68
|
+
activePath,
|
|
69
|
+
previousActivePath,
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* When a collapsible category has no link, we still link it to its first child
|
|
75
|
+
* during SSR as a temporary fallback. This allows to be able to navigate inside
|
|
76
|
+
* the category even when JS fails to load, is delayed or simply disabled
|
|
77
|
+
* React hydration becomes an optional progressive enhancement
|
|
78
|
+
* see https://github.com/facebookincubator/infima/issues/36#issuecomment-772543188
|
|
79
|
+
* see https://github.com/facebook/docusaurus/issues/3030
|
|
80
|
+
*/
|
|
81
|
+
function useCategoryHrefWithSSRFallback(
|
|
82
|
+
item: Props['item']
|
|
83
|
+
): string | undefined {
|
|
84
|
+
const isBrowser = useIsBrowser();
|
|
85
|
+
return useMemo(() => {
|
|
86
|
+
if (item.href && !item.linkUnlisted) {
|
|
87
|
+
return item.href;
|
|
88
|
+
}
|
|
89
|
+
// In these cases, it's not necessary to render a fallback
|
|
90
|
+
// We skip the "findFirstCategoryLink" computation
|
|
91
|
+
if (isBrowser || !item.collapsible) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
return findFirstSidebarItemLink(item);
|
|
95
|
+
}, [item, isBrowser]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function CollapseButton({
|
|
99
|
+
collapsed,
|
|
100
|
+
categoryLabel,
|
|
101
|
+
onClick,
|
|
102
|
+
}: {
|
|
103
|
+
collapsed: boolean;
|
|
104
|
+
categoryLabel: string;
|
|
105
|
+
onClick: ComponentProps<'button'>['onClick'];
|
|
106
|
+
}) {
|
|
107
|
+
return (
|
|
108
|
+
<button
|
|
109
|
+
aria-label={
|
|
110
|
+
collapsed
|
|
111
|
+
? translate(
|
|
112
|
+
{
|
|
113
|
+
id: 'theme.DocSidebarItem.expandCategoryAriaLabel',
|
|
114
|
+
message: "Expand sidebar category '{label}'",
|
|
115
|
+
description: 'The ARIA label to expand the sidebar category',
|
|
116
|
+
},
|
|
117
|
+
{ label: categoryLabel }
|
|
118
|
+
)
|
|
119
|
+
: translate(
|
|
120
|
+
{
|
|
121
|
+
id: 'theme.DocSidebarItem.collapseCategoryAriaLabel',
|
|
122
|
+
message: "Collapse sidebar category '{label}'",
|
|
123
|
+
description: 'The ARIA label to collapse the sidebar category',
|
|
124
|
+
},
|
|
125
|
+
{ label: categoryLabel }
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
aria-expanded={!collapsed}
|
|
129
|
+
type="button"
|
|
130
|
+
className="clean-btn menu__caret"
|
|
131
|
+
onClick={onClick}
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function CategoryLinkLabel({
|
|
137
|
+
label,
|
|
138
|
+
isRootCategory,
|
|
139
|
+
customProps,
|
|
140
|
+
}: {
|
|
141
|
+
label: string;
|
|
142
|
+
isRootCategory: boolean;
|
|
143
|
+
customProps?: CustomSidebarProps;
|
|
144
|
+
}) {
|
|
145
|
+
return (
|
|
146
|
+
<>
|
|
147
|
+
<span
|
|
148
|
+
title={label}
|
|
149
|
+
className={clsx(styles.categoryLinkLabel, {
|
|
150
|
+
[styles.categoryLinkLabelRoot]: isRootCategory,
|
|
151
|
+
[styles.categoryLinkLabelNested]: !isRootCategory,
|
|
152
|
+
})}
|
|
153
|
+
>
|
|
154
|
+
{label}
|
|
155
|
+
</span>
|
|
156
|
+
{customProps?.plan === 'pro' && <ProBadge />}
|
|
157
|
+
{customProps?.badgeType && <StatusBadge type={customProps.badgeType} />}
|
|
158
|
+
</>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export default function DocSidebarItemCategory(
|
|
163
|
+
props: WithCustomProps<Props>
|
|
164
|
+
): ReactNode {
|
|
165
|
+
const visibleChildren = useVisibleSidebarItems(
|
|
166
|
+
props.item.items,
|
|
167
|
+
props.activePath
|
|
168
|
+
);
|
|
169
|
+
if (visibleChildren.length === 0) {
|
|
170
|
+
return <DocSidebarItemCategoryEmpty {...props} />;
|
|
171
|
+
} else {
|
|
172
|
+
return <DocSidebarItemCategoryCollapsible {...props} />;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isCategoryWithHref(
|
|
177
|
+
category: PropSidebarItemCategory
|
|
178
|
+
): category is PropSidebarItemCategory & { href: string } {
|
|
179
|
+
return typeof category.href === 'string';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// If a category doesn't have any visible children, we render it as a link
|
|
183
|
+
function DocSidebarItemCategoryEmpty({
|
|
184
|
+
item,
|
|
185
|
+
...props
|
|
186
|
+
}: WithCustomProps<Props>): ReactNode {
|
|
187
|
+
// If the category has no link, we don't render anything
|
|
188
|
+
// It's not super useful to render a category you can't open nor click
|
|
189
|
+
if (!isCategoryWithHref(item)) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
// We remove props that don't make sense for a link and forward the rest
|
|
193
|
+
const {
|
|
194
|
+
type: _type,
|
|
195
|
+
collapsed: _collapsed,
|
|
196
|
+
collapsible: _collapsible,
|
|
197
|
+
items: _items,
|
|
198
|
+
linkUnlisted: _linkUnlisted,
|
|
199
|
+
...forwardableProps
|
|
200
|
+
} = item;
|
|
201
|
+
const linkItem: PropSidebarItemLink = {
|
|
202
|
+
type: 'link',
|
|
203
|
+
...forwardableProps,
|
|
204
|
+
};
|
|
205
|
+
return <DocSidebarItemLink item={linkItem} {...props} />;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function DocSidebarItemCategoryCollapsible({
|
|
209
|
+
item,
|
|
210
|
+
onItemClick,
|
|
211
|
+
activePath,
|
|
212
|
+
level,
|
|
213
|
+
index,
|
|
214
|
+
...props
|
|
215
|
+
}: WithCustomProps<Props>): ReactNode {
|
|
216
|
+
const { items, label, collapsible, className, href, customProps } = item;
|
|
217
|
+
const {
|
|
218
|
+
docs: {
|
|
219
|
+
sidebar: { autoCollapseCategories },
|
|
220
|
+
},
|
|
221
|
+
} = useThemeConfig();
|
|
222
|
+
const hrefWithSSRFallback = useCategoryHrefWithSSRFallback(item);
|
|
223
|
+
|
|
224
|
+
const isActive = isActiveSidebarItem(item, activePath);
|
|
225
|
+
const isCurrentPage = isSamePath(href, activePath);
|
|
226
|
+
|
|
227
|
+
// Root categories (level === 1) are always expanded and non-collapsible
|
|
228
|
+
const isRootCategory = level === 1;
|
|
229
|
+
|
|
230
|
+
const { collapsed, setCollapsed } = useCollapsible({
|
|
231
|
+
// Root categories (level === 1) are always expanded
|
|
232
|
+
// Level 2 categories respect the collapsed prop from sidebars.ts
|
|
233
|
+
// Deeper nested categories (level > 2) are collapsed by default, unless they are active
|
|
234
|
+
initialState: () => {
|
|
235
|
+
if (isRootCategory) {
|
|
236
|
+
return false; // Always expanded for root categories
|
|
237
|
+
}
|
|
238
|
+
if (!collapsible) {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
// Level 2 categories: respect the collapsed prop from sidebars.ts
|
|
242
|
+
if (level === 2) {
|
|
243
|
+
return item.collapsed ?? false; // Use sidebar config, default to open
|
|
244
|
+
}
|
|
245
|
+
// Deeper nested categories (level > 2): only expand if active, otherwise collapse
|
|
246
|
+
return isActive ? false : true;
|
|
247
|
+
},
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
const { expandedItem, setExpandedItem } = useDocSidebarItemsExpandedState();
|
|
251
|
+
// Use this instead of `setCollapsed`, because it is also reactive
|
|
252
|
+
const updateCollapsed = (toCollapsed: boolean = !collapsed) => {
|
|
253
|
+
// Root categories cannot be collapsed
|
|
254
|
+
if (isRootCategory) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
setExpandedItem(toCollapsed ? null : index);
|
|
258
|
+
setCollapsed(toCollapsed);
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// Only auto-expand for nested categories, not root
|
|
262
|
+
// We need to call the hook unconditionally (React rules), but make it a no-op for root categories
|
|
263
|
+
useAutoExpandActiveCategory({
|
|
264
|
+
isActive: isActive && !isRootCategory,
|
|
265
|
+
collapsed: isRootCategory ? false : collapsed,
|
|
266
|
+
updateCollapsed: isRootCategory ? () => {} : updateCollapsed,
|
|
267
|
+
activePath,
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
useEffect(() => {
|
|
271
|
+
// Root categories should never collapse - always keep them expanded
|
|
272
|
+
if (isRootCategory) {
|
|
273
|
+
setCollapsed(false);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
// Auto-collapse when other categories expand (only if autoCollapseCategories is enabled)
|
|
277
|
+
if (
|
|
278
|
+
collapsible &&
|
|
279
|
+
expandedItem != null &&
|
|
280
|
+
expandedItem !== index &&
|
|
281
|
+
autoCollapseCategories
|
|
282
|
+
) {
|
|
283
|
+
setCollapsed(true);
|
|
284
|
+
}
|
|
285
|
+
}, [
|
|
286
|
+
collapsible,
|
|
287
|
+
expandedItem,
|
|
288
|
+
index,
|
|
289
|
+
setCollapsed,
|
|
290
|
+
autoCollapseCategories,
|
|
291
|
+
isRootCategory,
|
|
292
|
+
]);
|
|
293
|
+
|
|
294
|
+
const handleItemClick: ComponentProps<'a'>['onClick'] = (e) => {
|
|
295
|
+
onItemClick?.(item);
|
|
296
|
+
// Root categories cannot be collapsed, so skip collapse logic
|
|
297
|
+
if (isRootCategory) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (collapsible) {
|
|
301
|
+
if (href) {
|
|
302
|
+
// When already on the category's page, we collapse it
|
|
303
|
+
// We don't use "isActive" because it would collapse the
|
|
304
|
+
// category even when we browse a children element
|
|
305
|
+
// See https://github.com/facebook/docusaurus/issues/11213
|
|
306
|
+
if (isCurrentPage) {
|
|
307
|
+
e.preventDefault();
|
|
308
|
+
updateCollapsed();
|
|
309
|
+
} else {
|
|
310
|
+
// When navigating to a new category, we always expand
|
|
311
|
+
// see https://github.com/facebook/docusaurus/issues/10854#issuecomment-2609616182
|
|
312
|
+
updateCollapsed(false);
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
e.preventDefault();
|
|
316
|
+
updateCollapsed();
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// Dynamic indent for nested categories (level 3+)
|
|
322
|
+
const nestedIndent = level >= 3 ? (level - 2) * 0.05 : 0;
|
|
323
|
+
|
|
324
|
+
return (
|
|
325
|
+
<li
|
|
326
|
+
className={clsx(
|
|
327
|
+
ThemeClassNames.docs.docSidebarItemCategory,
|
|
328
|
+
ThemeClassNames.docs.docSidebarItemCategoryLevel(level),
|
|
329
|
+
'menu__list-item',
|
|
330
|
+
{
|
|
331
|
+
'menu__list-item--collapsed': collapsed,
|
|
332
|
+
[styles.rootCategoryContainer]: isRootCategory,
|
|
333
|
+
},
|
|
334
|
+
className
|
|
335
|
+
)}
|
|
336
|
+
>
|
|
337
|
+
<div
|
|
338
|
+
className={clsx('menu__list-item-collapsible', {
|
|
339
|
+
'menu__list-item-collapsible--active': isCurrentPage,
|
|
340
|
+
})}
|
|
341
|
+
>
|
|
342
|
+
<Link
|
|
343
|
+
className={clsx(styles.categoryLink, 'menu__link', {
|
|
344
|
+
[styles.categoryLinkRoot]: isRootCategory,
|
|
345
|
+
'menu__link--sublist': collapsible && !isRootCategory,
|
|
346
|
+
'menu__link--sublist-caret':
|
|
347
|
+
!href && collapsible && !isRootCategory,
|
|
348
|
+
'menu__link--active': isActive,
|
|
349
|
+
})}
|
|
350
|
+
style={
|
|
351
|
+
nestedIndent > 0
|
|
352
|
+
? { paddingLeft: `${nestedIndent + 0.5}rem` }
|
|
353
|
+
: undefined
|
|
354
|
+
}
|
|
355
|
+
onClick={handleItemClick}
|
|
356
|
+
aria-current={isCurrentPage ? 'page' : undefined}
|
|
357
|
+
role={collapsible && !href && !isRootCategory ? 'button' : undefined}
|
|
358
|
+
aria-expanded={
|
|
359
|
+
collapsible && !href && !isRootCategory ? !collapsed : undefined
|
|
360
|
+
}
|
|
361
|
+
href={
|
|
362
|
+
collapsible && !isRootCategory
|
|
363
|
+
? (hrefWithSSRFallback ?? '#')
|
|
364
|
+
: hrefWithSSRFallback
|
|
365
|
+
}
|
|
366
|
+
{...props}
|
|
367
|
+
>
|
|
368
|
+
{/* Only show collapse button for nested categories (not root) */}
|
|
369
|
+
{href && collapsible && !isRootCategory && (
|
|
370
|
+
<CollapseButton
|
|
371
|
+
collapsed={collapsed}
|
|
372
|
+
categoryLabel={label}
|
|
373
|
+
onClick={(e) => {
|
|
374
|
+
e.preventDefault();
|
|
375
|
+
updateCollapsed();
|
|
376
|
+
}}
|
|
377
|
+
/>
|
|
378
|
+
)}
|
|
379
|
+
<CategoryLinkLabel
|
|
380
|
+
label={label}
|
|
381
|
+
isRootCategory={isRootCategory}
|
|
382
|
+
customProps={customProps}
|
|
383
|
+
/>
|
|
384
|
+
</Link>
|
|
385
|
+
</div>
|
|
386
|
+
|
|
387
|
+
<Collapsible
|
|
388
|
+
lazy
|
|
389
|
+
as="ul"
|
|
390
|
+
className={clsx('menu__list', isRootCategory && styles.categoryList)}
|
|
391
|
+
collapsed={collapsed}
|
|
392
|
+
>
|
|
393
|
+
<DocSidebarItems
|
|
394
|
+
items={items}
|
|
395
|
+
tabIndex={collapsed ? -1 : 0}
|
|
396
|
+
onItemClick={onItemClick}
|
|
397
|
+
activePath={activePath}
|
|
398
|
+
level={level + 1}
|
|
399
|
+
/>
|
|
400
|
+
</Collapsible>
|
|
401
|
+
</li>
|
|
402
|
+
);
|
|
403
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
.categoryLink {
|
|
2
|
+
overflow: hidden;
|
|
3
|
+
display: flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
padding-left: 0;
|
|
6
|
+
gap: 0.1;
|
|
7
|
+
position: relative;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.categoryLink::after {
|
|
11
|
+
order: -1;
|
|
12
|
+
margin-left: 1rem;
|
|
13
|
+
min-width: 1rem;
|
|
14
|
+
width: 1rem;
|
|
15
|
+
height: 1rem;
|
|
16
|
+
flex-shrink: 0;
|
|
17
|
+
background-size: contain;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.categoryLinkLabel {
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
display: -webkit-box;
|
|
23
|
+
line-clamp: 2;
|
|
24
|
+
-webkit-box-orient: vertical;
|
|
25
|
+
-webkit-line-clamp: 2;
|
|
26
|
+
color: var(--color-text-primary);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.categoryLinkRoot {
|
|
30
|
+
padding-left: 1.5rem;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Root category container with full-height border */
|
|
34
|
+
.rootCategoryContainer {
|
|
35
|
+
position: relative;
|
|
36
|
+
margin-left: 1rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Circle outline at the top */
|
|
40
|
+
.rootCategoryContainer::before {
|
|
41
|
+
content: '';
|
|
42
|
+
position: absolute;
|
|
43
|
+
left: calc(0.5rem - 4px);
|
|
44
|
+
top: 0.85rem;
|
|
45
|
+
width: 8px;
|
|
46
|
+
height: 8px;
|
|
47
|
+
border-radius: 50%;
|
|
48
|
+
border: 1px solid var(--ifm-toc-border-color);
|
|
49
|
+
background-color: var(--ifm-background-color);
|
|
50
|
+
z-index: 2;
|
|
51
|
+
pointer-events: none;
|
|
52
|
+
transition: border-color 0.15s ease, background-color 0.15s ease;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Circle outline turns blue on hover */
|
|
56
|
+
.rootCategoryContainer:has(> :global(.menu__list-item-collapsible) > :global(.menu__link):hover)::before {
|
|
57
|
+
border-color: var(--ifm-color-primary-light);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Circle outline turns blue when category has active item */
|
|
61
|
+
.rootCategoryContainer:has(:global(.menu__link--active))::before {
|
|
62
|
+
border-color: var(--ifm-color-primary-light);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* Vertical line starting from the circle */
|
|
66
|
+
.rootCategoryContainer::after {
|
|
67
|
+
content: '';
|
|
68
|
+
position: absolute;
|
|
69
|
+
left: 0.5rem;
|
|
70
|
+
top: calc(0.85rem + 8px);
|
|
71
|
+
bottom: 0;
|
|
72
|
+
width: 1px;
|
|
73
|
+
background-color: var(--ifm-toc-border-color);
|
|
74
|
+
z-index: 1;
|
|
75
|
+
pointer-events: none;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.categoryLinkLabelRoot {
|
|
79
|
+
font-size: 1rem;
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.categoryLinkLabelNested {
|
|
84
|
+
font-size: 0.85rem;
|
|
85
|
+
font-weight: 500;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.categoryList {
|
|
89
|
+
position: relative;
|
|
90
|
+
margin-left: 0;
|
|
91
|
+
padding-left: 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* Reset margin for nested lists inside root category */
|
|
95
|
+
.categoryList :global(.menu__list) {
|
|
96
|
+
margin-left: 0;
|
|
97
|
+
padding-left: 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* Border indicator on nested items only (not on root category link itself) */
|
|
101
|
+
.rootCategoryContainer .categoryList :global(.menu__link) {
|
|
102
|
+
position: relative;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.rootCategoryContainer .categoryList :global(.menu__link)::before {
|
|
106
|
+
content: '';
|
|
107
|
+
position: absolute;
|
|
108
|
+
left: 0.5rem;
|
|
109
|
+
top: 0;
|
|
110
|
+
bottom: 0;
|
|
111
|
+
width: 2px;
|
|
112
|
+
background-color: transparent;
|
|
113
|
+
z-index: 2;
|
|
114
|
+
transition: background-color 0.15s ease;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.rootCategoryContainer .categoryList :global(.menu__link:hover)::before {
|
|
118
|
+
background-color: var(--ifm-color-primary-light);
|
|
119
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import React, { type ReactNode } from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import { ThemeClassNames } from '@docusaurus/theme-common';
|
|
4
|
+
import { isActiveSidebarItem } from '@docusaurus/plugin-content-docs/client';
|
|
5
|
+
import Link from '@docusaurus/Link';
|
|
6
|
+
import isInternalUrl from '@docusaurus/isInternalUrl';
|
|
7
|
+
import IconExternalLink from '@theme/Icon/ExternalLink';
|
|
8
|
+
import type { Props } from '@theme/DocSidebarItem/Link';
|
|
9
|
+
import ProBadge from '@/components/ProBadge/ProBadge';
|
|
10
|
+
import StatusBadge from '@/components/StatusBadge/StatusBadge';
|
|
11
|
+
import type { WithCustomProps } from '@/types/sidebar';
|
|
12
|
+
|
|
13
|
+
import styles from './styles.module.css';
|
|
14
|
+
|
|
15
|
+
function LinkLabel({ label }: { label: string }) {
|
|
16
|
+
return (
|
|
17
|
+
<span title={label} className={styles.linkLabel}>
|
|
18
|
+
{label}
|
|
19
|
+
</span>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default function DocSidebarItemLink({
|
|
24
|
+
item,
|
|
25
|
+
onItemClick,
|
|
26
|
+
activePath,
|
|
27
|
+
level,
|
|
28
|
+
index: _index,
|
|
29
|
+
...props
|
|
30
|
+
}: WithCustomProps<Props>): ReactNode {
|
|
31
|
+
const { href, label, className, autoAddBaseUrl, customProps } = item;
|
|
32
|
+
const isActive = isActiveSidebarItem(item, activePath);
|
|
33
|
+
const isInternalLink = isInternalUrl(href);
|
|
34
|
+
|
|
35
|
+
const isLink = item.type === 'link';
|
|
36
|
+
|
|
37
|
+
// Dynamic indent for nested items (level 3+)
|
|
38
|
+
const nestedIndent = level >= 3 ? (level - 2) * 0.05 : 0;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<li
|
|
42
|
+
className={clsx(
|
|
43
|
+
ThemeClassNames.docs.docSidebarItemLink,
|
|
44
|
+
ThemeClassNames.docs.docSidebarItemLinkLevel(level),
|
|
45
|
+
'menu__list-item',
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
key={label}
|
|
49
|
+
>
|
|
50
|
+
<Link
|
|
51
|
+
className={clsx(
|
|
52
|
+
'menu__link',
|
|
53
|
+
styles.menuLink,
|
|
54
|
+
!isInternalLink && styles.menuExternalLink,
|
|
55
|
+
{
|
|
56
|
+
'menu__link--active': isActive,
|
|
57
|
+
}
|
|
58
|
+
)}
|
|
59
|
+
style={
|
|
60
|
+
nestedIndent > 0
|
|
61
|
+
? { paddingLeft: `${nestedIndent + 2.5}rem` }
|
|
62
|
+
: undefined
|
|
63
|
+
}
|
|
64
|
+
autoAddBaseUrl={autoAddBaseUrl}
|
|
65
|
+
aria-current={isActive ? 'page' : undefined}
|
|
66
|
+
to={href}
|
|
67
|
+
{...(isInternalLink && {
|
|
68
|
+
onClick: onItemClick ? () => onItemClick(item) : undefined,
|
|
69
|
+
})}
|
|
70
|
+
{...props}
|
|
71
|
+
>
|
|
72
|
+
<LinkLabel label={label} />
|
|
73
|
+
{!isInternalLink && <IconExternalLink />}
|
|
74
|
+
{isLink && (
|
|
75
|
+
<>
|
|
76
|
+
{customProps?.plan === 'pro' && <ProBadge />}
|
|
77
|
+
{customProps?.badgeType && (
|
|
78
|
+
<StatusBadge type={customProps.badgeType} />
|
|
79
|
+
)}
|
|
80
|
+
</>
|
|
81
|
+
)}
|
|
82
|
+
</Link>
|
|
83
|
+
</li>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
.menuExternalLink {
|
|
2
|
+
align-items: center;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.menuLink {
|
|
6
|
+
padding-left: 2rem;
|
|
7
|
+
padding-top: 0.25rem;
|
|
8
|
+
padding-bottom: 0.25rem;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.linkLabel {
|
|
12
|
+
overflow: hidden;
|
|
13
|
+
display: -webkit-box;
|
|
14
|
+
line-clamp: 2;
|
|
15
|
+
-webkit-box-orient: vertical;
|
|
16
|
+
-webkit-line-clamp: 2;
|
|
17
|
+
font-size: 0.8rem;
|
|
18
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, {type ReactNode} from 'react';
|
|
2
|
+
import clsx from 'clsx';
|
|
3
|
+
import TOCItems from '@theme/TOCItems';
|
|
4
|
+
import type {Props} from '@theme/TOC';
|
|
5
|
+
|
|
6
|
+
import styles from './styles.module.css';
|
|
7
|
+
import TWGBadge from '@/components/TWGBadge/TWGBadge';
|
|
8
|
+
|
|
9
|
+
// Using a custom className
|
|
10
|
+
// This prevents TOCInline/TOCCollapsible getting highlighted by mistake
|
|
11
|
+
const LINK_CLASS_NAME = 'table-of-contents__link toc-highlight';
|
|
12
|
+
const LINK_ACTIVE_CLASS_NAME = 'table-of-contents__link--active';
|
|
13
|
+
|
|
14
|
+
export default function TOC({className, ...props}: Props): ReactNode {
|
|
15
|
+
return (
|
|
16
|
+
<div className={clsx(styles.tableOfContents, 'thin-scrollbar', className)}>
|
|
17
|
+
<TOCItems
|
|
18
|
+
{...props}
|
|
19
|
+
linkClassName={LINK_CLASS_NAME}
|
|
20
|
+
linkActiveClassName={LINK_ACTIVE_CLASS_NAME}
|
|
21
|
+
/>
|
|
22
|
+
<TWGBadge visibleOnLarge={true} />
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.tableOfContents {
|
|
2
|
+
max-height: calc(100vh - (var(--ifm-navbar-height) + 2rem));
|
|
3
|
+
overflow-y: auto;
|
|
4
|
+
position: sticky;
|
|
5
|
+
top: calc(var(--ifm-navbar-height) + 1rem);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@media (max-width: 996px) {
|
|
9
|
+
.tableOfContents {
|
|
10
|
+
display: none;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.docItemContainer {
|
|
14
|
+
padding: 0 0.3rem;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { SidebarsConfig } from '@docusaurus/plugin-content-docs';
|
|
2
|
+
|
|
3
|
+
export interface CustomSidebarProps {
|
|
4
|
+
plan?: 'pro';
|
|
5
|
+
badgeType?: 'planned' | 'new';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type AddCustomProps<T> = T extends readonly (infer U)[]
|
|
9
|
+
? Array<AddCustomProps<U>>
|
|
10
|
+
: T extends object
|
|
11
|
+
? Omit<{ [K in keyof T]: AddCustomProps<T[K]> }, 'customProps'> & {
|
|
12
|
+
customProps?: CustomSidebarProps;
|
|
13
|
+
}
|
|
14
|
+
: T;
|
|
15
|
+
|
|
16
|
+
export type TypedSidebarsConfig = AddCustomProps<SidebarsConfig>;
|
|
17
|
+
|
|
18
|
+
export type WithCustomProps<Props extends { item: unknown }> = Omit<
|
|
19
|
+
Props,
|
|
20
|
+
'item'
|
|
21
|
+
> & {
|
|
22
|
+
item: AddCustomProps<Props['item']> & Props['item'];
|
|
23
|
+
};
|