1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00
lens/src/renderer/components/tabs/tabs.tsx
Roman 6addce21fb - Merging interfaces & classses to avoid overwriting fields from parent's super(data)-call with Object.assign(this, data). Otherwise use "declare" keyword at class field definition.
- Revamping {useDefineForClassFields: true} to avoid issues with non-observable class fields in some cases (from previous commit):

```
@observer
export class CommandContainer extends React.Component<CommandContainerProps> {
  // without some defined initial value "commandComponent" is non-observable for some reasons
  // when tsconfig.ts has {useDefineForClassFields:false}
  @observable.ref commandComponent: React.ReactNode = null;

  constructor(props: CommandContainerProps) {
    super(props);
    makeObservable(this);
  }
```

Signed-off-by: Roman <ixrock@gmail.com>
2021-05-14 16:04:30 +03:00

173 lines
4.6 KiB
TypeScript

/**
* Copyright (c) 2021 OpenLens Authors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
* the Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import "./tabs.scss";
import React, { DOMAttributes } from "react";
import { boundMethod, cssNames } from "../../utils";
import { Icon } from "../icon";
const TabsContext = React.createContext<TabsContextValue>({});
interface TabsContextValue<D = any> {
autoFocus?: boolean;
withBorder?: boolean;
value?: D;
onChange?(value: D): void;
}
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export interface TabsProps<D = any> extends TabsContextValue<D>, Omit<DOMAttributes<HTMLElement>, "onChange"> {
className?: string;
center?: boolean;
wrap?: boolean;
scrollable?: boolean;
}
export class Tabs extends React.PureComponent<TabsProps> {
public elem: HTMLElement;
@boundMethod
protected bindRef(elem: HTMLElement) {
this.elem = elem;
}
render() {
const { center, wrap, onChange, value, autoFocus, scrollable = true, withBorder, ...elemProps } = this.props;
const className = cssNames("Tabs", this.props.className, {
center,
wrap,
scrollable,
withBorder,
});
return (
<TabsContext.Provider value={{ autoFocus, value, onChange }}>
<div
{...elemProps}
className={className}
ref={this.bindRef}
/>
</TabsContext.Provider>
);
}
}
export interface TabProps<D = any> extends DOMAttributes<HTMLElement> {
id?: string;
className?: string;
active?: boolean;
disabled?: boolean;
icon?: React.ReactNode | string; // material-ui name or custom icon
label?: React.ReactNode;
value: D;
}
export class Tab extends React.PureComponent<TabProps> {
static contextType = TabsContext;
declare context: TabsContextValue;
public elem: HTMLElement;
get isActive() {
const { active, value } = this.props;
return typeof active === "boolean" ? active : this.context.value === value;
}
focus() {
this.elem.focus();
}
scrollIntoView() {
this.elem.scrollIntoView({
behavior: "smooth",
inline: "center",
});
}
@boundMethod
onClick(evt: React.MouseEvent<HTMLElement>) {
const { value, active, disabled, onClick } = this.props;
const { onChange } = this.context;
if (disabled || active) return;
if (onClick) onClick(evt);
if (onChange) onChange(value);
}
@boundMethod
onFocus(evt: React.FocusEvent<HTMLElement>) {
const { onFocus } = this.props;
if (onFocus) onFocus(evt);
this.scrollIntoView();
}
@boundMethod
onKeyDown(evt: React.KeyboardEvent<HTMLElement>) {
const ENTER_KEY = evt.keyCode === 13;
const SPACE_KEY = evt.keyCode === 32;
if (SPACE_KEY || ENTER_KEY) this.elem.click();
const { onKeyDown } = this.props;
if (onKeyDown) onKeyDown(evt);
}
componentDidMount() {
if (this.isActive && this.context.autoFocus) {
this.focus();
}
}
@boundMethod
protected bindRef(elem: HTMLElement) {
this.elem = elem;
}
render() {
const { active, disabled, icon, label, value, ...elemProps } = this.props;
let { className } = this.props;
className = cssNames("Tab flex gaps align-center", className, {
"active": this.isActive,
disabled,
});
return (
<div
{...elemProps}
className={className}
tabIndex={0}
onClick={this.onClick}
onFocus={this.onFocus}
onKeyDown={this.onKeyDown}
ref={this.bindRef}
>
{typeof icon === "string" ? <Icon small material={icon}/> : icon}
<div className="label">
{label}
</div>
</div>
);
}
}