Skip to content

Commit 96641fb

Browse files
authored
feat: NavTabList component (#1698)
This is ported from DHE and generalized a bit so it can be used in DHC and DHE. Also cleaned up some of the CSS
1 parent a25095e commit 96641fb

14 files changed

Lines changed: 852 additions & 252 deletions

File tree

packages/code-studio/src/main/AppMainContainer.scss

Lines changed: 0 additions & 239 deletions
Original file line numberDiff line numberDiff line change
@@ -1,253 +1,14 @@
11
@import '@deephaven/components/scss/custom.scss';
22

33
$tab-height: 32px;
4-
$tab-drag-border-width: 1px;
54
$tab-font-size: 1rem;
6-
$tab-link-max-width: 200px;
7-
8-
$tab-link-side-padding: 24px;
9-
$tab-link-underline-spacing: 6px;
10-
11-
$tab-close-right: 0.25rem;
12-
$tab-close-bottom: 6px;
13-
$tab-close-padding-x: 1px;
14-
$tab-close-padding-y: 2px;
15-
$tab-close-color: $gray-500;
16-
$tab-close-hover-color: $gray-200;
175

186
$tab-button-side-padding: 9px;
19-
$tab-button-separator-height: 16px;
20-
21-
$tab-link-color: $gray-400;
22-
23-
$tab-link-hover-color: $gray-300;
24-
$tab-link-hover-underline-color: $gray-400;
25-
26-
$tab-link-active-color: $gray-200;
27-
$tab-link-active-underline-color: $primary;
28-
29-
$tab-link-active-hover-color: $gray-200;
30-
$tab-link-active-hover-underline-color: $primary;
317

328
$tab-link-disabled-color: $gray-600;
339

34-
$tab-button-hover-color: $gray-200;
35-
$tab-button-separator-color: $gray-600;
36-
37-
$tab-dragging-bg-color: $primary-dark;
38-
$tab-dragging-ant-color: $gray-300;
39-
40-
$tab-control-btn-width: 25px;
41-
$tab-control-btn-offset: -8px;
42-
$tab-control-gradient-width: 12px;
43-
4410
$nav-space: 4px; // give a gap around some buttons for focus area that are in nav bar
4511

46-
@mixin underlined-nav-link($pseudo-element, $underline-color) {
47-
&::#{$pseudo-element} {
48-
content: '';
49-
position: absolute;
50-
height: 1px;
51-
left: $tab-link-side-padding;
52-
right: $tab-link-side-padding;
53-
bottom: $tab-link-underline-spacing;
54-
background: $underline-color;
55-
transition: all $transition-mid ease-out;
56-
@content;
57-
}
58-
}
59-
60-
.nav-container {
61-
display: flex;
62-
flex-shrink: 0;
63-
64-
.nav-tabs {
65-
border: none;
66-
height: $tab-height;
67-
font-size: $tab-font-size;
68-
flex-wrap: nowrap;
69-
overflow-x: hidden;
70-
position: relative;
71-
72-
&.dragging {
73-
@include ants-base($tab-dragging-ant-color, $background);
74-
}
75-
76-
.btn-nav-tab {
77-
color: $tab-link-color;
78-
border: $tab-drag-border-width solid transparent;
79-
line-height: $tab-height - $tab-drag-border-width * 2; // subtract top and bottom borders, and focus border
80-
width: auto;
81-
max-width: $tab-link-max-width;
82-
overflow: hidden;
83-
padding: 0 $tab-link-side-padding;
84-
position: relative;
85-
text-overflow: ellipsis;
86-
user-select: none;
87-
white-space: nowrap;
88-
flex-shrink: 0;
89-
background: none;
90-
background-clip: padding-box;
91-
92-
.btn-nav-tab-close {
93-
position: absolute;
94-
line-height: $tab-font-size;
95-
right: $tab-close-right;
96-
bottom: $tab-close-bottom;
97-
padding: $tab-close-padding-y $tab-close-padding-x;
98-
color: $tab-close-color;
99-
opacity: 0;
100-
transition: opacity $transition ease-out;
101-
102-
&:hover {
103-
color: $tab-button-hover-color;
104-
}
105-
106-
&:focus {
107-
opacity: 1;
108-
color: $tab-button-hover-color;
109-
}
110-
}
111-
112-
//hover line is drawn as a before element
113-
@include underlined-nav-link(before, transparent) {
114-
transform: translateY($tab-link-underline-spacing);
115-
}
116-
117-
//active is drawn animated overtop as after element
118-
@include underlined-nav-link(after, $tab-link-active-underline-color) {
119-
transform: scaleX(0);
120-
}
121-
122-
&:focus {
123-
// these seem like something that shouldn't have a regular focus state
124-
box-shadow: none;
125-
border-color: transparent;
126-
&::before {
127-
box-shadow: 0 1px 0 1px $input-btn-focus-color;
128-
}
129-
}
130-
131-
&:hover,
132-
&:focus {
133-
color: $tab-link-hover-color;
134-
text-decoration: none;
135-
136-
.btn-nav-tab-close {
137-
opacity: 1;
138-
}
139-
140-
&::before {
141-
background: $tab-link-hover-underline-color;
142-
transform: translateY(0);
143-
}
144-
}
145-
146-
&.active {
147-
color: $tab-link-active-color;
148-
149-
.btn-nav-tab-close {
150-
opacity: 1;
151-
}
152-
153-
&::after {
154-
background: $tab-link-active-underline-color;
155-
transform: scaleX(1);
156-
}
157-
&::before {
158-
transform: translateY(0);
159-
}
160-
}
161-
162-
&.dragging {
163-
color: $tab-link-active-color;
164-
background-color: $tab-dragging-bg-color;
165-
166-
.btn-nav-tab-close {
167-
opacity: 0;
168-
}
169-
170-
&::before {
171-
box-shadow: none;
172-
}
173-
174-
&::after {
175-
background: $tab-dragging-bg-color;
176-
}
177-
}
178-
}
179-
}
180-
181-
.tab-controls-backward {
182-
flex-shrink: 0;
183-
background-image: linear-gradient(
184-
270deg,
185-
hsla(var(--dh-color-bg-hsl), 0) 0%,
186-
$background $tab-control-gradient-width
187-
);
188-
background-clip: content-box;
189-
height: auto;
190-
width: $tab-control-btn-width;
191-
padding: 0;
192-
margin-right: $tab-control-btn-offset;
193-
border-radius: $border-radius;
194-
border: 0;
195-
min-width: unset;
196-
z-index: 2;
197-
}
198-
199-
.tab-controls-forward {
200-
background-image: linear-gradient(
201-
90deg,
202-
hsla(var(--dh-color-bg-hsl), 0) 0%,
203-
$background $tab-control-gradient-width
204-
);
205-
background-clip: content-box;
206-
height: 100%;
207-
border-radius: $border-radius;
208-
width: $tab-control-btn-width;
209-
padding: 0;
210-
margin-left: $tab-control-btn-offset;
211-
}
212-
213-
.tab-controls {
214-
margin-right: auto;
215-
white-space: nowrap;
216-
z-index: 2;
217-
218-
.btn {
219-
min-width: unset;
220-
height: $tab-height;
221-
}
222-
223-
.btn-new-tab {
224-
min-width: auto;
225-
padding: 0 $tab-button-side-padding;
226-
position: relative;
227-
white-space: nowrap;
228-
height: $tab-height - $nav-space;
229-
line-height: $tab-height - $nav-space - $input-border-width * 2;
230-
margin: $nav-space * 0.5 0 $nav-space * 0.5 $tab-button-side-padding;
231-
232-
&::before {
233-
content: '';
234-
position: absolute;
235-
left: -$tab-button-side-padding;
236-
width: 1px;
237-
top: ($tab-height - $tab-button-separator-height) * 0.5 - $nav-space *
238-
0.5 - $input-border-width;
239-
height: $tab-button-separator-height;
240-
background: $tab-button-separator-color;
241-
}
242-
&:hover,
243-
&:focus {
244-
color: $tab-button-hover-color;
245-
text-decoration: none;
246-
}
247-
}
248-
}
249-
}
250-
25112
.grid-cursor-copy {
25213
cursor:
25314
url('../assets/svg/cursor-copy.svg') 8 8,

packages/code-studio/src/styleguide/Navigations.scss

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

packages/code-studio/src/styleguide/Navigations.tsx

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,90 @@
22
import React, { useCallback, useEffect, useMemo, useState } from 'react';
33
import { vsFile, dhTruck, vsListUnordered } from '@deephaven/icons';
44
import { IconProp } from '@fortawesome/fontawesome-svg-core';
5-
import { Menu, Page, Stack } from '@deephaven/components';
5+
import {
6+
Menu,
7+
NavTabList,
8+
Page,
9+
Stack,
10+
type NavTabItem,
11+
} from '@deephaven/components';
612
import { pseudoRandomWithSeed, sampleSectionIdAndClasses } from './utils';
713

14+
function NavTabListExample({
15+
count = 5,
16+
activeKey: activeKeyProp = '',
17+
}: {
18+
count?: number;
19+
activeKey?: string;
20+
}) {
21+
const [activeKey, setActiveKey] = useState(activeKeyProp);
22+
const [tabs, setTabs] = useState(() => {
23+
const tabItems: NavTabItem[] = [];
24+
for (let i = 0; i < count; i += 1) {
25+
tabItems.push({ key: `${i}`, title: `Tab ${i}`, isClosable: i > 0 });
26+
}
27+
return tabItems;
28+
});
29+
30+
const handleReorder = useCallback((from: number, to: number) => {
31+
setTabs(t => {
32+
const newTabs = [...t];
33+
const [removed] = newTabs.splice(from, 1);
34+
newTabs.splice(to, 0, removed);
35+
return newTabs;
36+
});
37+
}, []);
38+
39+
const handleSelect = useCallback((key: string) => {
40+
setActiveKey(key);
41+
}, []);
42+
43+
const handleClose = useCallback((key: string) => {
44+
setTabs(t => t.filter(tab => tab.key !== key));
45+
}, []);
46+
47+
const makeContextActions = useCallback(
48+
(tab: NavTabItem) => [
49+
{
50+
title: 'Select Tab to the Left',
51+
group: 10,
52+
order: 10,
53+
disabled: tabs[0].key === tab.key,
54+
action: () => {
55+
const index = tabs.findIndex(t => t.key === tab.key);
56+
if (index > 0) {
57+
setActiveKey(tabs[index - 1].key);
58+
}
59+
},
60+
},
61+
{
62+
title: 'Select Tab to the Right',
63+
group: 30,
64+
order: 10,
65+
disabled: tabs[tabs.length - 1].key === tab.key,
66+
action: () => {
67+
const index = tabs.findIndex(t => t.key === tab.key);
68+
if (index < tabs.length - 1) {
69+
setActiveKey(tabs[index + 1].key);
70+
}
71+
},
72+
},
73+
],
74+
[tabs]
75+
);
76+
77+
return (
78+
<NavTabList
79+
tabs={tabs}
80+
activeKey={activeKey}
81+
onSelect={handleSelect}
82+
onReorder={handleReorder}
83+
onClose={handleClose}
84+
makeContextActions={makeContextActions}
85+
/>
86+
);
87+
}
88+
889
enum MENU_ITEM_TYPE {
990
SUBMENU = 'SUBMENU',
1091
PAGE = 'PAGE',
@@ -208,6 +289,12 @@ function Navigations(): JSX.Element {
208289
return (
209290
<div {...sampleSectionIdAndClasses('navigations')}>
210291
<h2 className="ui-title">Navigations</h2>
292+
<div style={{ marginBottom: '1rem' }}>
293+
<NavTabListExample count={100} activeKey="15" />
294+
</div>
295+
<div style={{ marginBottom: '1rem' }}>
296+
<NavTabListExample />
297+
</div>
211298
<div className="navigations">
212299
<Stack>{stack}</Stack>
213300
</div>

packages/code-studio/src/styleguide/StyleGuide.test.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import { dh } from '@deephaven/jsapi-shim';
55
import { ApiContext } from '@deephaven/jsapi-bootstrap';
66
import StyleGuide from './StyleGuide';
77

8+
window.HTMLElement.prototype.scroll = jest.fn();
9+
window.HTMLElement.prototype.scrollIntoView = jest.fn();
10+
811
describe('<StyleGuide /> mounts', () => {
912
test('h1 text of StyleGuide renders', () => {
1013
// Provide a non-null array to ThemeProvider to tell it to initialize

packages/components/scss/BaseStyleSheet.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ button:focus {
139139
}
140140

141141
span.btn-disabled-wrapper {
142-
display: inline-block;
142+
display: contents;
143143
.btn.disabled,
144144
.btn:disabled {
145145
pointer-events: none;

packages/components/src/context-actions/ContextActions.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import './ContextActions.scss';
1414
const log = Log.module('ContextActions');
1515

1616
interface ContextActionsProps {
17-
actions: ResolvableContextAction | ResolvableContextAction[];
17+
actions?: ResolvableContextAction | ResolvableContextAction[];
1818
ignoreClassNames?: string[];
1919
'data-testid'?: string;
2020
}

0 commit comments

Comments
 (0)