Skip to content

Commit

Permalink
feat: NavTabList component (#1698)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
mattrunyon authored Jan 4, 2024
1 parent a25095e commit 96641fb
Show file tree
Hide file tree
Showing 14 changed files with 852 additions and 252 deletions.
239 changes: 0 additions & 239 deletions packages/code-studio/src/main/AppMainContainer.scss
Original file line number Diff line number Diff line change
@@ -1,253 +1,14 @@
@import '@deephaven/components/scss/custom.scss';

$tab-height: 32px;
$tab-drag-border-width: 1px;
$tab-font-size: 1rem;
$tab-link-max-width: 200px;

$tab-link-side-padding: 24px;
$tab-link-underline-spacing: 6px;

$tab-close-right: 0.25rem;
$tab-close-bottom: 6px;
$tab-close-padding-x: 1px;
$tab-close-padding-y: 2px;
$tab-close-color: $gray-500;
$tab-close-hover-color: $gray-200;

$tab-button-side-padding: 9px;
$tab-button-separator-height: 16px;

$tab-link-color: $gray-400;

$tab-link-hover-color: $gray-300;
$tab-link-hover-underline-color: $gray-400;

$tab-link-active-color: $gray-200;
$tab-link-active-underline-color: $primary;

$tab-link-active-hover-color: $gray-200;
$tab-link-active-hover-underline-color: $primary;

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

$tab-button-hover-color: $gray-200;
$tab-button-separator-color: $gray-600;

$tab-dragging-bg-color: $primary-dark;
$tab-dragging-ant-color: $gray-300;

$tab-control-btn-width: 25px;
$tab-control-btn-offset: -8px;
$tab-control-gradient-width: 12px;

$nav-space: 4px; // give a gap around some buttons for focus area that are in nav bar

@mixin underlined-nav-link($pseudo-element, $underline-color) {
&::#{$pseudo-element} {
content: '';
position: absolute;
height: 1px;
left: $tab-link-side-padding;
right: $tab-link-side-padding;
bottom: $tab-link-underline-spacing;
background: $underline-color;
transition: all $transition-mid ease-out;
@content;
}
}

.nav-container {
display: flex;
flex-shrink: 0;

.nav-tabs {
border: none;
height: $tab-height;
font-size: $tab-font-size;
flex-wrap: nowrap;
overflow-x: hidden;
position: relative;

&.dragging {
@include ants-base($tab-dragging-ant-color, $background);
}

.btn-nav-tab {
color: $tab-link-color;
border: $tab-drag-border-width solid transparent;
line-height: $tab-height - $tab-drag-border-width * 2; // subtract top and bottom borders, and focus border
width: auto;
max-width: $tab-link-max-width;
overflow: hidden;
padding: 0 $tab-link-side-padding;
position: relative;
text-overflow: ellipsis;
user-select: none;
white-space: nowrap;
flex-shrink: 0;
background: none;
background-clip: padding-box;

.btn-nav-tab-close {
position: absolute;
line-height: $tab-font-size;
right: $tab-close-right;
bottom: $tab-close-bottom;
padding: $tab-close-padding-y $tab-close-padding-x;
color: $tab-close-color;
opacity: 0;
transition: opacity $transition ease-out;

&:hover {
color: $tab-button-hover-color;
}

&:focus {
opacity: 1;
color: $tab-button-hover-color;
}
}

//hover line is drawn as a before element
@include underlined-nav-link(before, transparent) {
transform: translateY($tab-link-underline-spacing);
}

//active is drawn animated overtop as after element
@include underlined-nav-link(after, $tab-link-active-underline-color) {
transform: scaleX(0);
}

&:focus {
// these seem like something that shouldn't have a regular focus state
box-shadow: none;
border-color: transparent;
&::before {
box-shadow: 0 1px 0 1px $input-btn-focus-color;
}
}

&:hover,
&:focus {
color: $tab-link-hover-color;
text-decoration: none;

.btn-nav-tab-close {
opacity: 1;
}

&::before {
background: $tab-link-hover-underline-color;
transform: translateY(0);
}
}

&.active {
color: $tab-link-active-color;

.btn-nav-tab-close {
opacity: 1;
}

&::after {
background: $tab-link-active-underline-color;
transform: scaleX(1);
}
&::before {
transform: translateY(0);
}
}

&.dragging {
color: $tab-link-active-color;
background-color: $tab-dragging-bg-color;

.btn-nav-tab-close {
opacity: 0;
}

&::before {
box-shadow: none;
}

&::after {
background: $tab-dragging-bg-color;
}
}
}
}

.tab-controls-backward {
flex-shrink: 0;
background-image: linear-gradient(
270deg,
hsla(var(--dh-color-bg-hsl), 0) 0%,
$background $tab-control-gradient-width
);
background-clip: content-box;
height: auto;
width: $tab-control-btn-width;
padding: 0;
margin-right: $tab-control-btn-offset;
border-radius: $border-radius;
border: 0;
min-width: unset;
z-index: 2;
}

.tab-controls-forward {
background-image: linear-gradient(
90deg,
hsla(var(--dh-color-bg-hsl), 0) 0%,
$background $tab-control-gradient-width
);
background-clip: content-box;
height: 100%;
border-radius: $border-radius;
width: $tab-control-btn-width;
padding: 0;
margin-left: $tab-control-btn-offset;
}

.tab-controls {
margin-right: auto;
white-space: nowrap;
z-index: 2;

.btn {
min-width: unset;
height: $tab-height;
}

.btn-new-tab {
min-width: auto;
padding: 0 $tab-button-side-padding;
position: relative;
white-space: nowrap;
height: $tab-height - $nav-space;
line-height: $tab-height - $nav-space - $input-border-width * 2;
margin: $nav-space * 0.5 0 $nav-space * 0.5 $tab-button-side-padding;

&::before {
content: '';
position: absolute;
left: -$tab-button-side-padding;
width: 1px;
top: ($tab-height - $tab-button-separator-height) * 0.5 - $nav-space *
0.5 - $input-border-width;
height: $tab-button-separator-height;
background: $tab-button-separator-color;
}
&:hover,
&:focus {
color: $tab-button-hover-color;
text-decoration: none;
}
}
}
}

