Skip to content

Commit ac25029

Browse files
TreeView: Align tree item toggle and visual icons to top of item (#4572)
* Pin tree item toggle icon to top * Create rich-pans-press.md * Fix horizontal centering * center instead of sace-around * Add story * Top align visual icons in multiline treeview items * Update patch message * Fix comment * Revert return * test(vrt): update snapshots * Remove unneccesary align * test(vrt): update snapshots --------- Co-authored-by: Stephanie Hong <[email protected]> Co-authored-by: JelloBagel <[email protected]>
1 parent c5f7940 commit ac25029

File tree

48 files changed

+111
-5
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+111
-5
lines changed

.changeset/rich-pans-press.md

Lines changed: 5 additions & 0 deletions

packages/react/src/TreeView/TreeView.features.stories.tsx

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,4 +1040,88 @@ export const LeadingAction: Story = () => {
10401040
)
10411041
}
10421042

1043+
export const MultilineItems: Story = () => (
1044+
<nav aria-label="Files changed">
1045+
<TreeView aria-label="Files changed">
1046+
<TreeView.Item id="src" defaultExpanded>
1047+
<TreeView.LeadingVisual>
1048+
<TreeView.DirectoryIcon />
1049+
</TreeView.LeadingVisual>
1050+
<div style={{whiteSpace: 'wrap'}}>
1051+
this is a very long directory name that we have intentionally allowed to wrap over multiple lines to
1052+
demonstrate alignment
1053+
</div>
1054+
<TreeView.SubTree>
1055+
<TreeView.Item id="src/Avatar.tsx">
1056+
<TreeView.LeadingVisual>
1057+
<FileIcon />
1058+
</TreeView.LeadingVisual>
1059+
Avatar.tsx
1060+
<TreeView.TrailingVisual>
1061+
<Octicon icon={DiffAddedIcon} color="success.fg" aria-label="Added" />
1062+
</TreeView.TrailingVisual>
1063+
</TreeView.Item>
1064+
</TreeView.SubTree>
1065+
</TreeView.Item>
1066+
<TreeView.Item id="src" defaultExpanded>
1067+
<TreeView.LeadingVisual>
1068+
<TreeView.DirectoryIcon />
1069+
</TreeView.LeadingVisual>
1070+
<div style={{whiteSpace: 'wrap'}}>
1071+
this is a medium directory name that we wrap over 2 lines to demonstrate alignment
1072+
</div>
1073+
<TreeView.TrailingVisual>
1074+
<Octicon icon={DiffAddedIcon} color="success.fg" aria-label="Added" />
1075+
</TreeView.TrailingVisual>
1076+
<TreeView.SubTree>
1077+
<TreeView.Item id="src/Avatar.tsx">
1078+
<TreeView.LeadingVisual>
1079+
<FileIcon />
1080+
</TreeView.LeadingVisual>
1081+
Avatar.tsx
1082+
<TreeView.TrailingVisual>
1083+
<Octicon icon={DiffAddedIcon} color="success.fg" aria-label="Added" />
1084+
</TreeView.TrailingVisual>
1085+
</TreeView.Item>
1086+
</TreeView.SubTree>
1087+
</TreeView.Item>
1088+
<TreeView.Item id="src" defaultExpanded>
1089+
<TreeView.LeadingVisual>
1090+
<TreeView.DirectoryIcon />
1091+
</TreeView.LeadingVisual>
1092+
this is a very long directory name that we have intentionally NOT allowed to wrap over multiple lines to
1093+
demonstrate alignment
1094+
<TreeView.SubTree>
1095+
<TreeView.Item id="src/Avatar.tsx">
1096+
<TreeView.LeadingVisual>
1097+
<FileIcon />
1098+
</TreeView.LeadingVisual>
1099+
Avatar.tsx
1100+
<TreeView.TrailingVisual>
1101+
<Octicon icon={DiffAddedIcon} color="success.fg" aria-label="Added" />
1102+
</TreeView.TrailingVisual>
1103+
</TreeView.Item>
1104+
</TreeView.SubTree>
1105+
</TreeView.Item>
1106+
<TreeView.Item id="src" defaultExpanded>
1107+
<TreeView.LeadingVisual>
1108+
<TreeView.DirectoryIcon />
1109+
</TreeView.LeadingVisual>
1110+
short name
1111+
<TreeView.SubTree>
1112+
<TreeView.Item id="src/Avatar.tsx">
1113+
<TreeView.LeadingVisual>
1114+
<FileIcon />
1115+
</TreeView.LeadingVisual>
1116+
Avatar.tsx
1117+
<TreeView.TrailingVisual>
1118+
<Octicon icon={DiffAddedIcon} color="success.fg" aria-label="Added" />
1119+
</TreeView.TrailingVisual>
1120+
</TreeView.Item>
1121+
</TreeView.SubTree>
1122+
</TreeView.Item>
1123+
</TreeView>
1124+
</nav>
1125+
)
1126+
10431127
export default meta

