Convert tab, tabs and searchBox to function components

This commit is contained in:
Labhansh Agrawal 2023-07-06 20:10:14 +05:30 committed by GitHub
parent a793a1d9a4
commit 6c8d0433d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 360 additions and 391 deletions

View file

@ -1,4 +1,4 @@
import React, {useCallback} from 'react'; import React, {useCallback, useRef, useEffect} from 'react';
import type {SearchBoxProps} from '../hyper'; import type {SearchBoxProps} from '../hyper';
import {VscArrowUp} from '@react-icons/all-files/vsc/VscArrowUp'; import {VscArrowUp} from '@react-icons/all-files/vsc/VscArrowUp';
import {VscArrowDown} from '@react-icons/all-files/vsc/VscArrowDown'; import {VscArrowDown} from '@react-icons/all-files/vsc/VscArrowDown';
@ -82,179 +82,152 @@ const SearchButton = ({
); );
}; };
class SearchBox extends React.PureComponent<SearchBoxProps> { const SearchBox = (props: SearchBoxProps) => {
searchTerm: string; const {
input: HTMLInputElement | null = null; caseSensitive,
searchButtonColors: SearchButtonColors; wholeWord,
regex,
results,
toggleCaseSensitive,
toggleWholeWord,
toggleRegex,
next,
prev,
close,
backgroundColor,
foregroundColor,
borderColor,
selectionColor,
font
} = props;
constructor(props: SearchBoxProps) { const searchTermRef = useRef<string>('');
super(props); const inputRef = useRef<HTMLInputElement | null>(null);
this.searchTerm = '';
this.searchButtonColors = {
backgroundColor: this.props.borderColor,
selectionColor: this.props.selectionColor,
foregroundColor: this.props.foregroundColor
};
}
handleChange = (event: React.KeyboardEvent<HTMLInputElement>) => { const handleChange = useCallback(
this.searchTerm = event.currentTarget.value; (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.shiftKey && event.key === 'Enter') { searchTermRef.current = event.currentTarget.value;
this.props.prev(this.searchTerm); if (event.shiftKey && event.key === 'Enter') {
} else if (event.key === 'Enter') { prev(searchTermRef.current);
this.props.next(this.searchTerm); } else if (event.key === 'Enter') {
} next(searchTermRef.current);
}
},
[prev, next]
);
useEffect(() => {
inputRef.current?.focus();
}, [inputRef.current]);
const searchButtonColors: SearchButtonColors = {
backgroundColor: borderColor,
selectionColor,
foregroundColor
}; };
componentDidMount(): void { return (
this.input?.focus(); <div className="flex-row search-container">
} <div className="flex-row search-box">
<input className="search-input" type="text" onKeyDown={handleChange} ref={inputRef} placeholder="Search" />
render() { <SearchButton onClick={toggleCaseSensitive} active={caseSensitive} title="Match Case" {...searchButtonColors}>
const { <VscCaseSensitive size="14px" />
caseSensitive, </SearchButton>
wholeWord,
regex,
results,
toggleCaseSensitive,
toggleWholeWord,
toggleRegex,
next,
prev,
close,
backgroundColor,
foregroundColor,
borderColor,
selectionColor,
font
} = this.props;
return ( <SearchButton onClick={toggleWholeWord} active={wholeWord} title="Match Whole Word" {...searchButtonColors}>
<div className="flex-row search-container"> <VscWholeWord size="14px" />
<div className="flex-row search-box"> </SearchButton>
<input
className="search-input"
type="text"
onKeyDown={this.handleChange}
ref={(input) => {
this.input = input;
}}
placeholder="Search"
></input>
<SearchButton <SearchButton onClick={toggleRegex} active={regex} title="Use Regular Expression" {...searchButtonColors}>
onClick={toggleCaseSensitive} <VscRegex size="14px" />
active={caseSensitive} </SearchButton>
title="Match Case"
{...this.searchButtonColors}
>
<VscCaseSensitive size="14px" />
</SearchButton>
<SearchButton
onClick={toggleWholeWord}
active={wholeWord}
title="Match Whole Word"
{...this.searchButtonColors}
>
<VscWholeWord size="14px" />
</SearchButton>
<SearchButton
onClick={toggleRegex}
active={regex}
title="Use Regular Expression"
{...this.searchButtonColors}
>
<VscRegex size="14px" />
</SearchButton>
</div>
<span style={{minWidth: '60px', marginLeft: '4px'}}>
{results === undefined
? ''
: results.resultCount === 0
? 'No results'
: `${results.resultIndex + 1} of ${results.resultCount}`}
</span>
<div className="flex-row">
<SearchButton
onClick={() => prev(this.searchTerm)}
active={false}
title="Previous Match"
{...this.searchButtonColors}
>
<VscArrowUp size="14px" />
</SearchButton>
<SearchButton
onClick={() => next(this.searchTerm)}
active={false}
title="Next Match"
{...this.searchButtonColors}
>
<VscArrowDown size="14px" />
</SearchButton>
<SearchButton onClick={() => close()} active={false} title="Close" {...this.searchButtonColors}>
<VscClose size="14px" />
</SearchButton>
</div>
<style jsx>
{`
.search-container {
background-color: ${backgroundColor};
border: 1px solid ${borderColor};
border-radius: 2px;
position: absolute;
right: 13px;
top: 4px;
z-index: 10;
padding: 4px;
font-family: ${font};
font-size: 12px;
}
.search-input {
outline: none;
background-color: transparent;
border: none;
color: ${foregroundColor};
align-self: stretch;
width: 100px;
}
.flex-row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 4px;
}
.search-box {
border: none;
border-radius: 2px;
outline: ${borderColor} solid 1px;
background-color: ${backgroundColor};
color: ${foregroundColor};
padding: 0px 4px;
}
.search-input::placeholder {
color: ${foregroundColor};
}
.search-box:focus-within {
outline: ${selectionColor} solid 2px;
}
`}
</style>
</div> </div>
);
} <span style={{minWidth: '60px', marginLeft: '4px'}}>
} {results === undefined
? ''
: results.resultCount === 0
? 'No results'
: `${results.resultIndex + 1} of ${results.resultCount}`}
</span>
<div className="flex-row">
<SearchButton
onClick={() => prev(searchTermRef.current)}
active={false}
title="Previous Match"
{...searchButtonColors}
>
<VscArrowUp size="14px" />
</SearchButton>
<SearchButton
onClick={() => next(searchTermRef.current)}
active={false}
title="Next Match"
{...searchButtonColors}
>
<VscArrowDown size="14px" />
</SearchButton>
<SearchButton onClick={close} active={false} title="Close" {...searchButtonColors}>
<VscClose size="14px" />
</SearchButton>
</div>
<style jsx>
{`
.search-container {
background-color: ${backgroundColor};
border: 1px solid ${borderColor};
border-radius: 2px;
position: absolute;
right: 13px;
top: 4px;
z-index: 10;
padding: 4px;
font-family: ${font};
font-size: 12px;
}
.search-input {
outline: none;
background-color: transparent;
border: none;
color: ${foregroundColor};
align-self: stretch;
width: 100px;
}
.flex-row {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: 4px;
}
.search-box {
border: none;
border-radius: 2px;
outline: ${borderColor} solid 1px;
background-color: ${backgroundColor};
color: ${foregroundColor};
padding: 0px 4px;
}
.search-input::placeholder {
color: ${foregroundColor};
}
.search-box:focus-within {
outline: ${selectionColor} solid 2px;
}
`}
</style>
</div>
);
};
export default SearchBox; export default SearchBox;

