mirror of
https://github.com/microsoft/playwright.git
synced 2024-09-11 20:37:54 +03:00
refactor(ui): some react refactorings (#31900)
Addresses https://github.com/microsoft/playwright/issues/31863. This PR is chonky, but the individual commits should be easy to review. If they're not, i'm happy to break them out into individual PRs. There's two main things this does: 1. Remove some unused imports 2. Add a `clsx`-inspired helper function for classname templating I wasn't able to replace `ReactDOM.render` with `ReactDOM.createRoot`. This is the new recommended way starting with React 18, and the existing one is going to be deprecated at some point. But it somehow breaks our tests, i'll have to investigate that separately.
This commit is contained in:
parent
64fe245297
commit
99724d0322
@ -19,6 +19,7 @@ import './chip.css';
|
||||
import './colors.css';
|
||||
import './common.css';
|
||||
import * as icons from './icons';
|
||||
import { clsx } from '@web/uiUtils';
|
||||
|
||||
export const Chip: React.FC<{
|
||||
header: JSX.Element | string,
|
||||
@ -31,14 +32,14 @@ export const Chip: React.FC<{
|
||||
}> = ({ header, expanded, setExpanded, children, noInsets, dataTestId, targetRef }) => {
|
||||
return <div className='chip' data-testid={dataTestId} ref={targetRef}>
|
||||
<div
|
||||
className={'chip-header' + (setExpanded ? ' expanded-' + expanded : '')}
|
||||
className={clsx('chip-header', setExpanded && ' expanded-' + expanded)}
|
||||
onClick={() => setExpanded?.(!expanded)}
|
||||
title={typeof header === 'string' ? header : undefined}>
|
||||
{setExpanded && !!expanded && icons.downArrow()}
|
||||
{setExpanded && !expanded && icons.rightArrow()}
|
||||
{header}
|
||||
</div>
|
||||
{(!setExpanded || expanded) && <div className={'chip-body' + (noInsets ? ' chip-body-no-insets' : '')}>{children}</div>}
|
||||
{(!setExpanded || expanded) && <div className={clsx('chip-body', noInsets && 'chip-body-no-insets')}>{children}</div>}
|
||||
</div>;
|
||||
};
|
||||
|
||||
|
@ -21,6 +21,7 @@ import { TreeItem } from './treeItem';
|
||||
import { CopyToClipboard } from './copyToClipboard';
|
||||
import './links.css';
|
||||
import { linkifyText } from './renderUtils';
|
||||
import { clsx } from '@web/uiUtils';
|
||||
|
||||
export function navigate(href: string) {
|
||||
window.history.pushState({}, '', href);
|
||||
@ -48,8 +49,8 @@ export const Link: React.FunctionComponent<{
|
||||
className?: string,
|
||||
title?: string,
|
||||
children: any,
|
||||
}> = ({ href, click, ctrlClick, className, children, title }) => {
|
||||
return <a style={{ textDecoration: 'none', color: 'var(--color-fg-default)', cursor: 'pointer' }} href={href} className={`${className || ''}`} title={title} onClick={e => {
|
||||
}> = ({ click, ctrlClick, children, ...rest }) => {
|
||||
return <a {...rest} style={{ textDecoration: 'none', color: 'var(--color-fg-default)', cursor: 'pointer' }} onClick={e => {
|
||||
if (click) {
|
||||
e.preventDefault();
|
||||
navigate(e.metaKey || e.ctrlKey ? ctrlClick || click : click);
|
||||
@ -64,7 +65,7 @@ export const ProjectLink: React.FunctionComponent<{
|
||||
const encoded = encodeURIComponent(projectName);
|
||||
const value = projectName === encoded ? projectName : `"${encoded.replace(/%22/g, '%5C%22')}"`;
|
||||
return <Link href={`#?q=p:${value}`}>
|
||||
<span className={'label label-color-' + (projectNames.indexOf(projectName) % 6)} style={{ margin: '6px 0 0 6px' }}>
|
||||
<span className={clsx('label', `label-color-${projectNames.indexOf(projectName) % 6}`)} style={{ margin: '6px 0 0 6px' }}>
|
||||
{projectName}
|
||||
</span>
|
||||
</Link>;
|
||||
|
@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { clsx } from '@web/uiUtils';
|
||||
import './tabbedPane.css';
|
||||
import * as React from 'react';
|
||||
|
||||
@ -34,7 +35,7 @@ export const TabbedPane: React.FunctionComponent<{
|
||||
<div className='hbox' style={{ flex: 'none' }}>
|
||||
<div className='tabbed-pane-tab-strip'>{
|
||||
tabs.map(tab => (
|
||||
<div className={'tabbed-pane-tab-element ' + (selectedTab === tab.id ? 'selected' : '')}
|
||||
<div className={clsx('tabbed-pane-tab-element', selectedTab === tab.id && 'selected')}
|
||||
onClick={() => setSelectedTab(tab.id)}
|
||||
key={tab.id}>
|
||||
<div className='tabbed-pane-tab-label'>{tab.title}</div>
|
||||
|
@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
import { TestCaseView } from './testCaseView';
|
||||
import type { TestCase, TestResult } from './types';
|
||||
|
@ -25,6 +25,7 @@ import './testCaseView.css';
|
||||
import { TestResultView } from './testResultView';
|
||||
import { linkifyText } from './renderUtils';
|
||||
import { hashStringToInt, msToString } from './utils';
|
||||
import { clsx } from '@web/uiUtils';
|
||||
|
||||
export const TestCaseView: React.FC<{
|
||||
projectNames: string[],
|
||||
@ -90,7 +91,7 @@ const LabelsLinkView: React.FC<React.PropsWithChildren<{
|
||||
<>
|
||||
{labels.map(label => (
|
||||
<a key={label} style={{ textDecoration: 'none', color: 'var(--color-fg-default)' }} href={`#?q=${label}`} >
|
||||
<span style={{ margin: '6px 0 0 6px', cursor: 'pointer' }} className={'label label-color-' + (hashStringToInt(label))}>
|
||||
<span style={{ margin: '6px 0 0 6px', cursor: 'pointer' }} className={clsx('label', 'label-color-' + hashStringToInt(label))}>
|
||||
{label.slice(1)}
|
||||
</span>
|
||||
</a>
|
||||
|
@ -23,6 +23,7 @@ import { generateTraceUrl, Link, navigate, ProjectLink } from './links';
|
||||
import { statusIcon } from './statusIcon';
|
||||
import './testFileView.css';
|
||||
import { video, image, trace } from './icons';
|
||||
import { clsx } from '@web/uiUtils';
|
||||
|
||||
export const TestFileView: React.FC<React.PropsWithChildren<{
|
||||
report: HTMLReport;
|
||||
@ -39,7 +40,7 @@ export const TestFileView: React.FC<React.PropsWithChildren<{
|
||||
{file.fileName}
|
||||
</span>}>
|
||||
{file.tests.filter(t => filter.matches(t)).map(test =>
|
||||
<div key={`test-${test.testId}`} className={'test-file-test test-file-test-outcome-' + test.outcome}>
|
||||
<div key={`test-${test.testId}`} className={clsx('test-file-test', 'test-file-test-outcome-' + test.outcome)}>
|
||||
<div className='hbox' style={{ alignItems: 'flex-start' }}>
|
||||
<div className='hbox'>
|
||||
<span className='test-file-test-status-icon'>
|
||||
@ -101,7 +102,7 @@ const LabelsClickView: React.FC<React.PropsWithChildren<{
|
||||
return labels.length > 0 ? (
|
||||
<>
|
||||
{labels.map(label => (
|
||||
<span key={label} style={{ margin: '6px 0 0 6px', cursor: 'pointer' }} className={'label label-color-' + (hashStringToInt(label))} onClick={e => onClickHandle(e, label)}>
|
||||
<span key={label} style={{ margin: '6px 0 0 6px', cursor: 'pointer' }} className={clsx('label', 'label-color-' + hashStringToInt(label))} onClick={e => onClickHandle(e, label)}>
|
||||
{label.slice(1)}
|
||||
</span>
|
||||
))}
|
||||
|
@ -17,7 +17,7 @@
|
||||
import './callLog.css';
|
||||
import * as React from 'react';
|
||||
import type { CallLog } from './recorderTypes';
|
||||
import { msToString } from '@web/uiUtils';
|
||||
import { clsx, msToString } from '@web/uiUtils';
|
||||
import { asLocator } from '@isomorphic/locatorGenerators';
|
||||
import type { Language } from '@isomorphic/locatorGenerators';
|
||||
|
||||
@ -53,9 +53,9 @@ export const CallLogView: React.FC<CallLogProps> = ({
|
||||
titlePrefix = callLog.title + '(';
|
||||
titleSuffix = ')';
|
||||
}
|
||||
return <div className={`call-log-call ${callLog.status}`} key={callLog.id}>
|
||||
return <div className={clsx('call-log-call', callLog.status)} key={callLog.id}>
|
||||
<div className='call-log-call-header'>
|
||||
<span className={`codicon codicon-chevron-${isExpanded ? 'down' : 'right'}`} style={{ cursor: 'pointer' }}onClick={() => {
|
||||
<span className={clsx('codicon', `codicon-chevron-${isExpanded ? 'down' : 'right'}`)} style={{ cursor: 'pointer' }}onClick={() => {
|
||||
const newOverrides = new Map(expandOverrides);
|
||||
newOverrides.set(callLog.id, !isExpanded);
|
||||
setExpandOverrides(newOverrides);
|
||||
@ -64,7 +64,7 @@ export const CallLogView: React.FC<CallLogProps> = ({
|
||||
{ callLog.params.url ? <span className='call-log-details'><span className='call-log-url' title={callLog.params.url}>{callLog.params.url}</span></span> : undefined }
|
||||
{ locator ? <span className='call-log-details'><span className='call-log-selector' title={`page.${locator}`}>{`page.${locator}`}</span></span> : undefined }
|
||||
{ titleSuffix }
|
||||
<span className={'codicon ' + iconClass(callLog)}></span>
|
||||
<span className={clsx('codicon', iconClass(callLog))}></span>
|
||||
{ typeof callLog.duration === 'number' ? <span className='call-log-time'>— {msToString(callLog.duration)}</span> : undefined}
|
||||
</div>
|
||||
{ (isExpanded ? callLog.messages : []).map((message, i) => {
|
||||
|
@ -17,7 +17,6 @@
|
||||
import '@web/common.css';
|
||||
import { applyTheme } from '@web/theme';
|
||||
import '@web/third_party/vscode/codicon.css';
|
||||
import React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { EmbeddedWorkbenchLoader } from './ui/embeddedWorkbenchLoader';
|
||||
|
||||
|
@ -17,7 +17,6 @@
|
||||
import '@web/common.css';
|
||||
import { applyTheme } from '@web/theme';
|
||||
import '@web/third_party/vscode/codicon.css';
|
||||
import React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { WorkbenchLoader } from './ui/workbenchLoader';
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
import type { SerializedValue } from '@protocol/channels';
|
||||
import type { ActionTraceEvent } from '@trace/trace';
|
||||
import { msToString } from '@web/uiUtils';
|
||||
import { clsx, msToString } from '@web/uiUtils';
|
||||
import * as React from 'react';
|
||||
import './callTab.css';
|
||||
import { CopyToClipboard } from './copyToClipboard';
|
||||
@ -71,7 +71,7 @@ function renderProperty(property: Property, key: string) {
|
||||
text = `"${text}"`;
|
||||
return (
|
||||
<div key={key} className='call-line'>
|
||||
{property.name}:<span className={`call-value ${property.type}`} title={property.text}>{text}</span>
|
||||
{property.name}:<span className={clsx('call-value', property.type)} title={property.text}>{text}</span>
|
||||
{ ['string', 'number', 'object', 'locator'].includes(property.type) &&
|
||||
<CopyToClipboard value={property.text} />
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import './consoleTab.css';
|
||||
import type * as modelUtil from './modelUtil';
|
||||
import { ListView } from '@web/components/listView';
|
||||
import type { Boundaries } from '../geometry';
|
||||
import { msToString } from '@web/uiUtils';
|
||||
import { clsx, msToString } from '@web/uiUtils';
|
||||
import { ansi2html } from '@web/ansi2html';
|
||||
import { PlaceholderPanel } from './placeholderPanel';
|
||||
|
||||
@ -124,8 +124,8 @@ export const ConsoleTab: React.FunctionComponent<{
|
||||
render={entry => {
|
||||
const timestamp = msToString(entry.timestamp - boundaries.minimum);
|
||||
const timestampElement = <span className='console-time'>{timestamp}</span>;
|
||||
const errorSuffix = entry.isError ? ' status-error' : entry.isWarning ? ' status-warning' : ' status-none';
|
||||
const statusElement = entry.browserMessage || entry.browserError ? <span className={'codicon codicon-browser' + errorSuffix} title='Browser message'></span> : <span className={'codicon codicon-file' + errorSuffix} title='Runner message'></span>;
|
||||
const errorSuffix = entry.isError ? 'status-error' : entry.isWarning ? 'status-warning' : 'status-none';
|
||||
const statusElement = entry.browserMessage || entry.browserError ? <span className={clsx('codicon', 'codicon-browser', errorSuffix)} title='Browser message'></span> : <span className={clsx('codicon', 'codicon-file', errorSuffix)} title='Runner message'></span>;
|
||||
let locationText: string | undefined;
|
||||
let messageBody: JSX.Element[] | string | undefined;
|
||||
let messageInnerHTML: string | undefined;
|
||||
|
@ -20,7 +20,7 @@ import type { ActionTraceEvent } from '@trace/trace';
|
||||
import { context, prevInList } from './modelUtil';
|
||||
import { Toolbar } from '@web/components/toolbar';
|
||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||
import { useMeasure } from '@web/uiUtils';
|
||||
import { clsx, useMeasure } from '@web/uiUtils';
|
||||
import { InjectedScript } from '@injected/injectedScript';
|
||||
import { Recorder } from '@injected/recorder/recorder';
|
||||
import ConsoleAPI from '@injected/consoleApi';
|
||||
@ -209,8 +209,8 @@ export const SnapshotTab: React.FunctionComponent<{
|
||||
}}>
|
||||
<BrowserFrame url={snapshotInfo.url} />
|
||||
<div className='snapshot-switcher'>
|
||||
<iframe ref={iframeRef0} name='snapshot' title='DOM Snapshot' className={loadingRef.current.visibleIframe === 0 ? 'snapshot-visible' : ''}></iframe>
|
||||
<iframe ref={iframeRef1} name='snapshot' title='DOM Snapshot' className={loadingRef.current.visibleIframe === 1 ? 'snapshot-visible' : ''}></iframe>
|
||||
<iframe ref={iframeRef0} name='snapshot' title='DOM Snapshot' className={clsx(loadingRef.current.visibleIframe === 0 && 'snapshot-visible')}></iframe>
|
||||
<iframe ref={iframeRef1} name='snapshot' title='DOM Snapshot' className={clsx(loadingRef.current.visibleIframe === 1 && 'snapshot-visible')}></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -14,11 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { clsx } from '@web/uiUtils';
|
||||
import './tag.css';
|
||||
|
||||
export const TagView: React.FC<{ tag: string, style?: React.CSSProperties, onClick?: (e: React.MouseEvent) => void }> = ({ tag, style, onClick }) => {
|
||||
return <span
|
||||
className={`tag tag-color-${tagNameToColor(tag)}`}
|
||||
className={clsx('tag', `tag-color-${tagNameToColor(tag)}`)}
|
||||
onClick={onClick}
|
||||
style={{ margin: '6px 0 0 6px', ...style }}
|
||||
title={`Click to filter by tag: ${tag}`}
|
||||
|
@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { msToString, useMeasure } from '@web/uiUtils';
|
||||
import { clsx, msToString, useMeasure } from '@web/uiUtils';
|
||||
import { GlassPane } from '@web/shared/glassPane';
|
||||
import * as React from 'react';
|
||||
import type { Boundaries } from '../geometry';
|
||||
@ -252,11 +252,12 @@ export const Timeline: React.FunctionComponent<{
|
||||
<div className='timeline-bars'>{
|
||||
bars.map((bar, index) => {
|
||||
return <div key={index}
|
||||
className={'timeline-bar' + (bar.action ? ' action' : '')
|
||||
+ (bar.resource ? ' network' : '')
|
||||
+ (bar.consoleMessage ? ' console-message' : '')
|
||||
+ (bar.active ? ' active' : '')
|
||||
+ (bar.error ? ' error' : '')}
|
||||
className={clsx('timeline-bar',
|
||||
bar.action && 'action',
|
||||
bar.resource && 'network',
|
||||
bar.consoleMessage && 'console-message',
|
||||
bar.active && 'active',
|
||||
bar.error && 'error')}
|
||||
style={{
|
||||
left: bar.leftPosition,
|
||||
width: Math.max(5, bar.rightPosition - bar.leftPosition),
|
||||
|
@ -30,7 +30,7 @@ import { Toolbar } from '@web/components/toolbar';
|
||||
import type { XtermDataSource } from '@web/components/xtermWrapper';
|
||||
import { XtermWrapper } from '@web/components/xtermWrapper';
|
||||
import { useDarkModeSetting } from '@web/theme';
|
||||
import { settings, useSetting } from '@web/uiUtils';
|
||||
import { clsx, settings, useSetting } from '@web/uiUtils';
|
||||
import { statusEx, TestTree } from '@testIsomorphic/testTree';
|
||||
import type { TreeItem } from '@testIsomorphic/testTree';
|
||||
import { TestServerConnection } from '@testIsomorphic/testServerConnection';
|
||||
@ -435,7 +435,7 @@ export const UIModeView: React.FC<{}> = ({
|
||||
</div>}
|
||||
<SplitView sidebarSize={250} minSidebarSize={150} orientation='horizontal' sidebarIsFirst={true} settingName='testListSidebar'>
|
||||
<div className='vbox'>
|
||||
<div className={'vbox' + (isShowingOutput ? '' : ' hidden')}>
|
||||
<div className={clsx('vbox', !isShowingOutput && 'hidden')}>
|
||||
<Toolbar>
|
||||
<div className='section-title' style={{ flex: 'none' }}>Output</div>
|
||||
<ToolbarButton icon='circle-slash' title='Clear output' onClick={() => xtermDataSource.clear()}></ToolbarButton>
|
||||
@ -444,7 +444,7 @@ export const UIModeView: React.FC<{}> = ({
|
||||
</Toolbar>
|
||||
<XtermWrapper source={xtermDataSource}></XtermWrapper>
|
||||
</div>
|
||||
<div className={'vbox' + (isShowingOutput ? ' hidden' : '')}>
|
||||
<div className={clsx('vbox', isShowingOutput && 'hidden')}>
|
||||
<TraceView
|
||||
item={selectedItem}
|
||||
rootDir={testModel?.config?.rootDir}
|
||||
|
@ -36,7 +36,7 @@ import { AttachmentsTab } from './attachmentsTab';
|
||||
import type { Boundaries } from '../geometry';
|
||||
import { InspectorTab } from './inspectorTab';
|
||||
import { ToolbarButton } from '@web/components/toolbarButton';
|
||||
import { useSetting, msToString } from '@web/uiUtils';
|
||||
import { useSetting, msToString, clsx } from '@web/uiUtils';
|
||||
import type { Entry } from '@trace/har';
|
||||
import './workbench.css';
|
||||
import { testStatusIcon, testStatusText } from './testUtils';
|
||||
@ -251,7 +251,7 @@ export const Workbench: React.FunctionComponent<{
|
||||
title: 'Actions',
|
||||
component: <div className='vbox'>
|
||||
{status && <div className='workbench-run-status'>
|
||||
<span className={`codicon ${testStatusIcon(status)}`}></span>
|
||||
<span className={clsx('codicon', testStatusIcon(status))}></span>
|
||||
<div>{testStatusText(status)}</div>
|
||||
<div className='spacer'></div>
|
||||
<div className='workbench-run-duration'>{time ? msToString(time) : ''}</div>
|
||||
|
@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import '@web/common.css';
|
||||
import { applyTheme } from '@web/theme';
|
||||
import '@web/third_party/vscode/codicon.css';
|
||||
|
@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { expect, test } from '@playwright/experimental-ct-react';
|
||||
import { CodeMirrorWrapper } from './codeMirrorWrapper';
|
||||
|
||||
|
@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { expect, test } from '@playwright/experimental-ct-react';
|
||||
import { Expandable } from './expandable';
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import './expandable.css';
|
||||
import { clsx } from '../uiUtils';
|
||||
|
||||
export const Expandable: React.FunctionComponent<React.PropsWithChildren<{
|
||||
title: JSX.Element | string,
|
||||
@ -23,10 +24,10 @@ export const Expandable: React.FunctionComponent<React.PropsWithChildren<{
|
||||
expanded: boolean,
|
||||
expandOnTitleClick?: boolean,
|
||||
}>> = ({ title, children, setExpanded, expanded, expandOnTitleClick }) => {
|
||||
return <div className={'expandable' + (expanded ? ' expanded' : '')}>
|
||||
return <div className={clsx('expandable', expanded && 'expanded')}>
|
||||
<div className='expandable-title' onClick={() => expandOnTitleClick && setExpanded(!expanded)}>
|
||||
<div
|
||||
className={'codicon codicon-' + (expanded ? 'chevron-down' : 'chevron-right')}
|
||||
className={clsx('codicon', expanded ? 'codicon-chevron-down' : 'codicon-chevron-right')}
|
||||
style={{ cursor: 'pointer', color: 'var(--vscode-foreground)', marginLeft: '5px' }}
|
||||
onClick={() => !expandOnTitleClick && setExpanded(!expanded)} />
|
||||
{title}
|
||||
|
@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { expect, test } from '@playwright/experimental-ct-react';
|
||||
import { SplitView } from './splitView';
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { useMeasure, useSetting } from '../uiUtils';
|
||||
import { clsx, useMeasure, useSetting } from '../uiUtils';
|
||||
import './splitView.css';
|
||||
import * as React from 'react';
|
||||
|
||||
@ -75,7 +75,7 @@ export const SplitView: React.FC<React.PropsWithChildren<SplitViewProps>> = ({
|
||||
resizerStyle = { right: resizing ? 0 : size - 4, left: resizing ? 0 : undefined, width: resizing ? 'initial' : 8 };
|
||||
}
|
||||
|
||||
return <div className={'split-view ' + orientation + (sidebarIsFirst ? ' sidebar-first' : '') } ref={ref}>
|
||||
return <div className={clsx('split-view', orientation, sidebarIsFirst && 'sidebar-first') } ref={ref}>
|
||||
<div className='split-view-main'>{childrenArray[0]}</div>
|
||||
{ !sidebarHidden && <div style={{ flexBasis: size }} className='split-view-sidebar'>{childrenArray[1]}</div> }
|
||||
{ !sidebarHidden && <div
|
||||
|
@ -14,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { clsx } from '@web/uiUtils';
|
||||
import './tabbedPane.css';
|
||||
import { Toolbar } from './toolbar';
|
||||
import * as React from 'react';
|
||||
@ -99,7 +100,7 @@ export const TabbedPaneTab: React.FunctionComponent<{
|
||||
selected?: boolean,
|
||||
onSelect: (id: string) => void
|
||||
}> = ({ id, title, count, errorCount, selected, onSelect }) => {
|
||||
return <div className={'tabbed-pane-tab ' + (selected ? 'selected' : '')}
|
||||
return <div className={clsx('tabbed-pane-tab', selected && 'selected')}
|
||||
onClick={() => onSelect(id)}
|
||||
title={title}
|
||||
key={id}>
|
||||
|
@ -14,6 +14,7 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { clsx } from '@web/uiUtils';
|
||||
import './toolbar.css';
|
||||
import * as React from 'react';
|
||||
|
||||
@ -31,5 +32,5 @@ export const Toolbar: React.FC<React.PropsWithChildren<ToolbarProps>> = ({
|
||||
className,
|
||||
onClick,
|
||||
}) => {
|
||||
return <div className={'toolbar' + (noShadow ? ' no-shadow' : '') + (noMinHeight ? ' no-min-height' : '') + ' ' + (className || '')} onClick={onClick}>{children}</div>;
|
||||
return <div className={clsx('toolbar', noShadow && 'no-shadow', noMinHeight && 'no-min-height', className)} onClick={onClick}>{children}</div>;
|
||||
};
|
||||
|
@ -14,7 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { test, expect } from '@playwright/experimental-ct-react';
|
||||
import type { ImageDiff } from './imageDiffView';
|
||||
import { ImageDiffView } from './imageDiffView';
|
||||
|
@ -191,3 +191,8 @@ export class Settings {
|
||||
}
|
||||
|
||||
export const settings = new Settings();
|
||||
|
||||
// inspired by https://www.npmjs.com/package/clsx
|
||||
export function clsx(...classes: (string | undefined | false)[]) {
|
||||
return classes.filter(Boolean).join(' ');
|
||||
}
|
Loading…
Reference in New Issue
Block a user