packages/react/src/TreeView/TreeView.tsx

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ export type TreeViewProps = {
6767
className?: string
6868
}
6969

70+
/* Size of toggle icon in pixels. */
71+
const TOGGLE_ICON_SIZE = 12
72+
7073
const UlBox = styled.ul<SxProp>`
7174
list-style: none;
7275
padding: 0;
@@ -105,14 +108,14 @@ const UlBox = styled.ul<SxProp>`
105108
.PRIVATE_TreeView-item-container {
106109
--level: 1; /* default level */
107110
--toggle-width: 1rem; /* 16px */
111+
--min-item-height: 2rem; /* 32px */
108112
position: relative;
109113
display: grid;
110114
--leading-action-width: calc(var(--has-leading-action, 0) * 1.5rem);
111115
--spacer-width: calc(calc(var(--level) - 1) * (var(--toggle-width) / 2));
112116
grid-template-columns: var(--spacer-width) var(--leading-action-width) var(--toggle-width) 1fr;
113117
grid-template-areas: 'spacer leadingAction toggle content';
114118
width: 100%;
115-
min-height: 2rem; /* 32px */
116119
font-size: ${get('fontSizes.1')};
117120
color: ${get('colors.fg.default')};
118121
border-radius: ${get('radii.2')};
@@ -129,7 +132,7 @@ const UlBox = styled.ul<SxProp>`
129132
130133
@media (pointer: coarse) {
131134
--toggle-width: 1.5rem; /* 24px */
132-
min-height: 2.75rem; /* 44px */
135+
--min-item-height: 2.75rem; /* 44px */
133136
}
134137
135138
&:has(.PRIVATE_TreeView-item-skeleton):hover {
@@ -169,8 +172,11 @@ const UlBox = styled.ul<SxProp>`
169172
.PRIVATE_TreeView-item-toggle {
170173
grid-area: toggle;
171174
display: flex;
172-
align-items: center;
173175
justify-content: center;
176+
align-items: flex-start;
177+
/* The toggle should appear vertically centered for single-line items, but remain at the top for items that wrap
178+
across more lines. */
179+
padding-top: calc(var(--min-item-height) / 2 - ${TOGGLE_ICON_SIZE}px / 2);
174180
height: 100%;
175181
color: ${get('colors.fg.muted')};
176182
}
@@ -187,10 +193,13 @@ const UlBox = styled.ul<SxProp>`
187193
.PRIVATE_TreeView-item-content {
188194
grid-area: content;
189195
display: flex;
190-
align-items: center;
191196
height: 100%;
192197
padding: 0 ${get('space.2')};
193198
gap: ${get('space.2')};
199+
line-height: var(--custom-line-height, var(--text-body-lineHeight-medium, 1.4285));
200+
/* The dynamic top and bottom padding to maintain the minimum item height for single line items */
201+
padding-top: calc((var(--min-item-height) - var(--custom-line-height, 1.3rem)) / 2);
202+
padding-bottom: calc((var(--min-item-height) - var(--custom-line-height, 1.3rem)) / 2);
194203
}
195204
196205
.PRIVATE_TreeView-item-content-text {
@@ -204,7 +213,11 @@ const UlBox = styled.ul<SxProp>`
204213
205214
.PRIVATE_TreeView-item-visual {
206215
display: flex;
216+
align-items: center;
207217
color: ${get('colors.fg.muted')};
218+
/* The visual icons should appear vertically centered for single-line items, but remain at the top for items that wrap
219+
across more lines. */
220+
height: var(--custom-line-height, 1.3rem);
208221
}
209222
210223
.PRIVATE_TreeView-item-leading-action {
@@ -524,7 +537,11 @@ const Item = React.forwardRef<HTMLElement, TreeViewItemProps>(
524537
}
525538
}}
526539
>
527-
{isExpanded ? <ChevronDownIcon size={12} /> : <ChevronRightIcon size={12} />}
540+
{isExpanded ? (
541+
<ChevronDownIcon size={TOGGLE_ICON_SIZE} />
542+
) : (
543+
<ChevronRightIcon size={TOGGLE_ICON_SIZE} />
544+
)}
528545
</div>
529546
) : null}
530547
<div id={labelId} className="PRIVATE_TreeView-item-content">

0 commit comments

Comments
 (0)