View file

@ -1,164 +1,160 @@
import React from 'react'; import React from 'react';
import type {TabProps} from '../hyper'; import type {TabProps} from '../hyper';
export default class Tab extends React.PureComponent<TabProps> { const Tab = (props: TabProps) => {
constructor(props: TabProps) { const handleClick = (event: React.MouseEvent) => {
super(props);
}
handleClick = (event: React.MouseEvent) => {
const isLeftClick = event.nativeEvent.which === 1; const isLeftClick = event.nativeEvent.which === 1;
if (isLeftClick && !this.props.isActive) { if (isLeftClick && !props.isActive) {
this.props.onSelect(); props.onSelect();
} }
}; };
handleMouseUp = (event: React.MouseEvent) => { const handleMouseUp = (event: React.MouseEvent) => {
const isMiddleClick = event.nativeEvent.which === 2; const isMiddleClick = event.nativeEvent.which === 2;
if (isMiddleClick) { if (isMiddleClick) {
this.props.onClose(); props.onClose();
} }
}; };
render() { const {isActive, isFirst, isLast, borderColor, hasActivity} = props;
const {isActive, isFirst, isLast, borderColor, hasActivity} = this.props;
return ( return (
<React.Fragment> <>
<li <li
onClick={this.props.onClick} onClick={props.onClick}
style={{borderColor}} style={{borderColor}}
className={`tab_tab ${isFirst ? 'tab_first' : ''} ${isActive ? 'tab_active' : ''} ${ className={`tab_tab ${isFirst ? 'tab_first' : ''} ${isActive ? 'tab_active' : ''} ${
isFirst && isActive ? 'tab_firstActive' : '' isFirst && isActive ? 'tab_firstActive' : ''
} ${hasActivity ? 'tab_hasActivity' : ''}`} } ${hasActivity ? 'tab_hasActivity' : ''}`}
>
{props.customChildrenBefore}
<span
className={`tab_text ${isLast ? 'tab_textLast' : ''} ${isActive ? 'tab_textActive' : ''}`}
onClick={handleClick}
onMouseUp={handleMouseUp}
> >
{this.props.customChildrenBefore} <span title={props.text} className="tab_textInner">
<span {props.text}
className={`tab_text ${isLast ? 'tab_textLast' : ''} ${isActive ? 'tab_textActive' : ''}`}
onClick={this.handleClick}
onMouseUp={this.handleMouseUp}
>
<span title={this.props.text} className="tab_textInner">
{this.props.text}
</span>
</span> </span>
<i className="tab_icon" onClick={this.props.onClose}> </span>
<svg className="tab_shape"> <i className="tab_icon" onClick={props.onClose}>
<use xlinkHref="./renderer/assets/icons.svg#close-tab" /> <svg className="tab_shape">
</svg> <use xlinkHref="./renderer/assets/icons.svg#close-tab" />
</i> </svg>
{this.props.customChildren} </i>
</li> {props.customChildren}
</li>
<style jsx>{` <style jsx>{`
.tab_tab { .tab_tab {
color: #ccc; color: #ccc;
border-color: #ccc; border-color: #ccc;
border-bottom-width: 1px; border-bottom-width: 1px;
border-bottom-style: solid; border-bottom-style: solid;
border-left-width: 1px; border-left-width: 1px;
border-left-style: solid; border-left-style: solid;
list-style-type: none; list-style-type: none;
flex-grow: 1; flex-grow: 1;
position: relative; position: relative;
} }
.tab_tab:hover { .tab_tab:hover {
color: #ccc; color: #ccc;
} }
.tab_first { .tab_first {
border-left-width: 0; border-left-width: 0;
padding-left: 1px; padding-left: 1px;
} }
.tab_firstActive { .tab_firstActive {
border-left-width: 1px; border-left-width: 1px;
padding-left: 0; padding-left: 0;
} }
.tab_active { .tab_active {
color: #fff; color: #fff;
border-bottom-width: 0; border-bottom-width: 0;
} }
.tab_active:hover { .tab_active:hover {
color: #fff; color: #fff;
} }
.tab_hasActivity { .tab_hasActivity {
color: #50e3c2; color: #50e3c2;
} }
.tab_hasActivity:hover { .tab_hasActivity:hover {
color: #50e3c2; color: #50e3c2;
} }
.tab_text { .tab_text {
transition: color 0.2s ease; transition: color 0.2s ease;
height: 34px; height: 34px;
display: block; display: block;
width: 100%; width: 100%;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
} }
.tab_textInner { .tab_textInner {
position: absolute; position: absolute;
left: 24px; left: 24px;
right: 24px; right: 24px;
top: 0; top: 0;
bottom: 0; bottom: 0;
text-align: center; text-align: center;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
} }
.tab_icon { .tab_icon {
transition: opacity 0.2s ease, color 0.2s ease, transform 0.25s ease, background-color 0.1s ease; transition: opacity 0.2s ease, color 0.2s ease, transform 0.25s ease, background-color 0.1s ease;
pointer-events: none; pointer-events: none;
position: absolute; position: absolute;
right: 7px; right: 7px;
top: 10px; top: 10px;
display: inline-block; display: inline-block;
width: 14px; width: 14px;
height: 14px; height: 14px;
border-radius: 100%; border-radius: 100%;
color: #e9e9e9; color: #e9e9e9;
opacity: 0; opacity: 0;
transform: scale(0.95); transform: scale(0.95);
} }
.tab_icon:hover { .tab_icon:hover {
background-color: rgba(255, 255, 255, 0.13); background-color: rgba(255, 255, 255, 0.13);
color: #fff; color: #fff;
} }
.tab_icon:active { .tab_icon:active {
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
color: #909090; color: #909090;
} }
.tab_tab:hover .tab_icon { .tab_tab:hover .tab_icon {
opacity: 1; opacity: 1;
transform: none; transform: none;
pointer-events: all; pointer-events: all;
} }
.tab_shape { .tab_shape {
position: absolute; position: absolute;
left: 4px; left: 4px;
top: 4px; top: 4px;
width: 6px; width: 6px;
height: 6px; height: 6px;
vertical-align: middle; vertical-align: middle;
fill: currentColor; fill: currentColor;
shape-rendering: crispEdges; shape-rendering: crispEdges;
} }
`}</style> `}</style>
</React.Fragment> </>
); );
} };
}
export default Tab;

