Skip to content

Commit f16e27d

Browse files
committed
feat(ui): Enhance SourceSwitcher component design
Improves the visual presentation and usability of the source selection dropdown menu. - Aligns the dropdown menu correctly under the trigger button. - Implements a tree-like structure with connecting lines to clearly represent the data source hierarchy. - Adds a visual highlight to the currently selected item in the list.
1 parent cb8cb30 commit f16e27d

File tree

4 files changed

+118
-29
lines changed

4 files changed

+118
-29
lines changed

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
"@radix-ui/react-dropdown-menu": "^2",
7676
"@radix-ui/react-icons": "^1.3.0",
7777
"@radix-ui/react-tooltip": "^1",
78+
"clsx": "^2.1.1",
7879
"csv-parse": "^5.6.0",
7980
"escape-string-regexp": "^5",
8081
"fast-glob": "^3",

src/theme/ResistanceTable/styles.module.css

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,12 +112,59 @@
112112
padding: 0.5rem 1rem;
113113
cursor: pointer;
114114
color: var(--rt-text-color);
115+
position: relative;
116+
padding-left: calc(1rem + (var(--level, 0) * 1.5rem));
117+
outline: none; /* Radix adds a default outline */
115118
}
116119

117-
.sourceSwitcherItem:hover {
120+
.sourceSwitcherItem[data-highlighted] {
118121
background-color: var(--rt-subtle-background-hover);
119122
}
120123

124+
.sourceSwitcherItemSelected {
125+
font-weight: 600;
126+
color: var(--ifm-color-primary);
127+
}
128+
129+
.sourceSwitcherItemContainer {
130+
position: relative;
131+
}
132+
133+
/* Vertical line for all indented items */
134+
.sourceSwitcherItemIndented::before {
135+
content: '';
136+
position: absolute;
137+
left: calc(1rem + (var(--level, 1) - 1) * 1.5rem);
138+
top: 0;
139+
bottom: 0;
140+
width: 1px;
141+
background-color: var(--rt-border-color);
142+
z-index: 1;
143+
}
144+
145+
/* Horizontal line (elbow) for all indented items */
146+
.sourceSwitcherItemIndented::after {
147+
content: '';
148+
position: absolute;
149+
left: calc(1rem + (var(--level, 1) - 1) * 1.5rem);
150+
top: 50%;
151+
height: 1px;
152+
width: 1rem; /* Connects the vertical line to the item */
153+
background-color: var(--rt-border-color);
154+
z-index: 1;
155+
}
156+
157+
/* For the last item in a group, the vertical line should only go halfway down */
158+
.sourceSwitcherItemIsLast::before {
159+
height: 50%;
160+
}
161+
162+
.sourceSwitcherItemInner {
163+
display: flex;
164+
align-items: center;
165+
gap: 0.5rem;
166+
}
167+
121168

122169
/* ============================================================================
123170
Tooltip

src/theme/ResistanceTable/ui/components.jsx

Lines changed: 59 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,59 @@
11
import React from 'react';
22
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
33
import { ChevronDownIcon } from '@radix-ui/react-icons';
4+
import clsx from 'clsx';
45

56
// Recursive component to render the source tree
67
const SourceMenuItem = ({
78
source,
9+
selected,
810
onSelect,
911
styles,
1012
level = 0,
13+
isLast = false,
1114
locale,
12-
}) => (
13-
<>
14-
<DropdownMenu.Item
15-
key={source.id}
16-
className={styles.sourceSwitcherItem}
17-
style={{ paddingLeft: `${1 + level * 1.5}rem` }}
18-
onSelect={() => onSelect(source)}
19-
>
20-
{source[`name_${locale}`] || source.name_en || source.id}
21-
</DropdownMenu.Item>
22-
{source.children && source.children.length > 0 && (
23-
source.children.map(child => (
24-
<SourceMenuItem
25-
key={child.id}
26-
source={child}
27-
onSelect={onSelect}
28-
styles={styles}
29-
level={level + 1}
30-
locale={locale}
31-
/>
32-
))
33-
)}
34-
</>
35-
);
15+
}) => {
16+
const hasChildren = source.children && source.children.length > 0;
17+
const isSelected = selected?.id === source.id;
18+
19+
return (
20+
<div className={styles.sourceSwitcherItemContainer} role="presentation">
21+
<DropdownMenu.Item
22+
key={source.id}
23+
className={clsx(
24+
styles.sourceSwitcherItem,
25+
isSelected && styles.sourceSwitcherItemSelected,
26+
isLast && styles.sourceSwitcherItemIsLast,
27+
level > 0 && styles.sourceSwitcherItemIndented,
28+
)}
29+
style={{ '--level': level }}
30+
onSelect={() => onSelect(source)}
31+
role="menuitemradio"
32+
aria-checked={isSelected}
33+
>
34+
<span className={styles.sourceSwitcherItemInner}>
35+
<span className={styles.sourceSwitcherItemLabel}>{source[`name_${locale}`] || source.name_en || source.id}</span>
36+
</span>
37+
</DropdownMenu.Item>
38+
{hasChildren && (
39+
<div className={styles.sourceSwitcherSubMenu} role="group">
40+
{source.children.map((child, index) => (
41+
<SourceMenuItem
42+
key={child.id}
43+
source={child}
44+
selected={selected}
45+
onSelect={onSelect}
46+
styles={styles}
47+
level={level + 1}
48+
isLast={index === source.children.length - 1}
49+
locale={locale}
50+
/>
51+
))}
52+
</div>
53+
)}
54+
</div>
55+
);
56+
};
3657

3758
export const SourceSwitcher = ({
3859
sources,
@@ -58,10 +79,20 @@ export const SourceSwitcher = ({
5879
</button>
5980
</DropdownMenu.Trigger>
6081
<DropdownMenu.Portal>
61-
<DropdownMenu.Content className={styles.sourceSwitcherContent} sideOffset={5}>
62-
{sources.map((s) => (
63-
<SourceMenuItem key={s.id} source={s} onSelect={onSelect} styles={styles} locale={locale} />
64-
))}
82+
<DropdownMenu.Content className={styles.sourceSwitcherContent} sideOffset={-1} align="start">
83+
<DropdownMenu.RadioGroup value={selected?.id}>
84+
{sources.map((s, index) => (
85+
<SourceMenuItem
86+
key={s.id}
87+
source={s}
88+
selected={selected}
89+
onSelect={onSelect}
90+
styles={styles}
91+
isLast={index === sources.length - 1}
92+
locale={locale}
93+
/>
94+
))}
95+
</DropdownMenu.RadioGroup>
6596
</DropdownMenu.Content>
6697
</DropdownMenu.Portal>
6798
</DropdownMenu.Root>

0 commit comments

Comments
 (0)