.grid-cursor-copy {
cursor:
url('../assets/svg/cursor-copy.svg') 8 8,
Expand Down
8 changes: 0 additions & 8 deletions packages/code-studio/src/styleguide/Navigations.scss

This file was deleted.

89 changes: 88 additions & 1 deletion packages/code-studio/src/styleguide/Navigations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,90 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { vsFile, dhTruck, vsListUnordered } from '@deephaven/icons';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { Menu, Page, Stack } from '@deephaven/components';
import {
Menu,
NavTabList,
Page,
Stack,
type NavTabItem,
} from '@deephaven/components';
import { pseudoRandomWithSeed, sampleSectionIdAndClasses } from './utils';

function NavTabListExample({
count = 5,
activeKey: activeKeyProp = '',
}: {
count?: number;
activeKey?: string;
}) {
const [activeKey, setActiveKey] = useState(activeKeyProp);
const [tabs, setTabs] = useState(() => {
const tabItems: NavTabItem[] = [];
for (let i = 0; i < count; i += 1) {
tabItems.push({ key: `${i}`, title: `Tab ${i}`, isClosable: i > 0 });
}
return tabItems;
});

const handleReorder = useCallback((from: number, to: number) => {
setTabs(t => {
const newTabs = [...t];
const [removed] = newTabs.splice(from, 1);
newTabs.splice(to, 0, removed);
return newTabs;
});
}, []);

const handleSelect = useCallback((key: string) => {
setActiveKey(key);
}, []);

const handleClose = useCallback((key: string) => {
setTabs(t => t.filter(tab => tab.key !== key));
}, []);

const makeContextActions = useCallback(
(tab: NavTabItem) => [
{
title: 'Select Tab to the Left',
group: 10,
order: 10,
disabled: tabs[0].key === tab.key,
action: () => {
const index = tabs.findIndex(t => t.key === tab.key);
if (index > 0) {
setActiveKey(tabs[index - 1].key);
}
},
},
{
title: 'Select Tab to the Right',
group: 30,
order: 10,
disabled: tabs[tabs.length - 1].key === tab.key,
action: () => {
const index = tabs.findIndex(t => t.key === tab.key);
if (index < tabs.length - 1) {
setActiveKey(tabs[index + 1].key);
}
},
},
],
[tabs]
);

return (
<NavTabList
tabs={tabs}
activeKey={activeKey}
onSelect={handleSelect}
onReorder={handleReorder}
onClose={handleClose}
makeContextActions={makeContextActions}
/>
);
}

enum MENU_ITEM_TYPE {
SUBMENU = 'SUBMENU',
PAGE = 'PAGE',
Expand Down Expand Up @@ -208,6 +289,12 @@ function Navigations(): JSX.Element {
return (
<div {...sampleSectionIdAndClasses('navigations')}>
<h2 className="ui-title">Navigations</h2>
<div style={{ marginBottom: '1rem' }}>
<NavTabListExample count={100} activeKey="15" />
</div>
<div style={{ marginBottom: '1rem' }}>
<NavTabListExample />
</div>
<div className="navigations">
<Stack>{stack}</Stack>
</div>
Expand Down
3 changes: 3 additions & 0 deletions packages/code-studio/src/styleguide/StyleGuide.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { dh } from '@deephaven/jsapi-shim';
import { ApiContext } from '@deephaven/jsapi-bootstrap';
import StyleGuide from './StyleGuide';

window.HTMLElement.prototype.scroll = jest.fn();
window.HTMLElement.prototype.scrollIntoView = jest.fn();

describe('<StyleGuide /> mounts', () => {
test('h1 text of StyleGuide renders', () => {
// Provide a non-null array to ThemeProvider to tell it to initialize
Expand Down
2 changes: 1 addition & 1 deletion packages/components/scss/BaseStyleSheet.scss
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ button:focus {
}

span.btn-disabled-wrapper {
display: inline-block;
display: contents;
.btn.disabled,
.btn:disabled {
pointer-events: none;
Expand Down
2 changes: 1 addition & 1 deletion packages/components/src/context-actions/ContextActions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import './ContextActions.scss';
const log = Log.module('ContextActions');

interface ContextActionsProps {
actions: ResolvableContextAction | ResolvableContextAction[];
actions?: ResolvableContextAction | ResolvableContextAction[];
ignoreClassNames?: string[];
'data-testid'?: string;
}
Expand Down
Loading

0 comments on commit 96641fb

Please sign in to comment.