View file

@ -9,103 +9,103 @@ import DropdownButton from './new-tab';
const Tab = decorate(Tab_, 'Tab'); const Tab = decorate(Tab_, 'Tab');
const isMac = /Mac/.test(navigator.userAgent); const isMac = /Mac/.test(navigator.userAgent);
export default class Tabs extends React.PureComponent<TabsProps> { const Tabs = (props: TabsProps) => {
render() { const {tabs = [], borderColor, onChange, onClose, fullScreen} = props;
const {tabs = [], borderColor, onChange, onClose, fullScreen} = this.props;
const hide = !isMac && tabs.length === 1; const hide = !isMac && tabs.length === 1;
return ( return (
<nav className={`tabs_nav ${hide ? 'tabs_hiddenNav' : ''}`}> <nav className={`tabs_nav ${hide ? 'tabs_hiddenNav' : ''}`}>
{this.props.customChildrenBefore} {props.customChildrenBefore}
{tabs.length === 1 && isMac ? <div className="tabs_title">{tabs[0].title}</div> : null} {tabs.length === 1 && isMac ? <div className="tabs_title">{tabs[0].title}</div> : null}
{tabs.length > 1 {tabs.length > 1 ? (
? [ <>
<ul key="list" className={`tabs_list ${fullScreen && isMac ? 'tabs_fullScreen' : ''}`}> <ul key="list" className={`tabs_list ${fullScreen && isMac ? 'tabs_fullScreen' : ''}`}>
{tabs.map((tab, i) => { {tabs.map((tab, i) => {
const {uid, title, isActive, hasActivity} = tab; const {uid, title, isActive, hasActivity} = tab;
const props = getTabProps(tab, this.props, { const tabProps = getTabProps(tab, props, {
text: title === '' ? 'Shell' : title, text: title === '' ? 'Shell' : title,
isFirst: i === 0, isFirst: i === 0,
isLast: tabs.length - 1 === i, isLast: tabs.length - 1 === i,
borderColor, borderColor,
isActive, isActive,
hasActivity, hasActivity,
onSelect: onChange.bind(null, uid), onSelect: onChange.bind(null, uid),
onClose: onClose.bind(null, uid) onClose: onClose.bind(null, uid)
}); });
return <Tab key={`tab-${uid}`} {...props} />; return <Tab key={`tab-${uid}`} {...tabProps} />;
})} })}
</ul>, </ul>
isMac && ( {isMac && (
<div <div
key="shim" key="shim"
style={{borderColor}} style={{borderColor}}
className={`tabs_borderShim ${fullScreen ? 'tabs_borderShimUndo' : ''}`} className={`tabs_borderShim ${fullScreen ? 'tabs_borderShimUndo' : ''}`}
/> />
) )}
] </>
: null} ) : null}
<DropdownButton {...this.props} tabsVisible={tabs.length > 1} /> <DropdownButton {...props} tabsVisible={tabs.length > 1} />
{this.props.customChildren} {props.customChildren}
<style jsx>{` <style jsx>{`
.tabs_nav { .tabs_nav {
font-size: 12px; font-size: 12px;
height: 34px; height: 34px;
line-height: 34px; line-height: 34px;
vertical-align: middle; vertical-align: middle;
color: #9b9b9b; color: #9b9b9b;
cursor: default; cursor: default;
position: relative; position: relative;
-webkit-user-select: none; -webkit-user-select: none;
-webkit-app-region: ${isMac ? 'drag' : ''}; -webkit-app-region: ${isMac ? 'drag' : ''};
top: ${isMac ? '0px' : '34px'}; top: ${isMac ? '0px' : '34px'};
display: flex; display: flex;
flex-flow: row; flex-flow: row;
} }
.tabs_hiddenNav { .tabs_hiddenNav {
display: none; display: none;
} }
.tabs_title { .tabs_title {
text-align: center; text-align: center;
color: #fff; color: #fff;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
padding-left: 76px; padding-left: 76px;
padding-right: 76px; padding-right: 76px;
flex-grow: 1; flex-grow: 1;
} }
.tabs_list { .tabs_list {
max-height: 34px; max-height: 34px;
display: flex; display: flex;
flex-flow: row; flex-flow: row;
margin-left: ${isMac ? '76px' : '0'}; margin-left: ${isMac ? '76px' : '0'};
flex-grow: 1; flex-grow: 1;
} }
.tabs_fullScreen { .tabs_fullScreen {
margin-left: -1px; margin-left: -1px;
} }
.tabs_borderShim { .tabs_borderShim {
position: absolute; position: absolute;
width: 76px; width: 76px;
bottom: 0; bottom: 0;
border-color: #ccc; border-color: #ccc;
border-bottom-style: solid; border-bottom-style: solid;
border-bottom-width: 1px; border-bottom-width: 1px;
} }
.tabs_borderShimUndo { .tabs_borderShimUndo {
border-bottom-width: 0px; border-bottom-width: 0px;
} }
`}</style> `}</style>
</nav> </nav>
); );
} };
}
export default Tabs;