Skip to content
This repository was archived by the owner on Jul 30, 2025. It is now read-only.

Commit 8d7619d

Browse files
committed
feat: use Carbon Components UIShell for TopTabStripe
This PR switches TopTabStripe.tsx partially over to Carbon's UIShell. We still have to style the HeaderMenuItems ourselves to look like tabs, since carbon on its own does not do this. But this is only some CSS rules, not core component work. Fixes #3819
1 parent 328f505 commit 8d7619d

File tree

19 files changed

+455
-630
lines changed

19 files changed

+455
-630
lines changed

packages/core/src/webapp/cancel.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,6 @@ export default function doCancel(tab: Tab, block: Block) {
3232

3333
const execUUID = block.getAttribute('data-uuid')
3434
const endEvent = { tab, execType: ExecType.TopLevel, cancelled: true, execUUID }
35+
eventBus.emit('/command/complete', endEvent)
3536
eventBus.emit(`/command/complete/fromuser/${getTabId(tab)}`, endEvent)
3637
}

plugins/plugin-carbon-themes/web/css/carbon-gray10.css

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
@import "kui--alternate.css";
33

44
/* inverted sidecar */
5-
body[kui-theme="Carbon Gray10"] .kui--sidecar {
5+
body[kui-theme="Carbon Gray10"] .kui--sidecar,
6+
body[kui-theme="Carbon Gray10"] .bx--header .bx--side-nav {
67
--color-base00: #262626;
78
--color-base01: #171717;
89
--color-base02: #565656;
Lines changed: 3 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,95 +1,4 @@
1-
/* Alternate top tab stripe UI yoyo */
2-
3-
body.kui--alternate .kui-header__title {
4-
display: none;
5-
}
6-
body.kui--alternate .kui-header__title,
7-
body.kui--alternate .kui-tab,
8-
body.kui--alternate .kui-new-tab {
9-
color: var(--tab-gray-text-color);
10-
display: inline-flex;
11-
justify-content: center;
12-
align-items: center;
13-
cursor: pointer;
14-
}
15-
16-
body.kui--alternate .kui-header {
17-
width: 100%;
18-
height: 48px;
19-
flex-basis: 48px;
20-
display: flex;
21-
background-color: var(--color-stripe-01);
22-
padding: 0 1em 0 3em;
23-
font-weight: 600;
24-
box-shadow: 0 1px 10px 0 rgba(21, 41, 53, 0.1);
25-
z-index: 1;
26-
}
27-
28-
body.kui--alternate .kui-header__title {
29-
padding-right: 28px;
30-
}
31-
32-
body.kui--alternate .kui-tab {
33-
position: relative;
34-
border: none;
35-
background: transparent;
36-
}
37-
38-
body.kui--alternate .kui-tab--label {
39-
text-align: left;
40-
font-size: 0.875rem;
41-
font-weight: 600;
42-
padding: 0.5em 0 0;
43-
border: none !important;
44-
}
45-
body.kui--alternate .left-tab-stripe-buttons .left-tab-stripe-button-closer {
46-
transform: translateY(calc(-50% + 0.25em));
47-
}
48-
49-
body.kui--alternate .left-tab-stripe-button.processing .kui-tab--label {
50-
color: var(--active-tab-color);
51-
}
52-
53-
body.kui--alternate .kui-tab--label-index:before {
54-
content: counter(tab-index);
55-
}
56-
57-
body.kui--alternate .kui-tab--active {
58-
color: var(--active-tab-color);
59-
}
60-
61-
body.kui--alternate .kui-tab.left-tab-stripe-button-selected {
62-
margin-top: 0;
63-
}
64-
65-
body.kui--alternate .kui-tab::after {
66-
display: block;
67-
content: "";
68-
position: absolute;
69-
width: 100%;
70-
height: 2px;
71-
background-color: transparent;
72-
bottom: 0;
73-
transition: all 0.2s;
74-
pointer-events: none;
75-
}
76-
77-
body.kui--alternate .kui-tab:hover:after,
78-
body.kui--alternate .kui-tab--active:after {
79-
background-color: currentColor;
80-
}
81-
82-
body.kui--alternate #new-tab-button.kui-new-tab {
83-
padding-top: 0.5em;
84-
}
85-
86-
body.kui--alternate .kui-header a.kui--tab-navigatable {
87-
padding: 0.5em;
88-
}
89-
90-
body.kui--alternate .kui-new-tab__text {
91-
color: var(--tab-gray-text-color);
92-
}
1+
/* Alternate look for carbon themes */
932

943
body.kui--alternate .repl:not(.kui--input-stripe) .repl-block {
954
padding-left: 2em;
@@ -107,19 +16,6 @@ body.kui--alternate .repl-prompt-righty svg path {
10716
fill: var(--color-text-01);
10817
}
10918

110-
/* status indicators */
111-
body.kui--alternate .repl-block .repl-prompt-right-element-status-icon svg {
112-
transform: scale(0.875);
113-
filter: none;
114-
}
115-
body.kui--alternate .repl-block.valid-response .repl-prompt-right-element-status-icon svg.kui--icon-ok {
116-
display: none;
117-
}
118-
body.kui--alternate .repl .repl-prompt-right-element-status-icon.deemphasize {
119-
order: -1;
120-
margin-right: 0.5em;
121-
margin-left: 0;
122-
}
123-
body.kui--alternate .kui--repl-prompt-buttons {
124-
order: -2;
19+
body.kui--alternate .bx--header {
20+
font-size: 1em;
12521
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2020 IBM Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as React from 'react'
18+
import { i18n } from '@kui-shell/core'
19+
import { SideNavMenu, SideNavMenuItem, SideNav, SideNavItems } from 'carbon-components-react'
20+
21+
import '../../web/css/static/About.scss'
22+
import 'carbon-components/scss/components/ui-shell/_content.scss'
23+
import 'carbon-components/scss/components/ui-shell/_side-nav.scss'
24+
25+
const strings = i18n('client', 'about')
26+
27+
interface Props {
28+
expanded: boolean
29+
}
30+
31+
interface State {
32+
current: { nav: number; tab: number }
33+
}
34+
35+
export default class About extends React.PureComponent<Props, State> {
36+
public constructor(props: Props) {
37+
super(props)
38+
39+
this.state = {
40+
current: { nav: 0, tab: 0 }
41+
}
42+
}
43+
44+
private configure(menuIdx: number) {
45+
return (
46+
<SideNavMenu title={strings('Configure')} isActive defaultExpanded>
47+
<SideNavMenuItem
48+
href="#" // needed for tab navigation
49+
key={0}
50+
data-mode={'theme'} // needed for tests
51+
isActive={this.state.current.nav === menuIdx && this.state.current.tab === 0}
52+
onClick={() => {
53+
this.setState({ current: { nav: menuIdx, tab: 0 } })
54+
}}
55+
>
56+
<span className="kui--mode-placeholder" data-mode="theme">
57+
{strings('Theme')}
58+
</span>
59+
</SideNavMenuItem>
60+
</SideNavMenu>
61+
)
62+
}
63+
64+
public render() {
65+
return (
66+
<SideNav aria-label="Side navigation" {...this.props} isChildOfHeader isFixedNav>
67+
<SideNavItems>
68+
{/* this.state.allNavs.map((nav, idx) => this.renderSideNavMenu(idx)) */}
69+
{this.configure(0)}
70+
</SideNavItems>
71+
</SideNav>
72+
)
73+
}
74+
}

plugins/plugin-client-common/src/components/Screenshot.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,7 @@ export default class Screenshot extends React.PureComponent<Props, State> {
204204
direction="top"
205205
className="kui--screenshot-tooltip"
206206
showIcon
207+
tabIndex={-1}
207208
renderIcon={React.forwardRef(function screenshotIcon(props, ref) {
208209
return (
209210
<a

plugins/plugin-client-common/src/components/StatusStripe/index.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
/* eslint-disable react/prop-types */
1919

2020
import * as React from 'react'
21+
import { inElectron } from '@kui-shell/core'
2122
import { SettingsAdjust16 as SettingsIcon } from '@carbon/icons-react'
2223

2324
import Screenshot from '../Screenshot'
@@ -58,9 +59,7 @@ export default class StatusStripe extends React.PureComponent {
5859
<div className="kui--status-stripe" id="kui--status-stripe">
5960
{this.widgets()}
6061
<div className="kui--status-stripe-button">
61-
<div className="kui--status-stripe-element">
62-
<Screenshot />
63-
</div>
62+
<div className="kui--status-stripe-element">{inElectron() && <Screenshot />}</div>
6463
</div>
6564

6665
<div className="kui--status-stripe-button">
@@ -70,7 +69,7 @@ export default class StatusStripe extends React.PureComponent {
7069
className="kui--tab-navigatable clickable"
7170
id="help-button"
7271
aria-label="Help"
73-
tabIndex={2}
72+
tabIndex={0}
7473
onClick={() => this.doAbout()}
7574
>
7675
<SettingsIcon />

plugins/plugin-client-common/src/components/TabContainer.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,14 @@
1515
*/
1616

1717
import * as React from 'react'
18-
import { eventBus } from '@kui-shell/core'
18+
import { inElectron, eventBus } from '@kui-shell/core'
1919

20-
import Search from './Search'
2120
import TabModel from './TabModel'
2221
import TopTabStripe, { TopTabStripeConfiguration } from './TopTabStripe'
2322
import TabContent, { TabContentOptions } from './TabContent'
2423

24+
import '../../web/css/static/TabContainer.scss'
25+
2526
/**
2627
*
2728
* TabContainer
@@ -148,6 +149,8 @@ export default class TabContainer extends React.PureComponent<Props, State> {
148149
}
149150

150151
public render() {
152+
const Search = inElectron && React.lazy(() => import('./Search'))
153+
151154
return (
152155
<div className="kui--full-height">
153156
<TopTabStripe
@@ -158,7 +161,9 @@ export default class TabContainer extends React.PureComponent<Props, State> {
158161
onCloseTab={(idx: number) => this.onCloseTab(idx)}
159162
onSwitchTab={(idx: number) => this.onSwitchTab(idx)}
160163
/>
161-
<Search />
164+
<React.Suspense fallback={<div />}>
165+
<Search />
166+
</React.Suspense>
162167
<div className="tab-container">
163168
{this.state.tabs.map((_, idx) => (
164169
<TabContent key={idx} uuid={_.uuid} active={idx === this.state.activeIdx} state={_.state} {...this.props}>

plugins/plugin-client-common/src/components/TopTabStripe/NewTabButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ export default class NewTabButton extends React.PureComponent<Props> {
2626
return (
2727
<a
2828
href="#"
29-
className="kui--tab-navigatable kui-new-tab__plus left-tab-stripe-button smaller-button kui-new-tab"
29+
className="kui--tab-navigatable kui--new-tab__plus left-tab-stripe-button smaller-button kui-new-tab"
3030
id="new-tab-button"
3131
data-balloon-disabled="Open a new tab"
3232
data-balloon-pos="right"
3333
data-balloon-length="fit"
3434
aria-label="Open a new tab"
35-
tabIndex={2}
35+
tabIndex={0}
3636
onClick={() => this.props.onNewTab()}
3737
>
3838
<Add20 className="kui-new-tab__plus" focusable="false" preserveAspectRatio="xMidYMid meet" aria-hidden="true" />

plugins/plugin-client-common/src/components/TopTabStripe/Tab.tsx

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import * as React from 'react'
1818
import { Close16 } from '@carbon/icons-react'
1919
import { i18n, eventBus, Event, ExecType, Theme } from '@kui-shell/core'
20+
import { HeaderMenuItem } from 'carbon-components-react'
2021

2122
const strings = i18n('plugin-core-support')
2223

@@ -37,6 +38,7 @@ type Props = TabConfiguration & {
3738
interface State {
3839
title: string
3940
processing: boolean
41+
isFreshlyCreated: boolean
4042
topTabNames: 'command' | 'fixed'
4143
}
4244

@@ -49,8 +51,9 @@ export default class Tab extends React.PureComponent<Props, State> {
4951
super(props)
5052

5153
this.state = {
52-
title: this.props.productName,
54+
title: strings('Tab'),
5355
processing: false,
56+
isFreshlyCreated: true,
5457
topTabNames: props.topTabNames || 'command'
5558
}
5659

@@ -93,12 +96,12 @@ export default class Tab extends React.PureComponent<Props, State> {
9396
!event.route.match(/^\/(tab|getting\/started)/) // ignore our own events and help
9497
) {
9598
if (this.isUsingCommandName()) {
96-
this.setState({ processing: true, title: event.command || this.state.title })
99+
this.setState({ processing: true, title: event.command || this.state.title, isFreshlyCreated: false })
97100
return
98101
}
99102
}
100103

101-
this.setState({ processing: true })
104+
this.setState({ processing: true, isFreshlyCreated: false })
102105
}
103106
}
104107
}
@@ -120,16 +123,17 @@ export default class Tab extends React.PureComponent<Props, State> {
120123

121124
public render() {
122125
return (
123-
<a
126+
<HeaderMenuItem
124127
href="#"
128+
data-tab-names={this.state.topTabNames}
129+
data-fresh={this.state.isFreshlyCreated}
125130
className={
126-
'kui-tab left-tab-stripe-button kui--tab-navigatable' +
127-
(this.props.active ? ' kui-tab--active left-tab-stripe-button-selected' : '') +
131+
'kui--tab kui--tab-navigatable' +
132+
(this.props.active ? ' kui--tab--active' : '') +
128133
(this.state.processing ? ' processing' : '')
129134
}
130135
data-tab-button-index={this.props.idx + 1}
131136
aria-label="tab"
132-
tabIndex={2}
133137
onMouseDown={evt => {
134138
evt.preventDefault()
135139
evt.stopPropagation()
@@ -138,25 +142,25 @@ export default class Tab extends React.PureComponent<Props, State> {
138142
this.props.onSwitchTab(this.props.idx)
139143
}}
140144
>
141-
<div className="kui-tab--label left-tab-stripe-button-label">
145+
<div className="kui--tab--label">
142146
{this.isUsingCommandName() && this.state.title}
143-
{!this.isUsingCommandName() && <span className="kui-tab--label-text">{strings('Tab')} </span>}
144-
{!this.isUsingCommandName() && <span className="kui-tab--label-index"></span>}
147+
{!this.isUsingCommandName() && <span className="kui--tab--label-text">{strings('Tab')} </span>}
148+
{!this.isUsingCommandName() && <span className="kui--tab--label-index"></span>}
145149
</div>
146150

147151
{this.props.closeable && (
148152
<div
149-
className="left-tab-stripe-button-closer"
153+
className="kui--tab-close"
150154
onClick={evt => {
151155
evt.stopPropagation()
152156
evt.preventDefault()
153157
this.props.onCloseTab(this.props.idx)
154158
}}
155159
>
156-
<Close16 focusable="false" width={12} height={16} preserveAspectRatio="xMidYMid meet" aria-hidden="true" />
160+
<Close16 focusable="false" preserveAspectRatio="xMidYMid meet" aria-hidden="true" />
157161
</div>
158162
)}
159-
</a>
163+
</HeaderMenuItem>
160164
)
161165
}
162166
}

0 commit comments

Comments
 (0)