Skip to content

Commit 533bda9

Browse files
authored
Make section headers sticky (#3465)
* Make section headers sticky * Self review * Try to reduce complexity * Do not toggle section while moving sliders and remove tick marks from sliders
1 parent 0ffbb0c commit 533bda9

File tree

11 files changed

+119
-142
lines changed

11 files changed

+119
-142
lines changed

extension/src/plots/webview/contract.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@ import { Color } from '../../experiments/model/status/colors'
44
export const DEFAULT_NB_ITEMS_PER_ROW = 2
55

66
export enum PlotHeight {
7-
SMALLER = 0,
8-
SMALL = 1,
9-
REGULAR = 2,
10-
SQUARE = 3,
11-
VERTICAL_NORMAL = 4,
12-
VERTICAL_LARGER = 5
7+
SMALLER,
8+
SMALL,
9+
REGULAR,
10+
SQUARE,
11+
VERTICAL_NORMAL,
12+
VERTICAL_LARGER
1313
}
1414

1515
export const DEFAULT_PLOT_HEIGHT = PlotHeight.SMALL

webview/src/plots/components/PlotsContainer.tsx

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,8 @@ import styles from './styles.module.scss'
1414
import { IconMenuItemProps } from '../../shared/components/iconMenu/IconMenuItem'
1515
import { sendMessage } from '../../shared/vscode'
1616
import { Lines, Add, Trash } from '../../shared/components/icons'
17-
import { MinMaxSlider } from '../../shared/components/slider/MinMaxSlider'
17+
import { Slider } from '../../shared/components/slider/Slider'
1818
import { PlotsState } from '../store'
19-
import { ItemsSlider } from '../../shared/components/slider/ItemsSlider'
2019
import { SectionContainer } from '../../shared/components/sectionContainer/SectionContainer'
2120

2221
export interface PlotsContainerProps {
@@ -114,6 +113,10 @@ export const PlotsContainer: React.FC<PlotsContainerProps> = ({
114113
type: MessageFromWebviewType.TOGGLE_PLOTS_SECTION
115114
})
116115

116+
const plotHeights = Object.values(PlotHeight).filter(
117+
value => typeof value !== 'string'
118+
) as number[]
119+
117120
return (
118121
<SectionContainer
119122
menuItems={menuItems}
@@ -129,34 +132,40 @@ export const PlotsContainer: React.FC<PlotsContainerProps> = ({
129132
[styles.ratioVerticalNormal]: height === PlotHeight.VERTICAL_NORMAL,
130133
[styles.ratioVerticalLarger]: height === PlotHeight.VERTICAL_LARGER
131134
})}
132-
>
133-
{changeSize && hasItems && maxNbPlotsPerRow > 1 && (
134-
<div
135-
className={styles.sizeSliders}
136-
style={{ top: ribbonHeight - 4 }}
137-
data-testid="size-sliders"
138-
>
139-
<div className={styles.sizeSlider}>
140-
<MinMaxSlider
141-
maximum={-1}
142-
minimum={-maxNbPlotsPerRow}
143-
label="Plot Width"
144-
onChange={nbItems => handleResize(nbItems, height)}
145-
defaultValue={-nbItemsPerRow}
146-
/>
147-
</div>
148-
<div className={styles.sizeSlider}>
149-
<ItemsSlider
150-
items={Object.values(PlotHeight) as number[]}
151-
label="Plot Height"
152-
onChange={newHeight =>
153-
handleResize(nbItemsPerRow, newHeight as unknown as PlotHeight)
154-
}
155-
defaultValue={height}
156-
/>
135+
stickyHeaderTop={ribbonHeight - 4}
136+
headerChildren={
137+
open &&
138+
changeSize &&
139+
hasItems &&
140+
maxNbPlotsPerRow > 1 && (
141+
<div className={styles.sizeSliders} data-testid="size-sliders">
142+
<div className={styles.sizeSlider}>
143+
<Slider
144+
maximum={-1}
145+
minimum={-maxNbPlotsPerRow}
146+
label="Plot Width"
147+
onChange={nbItems => handleResize(nbItems, height)}
148+
defaultValue={-nbItemsPerRow}
149+
/>
150+
</div>
151+
<div className={styles.sizeSlider}>
152+
<Slider
153+
minimum={Math.min(...plotHeights)}
154+
maximum={Math.max(...plotHeights)}
155+
label="Plot Height"
156+
onChange={newHeight =>
157+
handleResize(
158+
nbItemsPerRow,
159+
newHeight as unknown as PlotHeight
160+
)
161+
}
162+
defaultValue={height}
163+
/>
164+
</div>
157165
</div>
158-
</div>
159-
)}
166+
)
167+
}
168+
>
160169
{open && (
161170
<div
162171
className={cx({

webview/src/plots/components/styles.module.scss

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,19 +235,16 @@ $gap: 20px;
235235
}
236236

237237
.sizeSliders {
238-
margin: 20px;
239238
display: flex;
240239
justify-content: end;
241240
gap: 10px;
242-
position: sticky;
243241
right: 0;
244242
top: 0;
245-
width: 100%;
246243
display: flex;
247244
justify-content: end;
248-
padding: 20px;
249245
background-color: $bg-color;
250246
z-index: 3;
247+
justify-self: flex-end;
251248
}
252249

253250
.sizeSlider {

webview/src/shared/components/iconMenu/IconMenuItem.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react'
1+
import React, { MouseEvent, ReactNode } from 'react'
22
import cx from 'classnames'
33
import { TippyProps } from '@tippyjs/react'
44
import styles from './styles.module.scss'
@@ -8,7 +8,7 @@ import Tooltip from '../tooltip/Tooltip'
88
export interface IconMenuItemProps {
99
icon: IconValue
1010
onClick?: () => void
11-
onClickNode?: React.ReactNode
11+
onClickNode?: ReactNode
1212
tooltip: string
1313
hidden?: boolean
1414
}
@@ -26,12 +26,16 @@ export const IconMenuItem: React.FC<IconMenuItemAllProps> = ({
2626
tooltipTarget,
2727
menuTarget
2828
}) => {
29+
const handleOnClick = (e: MouseEvent<HTMLButtonElement>) => {
30+
e.stopPropagation()
31+
onClick?.()
32+
}
2933
let button = (
3034
<Tooltip content={tooltip} singleton={tooltipTarget}>
3135
<button
3236
aria-label={tooltip}
3337
className={cx(styles.item, { [styles.clickable]: !!onClick })}
34-
onClick={onClick}
38+
onClick={handleOnClick}
3539
data-testid="icon-menu-item"
3640
>
3741
<Icon icon={icon} data-testid="icon-menu-item-icon" width={15} />

webview/src/shared/components/iconMenu/styles.module.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
border: 1px solid var(--editor-foreground-transparency-5);
88
width: max-content;
99
background-color: $bg-color;
10+
margin: 0;
1011
}
1112

1213
.item {

webview/src/shared/components/sectionContainer/SectionContainer.tsx

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import cx from 'classnames'
2-
import React, { MouseEvent } from 'react'
2+
import React, { MouseEvent, ReactNode } from 'react'
33
import { Section as PlotsSection } from 'dvc/src/plots/webview/contract'
44
import {
55
STUDIO_URL,
@@ -84,11 +84,13 @@ export const SectionDescription = {
8484
export interface SectionContainerProps<T extends PlotsSection | SetupSection> {
8585
children: React.ReactNode
8686
menuItems?: IconMenuItemProps[]
87+
headerChildren?: ReactNode
8788
onToggleSection: () => void
8889
sectionCollapsed: boolean
8990
sectionKey: T
9091
title: string
9192
className?: string
93+
stickyHeaderTop?: number
9294
}
9395

9496
const InfoIcon = () => (
@@ -104,7 +106,9 @@ export const SectionContainer: React.FC<
104106
sectionCollapsed,
105107
sectionKey,
106108
title,
107-
className
109+
className,
110+
stickyHeaderTop = 0,
111+
headerChildren
108112
}) => {
109113
const open = !sectionCollapsed
110114

@@ -131,31 +135,40 @@ export const SectionContainer: React.FC<
131135
data-testid="section-container"
132136
>
133137
<details open={open} className={styles.sectionContainer}>
134-
<summary onClick={toggleSection}>
135-
<Icon
136-
icon={open ? ChevronDown : ChevronRight}
137-
data-testid="section-container-details-chevron"
138-
width={20}
139-
height={20}
140-
className={styles.detailsIcon}
141-
/>
142-
{title}
143-
<Tooltip content={tooltipContent} placement="bottom-end" interactive>
144-
<div
145-
className={styles.infoTooltipToggle}
146-
data-testid="info-tooltip-toggle"
138+
<summary onClick={toggleSection} style={{ top: stickyHeaderTop }}>
139+
<div className={styles.summaryTitle}>
140+
<Icon
141+
icon={open ? ChevronDown : ChevronRight}
142+
data-testid="section-container-details-chevron"
143+
width={20}
144+
height={20}
145+
className={styles.detailsIcon}
146+
/>
147+
{title}
148+
<Tooltip
149+
content={tooltipContent}
150+
placement="bottom-end"
151+
interactive
147152
>
148-
<InfoIcon />
153+
<div
154+
className={styles.infoTooltipToggle}
155+
data-testid="info-tooltip-toggle"
156+
>
157+
<InfoIcon />
158+
</div>
159+
</Tooltip>
160+
</div>
161+
162+
{headerChildren}
163+
164+
{menuItems.length > 0 && (
165+
<div className={styles.iconMenu}>
166+
<IconMenu items={menuItems} />
149167
</div>
150-
</Tooltip>
168+
)}
151169
</summary>
152170
{children}
153171
</details>
154-
{menuItems.length > 0 && (
155-
<div className={styles.iconMenu}>
156-
<IconMenu items={menuItems} />
157-
</div>
158-
)}
159172
</div>
160173
)
161174
}

webview/src/shared/components/sectionContainer/styles.module.scss

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,24 @@ $gap: 20px;
1313
summary {
1414
list-style-type: none;
1515
font-family: var(--vscode-font-family);
16-
margin: 14px 10px;
16+
padding: 14px 10px;
1717
font-weight: bold;
1818
font-size: 1.25rem;
1919
display: flex;
2020
align-items: center;
21-
width: max-content;
21+
width: 100%;
2222
cursor: pointer;
23+
position: sticky;
24+
background-color: $bg-color;
25+
z-index: 3;
26+
gap: $gap;
2327
}
2428
}
2529

26-
.iconMenu {
27-
position: absolute;
28-
right: $gap;
29-
top: -15px;
30-
z-index: 10;
30+
.summaryTitle {
31+
display: flex;
32+
align-items: center;
33+
flex-grow: 1;
3134
}
3235

3336
.detailsIcon {

webview/src/shared/components/slider/ItemsSlider.tsx

Lines changed: 0 additions & 32 deletions
This file was deleted.

webview/src/shared/components/slider/MinMaxSlider.tsx

Lines changed: 0 additions & 29 deletions
This file was deleted.

0 commit comments

Comments
 (0)