Replacing <Avatar/> component by native one (#4452)

This commit is contained in:
Alex Andreev authored and GitHub committed 2021-12-01 08:28:56 -05:00
1 parent 80eeffc229
commit 9ce91884c2
19 files changed
+337 -261

No files matched your search

@@ -0,0 +1,56 @@
/**
* 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.
*/
.led {
position: absolute;
left: 3px;
top: 3px;
background-color: var(--layoutBackground);
border: 1px solid var(--clusterMenuBackground);
border-radius: 50%;
padding: 0px;
width: 8px;
height: 8px;
&.online {
background-color: var(--primary);
box-shadow: 0 0 5px var(--clusterMenuBackground), 0 0 5px var(--primary);
}
}
.badge {
position: absolute;
right: -2px;
bottom: -3px;
margin: -8px;
font-size: var(--font-size-small);
background: var(--clusterMenuBackground);
color: var(--textColorAccent);
padding: 0px;
border-radius: 50%;
border: 2px solid var(--clusterMenuBackground);
width: 15px;
height: 15px;
svg {
width: 13px;
}
}
@@ -19,7 +19,9 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import React, { DOMAttributes } from "react";
import styles from "./hotbar-entity-icon.module.css";
import React, { HTMLAttributes } from "react";
import { makeObservable, observable } from "mobx";
import { observer } from "mobx-react";
@@ -32,10 +34,9 @@ import { Icon } from "../icon";
import { HotbarIcon } from "./hotbar-icon";
import { LensKubernetesClusterStatus } from "../../../common/catalog-entities/kubernetes-cluster";
interface Props extends DOMAttributes<HTMLElement> {
interface Props extends HTMLAttributes<HTMLElement> {
entity: CatalogEntity;
index: number;
className?: IClassName;
errorClass?: IClassName;
add: (item: CatalogEntity, index: number) => void;
remove: (uid: string) => void;
@@ -55,7 +56,7 @@ export class HotbarEntityIcon extends React.Component<Props> {
}
get kindIcon() {
const className = "badge";
const className = styles.badge;
const category = catalogCategoryRegistry.getCategoryForEntity(this.props.entity);
if (!category) {
@@ -74,7 +75,7 @@ export class HotbarEntityIcon extends React.Component<Props> {
return null;
}
const className = cssNames("led", { online: this.props.entity.status.phase === LensKubernetesClusterStatus.CONNECTED }); // TODO: make it more generic
const className = cssNames(styles.led, { [styles.online]: this.props.entity.status.phase === LensKubernetesClusterStatus.CONNECTED }); // TODO: make it more generic
return <div className={className} />;
}
@@ -83,34 +84,25 @@ export class HotbarEntityIcon extends React.Component<Props> {
return catalogEntityRegistry.activeEntity?.metadata?.uid == item.getId();
}
async onMenuOpen() {
const menuItems: CatalogEntityContextMenu[] = [];
menuItems.unshift({
title: "Remove from Hotbar",
onClick: () => this.props.remove(this.props.entity.metadata.uid),
});
this.contextMenu.menuItems = menuItems;
await this.props.entity.onContextMenuOpen(this.contextMenu);
}
render() {
if (!this.contextMenu) {
return null;
}
const {
entity, errorClass, add, remove,
index, children, ...elemProps
} = this.props;
const className = cssNames("HotbarEntityIcon", this.props.className, {
interactive: true,
active: this.isActive(entity),
disabled: !entity,
});
const onOpen = async () => {
const menuItems: CatalogEntityContextMenu[] = [];
menuItems.unshift({
title: "Remove from Hotbar",
onClick: () => remove(entity.metadata.uid),
});
this.contextMenu.menuItems = menuItems;
await entity.onContextMenuOpen(this.contextMenu);
};
const isActive = this.isActive(entity);
const { entity, errorClass, add, remove, index, children, ...elemProps } = this.props;
return (
<HotbarIcon
@@ -120,9 +112,10 @@ export class HotbarEntityIcon extends React.Component<Props> {
src={entity.spec.icon?.src}
material={entity.spec.icon?.material}
background={entity.spec.icon?.background}
className={className}
active={isActive}
onMenuOpen={onOpen}
className={this.props.className}
active={this.isActive(entity)}
onMenuOpen={() => this.onMenuOpen()}
disabled={!entity}
menuItems={this.contextMenu.menuItems}
tooltip={`${entity.metadata.name} (${entity.metadata.source})`}
{...elemProps}
@@ -0,0 +1,47 @@
/**
* 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.
*/
.HotbarIcon {
--iconActiveShadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px var(--textColorAccent);
--iconHoverShadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px #ffffff50;
display: flex;
cursor: pointer;
position: relative;
border-radius: 6px;
transition: box-shadow 0.1s ease-in-out;
&:not(.active):hover {
box-shadow: var(--iconHoverShadow);
}
}
.contextMenuAvailable {
cursor: context-menu;
}
.avatar {
border-radius: 6px;
}
.active {
box-shadow: var(--iconActiveShadow);
}
@@ -1,131 +0,0 @@
/**
* 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.
*/
.HotbarMenu {
.HotbarIconMenu {
left: 30px;
min-width: 250px;
li.MenuItem {
font-size: 0.9em;
}
}
}
.HotbarIcon {
--iconActiveShadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px var(--textColorAccent);
--iconHoverShadow: 0 0 0px 3px var(--clusterMenuBackground), 0 0 0px 6px #ffffff50;
border-radius: 6px;
user-select: none;
cursor: pointer;
transition: none;
text-shadow: 0 0 4px #0000008f;
position: relative;
z-index: 0; // allows to catch state of :active pseudo-selector
&:active .MuiAvatar-root {
box-shadow: var(--iconActiveShadow) !important;
}
div.MuiAvatar-colorDefault {
font-weight:500;
text-transform: uppercase;
border-radius: 6px;
}
&.disabled {
opacity: 0.4;
cursor: default;
filter: grayscale(0.7);
&.contextMenuAvailable {
cursor: context-menu;
}
&:hover {
&:not(.active) {
box-shadow: none;
}
}
}
&.isDragging {
box-shadow: none;
}
.MuiAvatar-root {
width: var(--size);
height: var(--size);
border-radius: 6px;
&.active {
box-shadow: var(--iconActiveShadow);
}
&.interactive:not(.active):hover {
box-shadow: var(--iconHoverShadow);
}
}
.led {
position: absolute;
left: 3px;
top: 3px;
background-color: var(--layoutBackground);
border: 1px solid var(--clusterMenuBackground);
border-radius: 50%;
padding: 0px;
width: 8px;
height: 8px;
&.online {
background-color: var(--primary);
box-shadow: 0 0 5px var(--clusterMenuBackground), 0 0 5px var(--primary);
}
}
.badge {
position: absolute;
right: -2px;
bottom: -3px;
margin: -8px;
font-size: var(--font-size-small);
background: var(--clusterMenuBackground);
color: var(--textColorAccent);
padding: 0px;
border-radius: 50%;
border: 2px solid var(--clusterMenuBackground);
width: 15px;
height: 15px;
svg {
width: 13px;
}
}
.materialIcon {
margin-left: 1px;
margin-top: 1px;
text-shadow: none;
font-size: 180%;
}
}
+19 -32
View File
@@ -19,32 +19,27 @@
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
import "./hotbar-icon.scss";
import styles from "./hotbar-icon.module.css";
import React, { DOMAttributes, useState } from "react";
import React, { useState } from "react";
import type { CatalogEntityContextMenu } from "../../../common/catalog";
import { cssNames, IClassName } from "../../utils";
import { cssNames } from "../../utils";
import { ConfirmDialog } from "../confirm-dialog";
import { Menu, MenuItem } from "../menu";
import { observer } from "mobx-react";
import { Avatar } from "../avatar/avatar";
import { Avatar, AvatarProps } from "../avatar";
import { Icon } from "../icon";
import { Tooltip } from "../tooltip";
export interface HotbarIconProps extends DOMAttributes<HTMLElement> {
export interface Props extends AvatarProps {
uid: string;
title: string;
source: string;
src?: string;
material?: string;
onMenuOpen?: () => void;
className?: IClassName;
active?: boolean;
menuItems?: CatalogEntityContextMenu[];
disabled?: boolean;
size?: number;
background?: string;
tooltip?: string;
}
@@ -65,7 +60,7 @@ function onMenuItemClick(menuItem: CatalogEntityContextMenu) {
}
}
export const HotbarIcon = observer(({ menuItems = [], size = 40, tooltip, ...props }: HotbarIconProps) => {
export const HotbarIcon = observer(({ menuItems = [], size = 40, tooltip, ...props }: Props) => {
const { uid, title, src, material, active, className, source, disabled, onMenuOpen, onClick, children, ...rest } = props;
const id = `hotbarIcon-${uid}`;
const [menuOpen, setMenuOpen] = useState(false);
@@ -75,33 +70,25 @@ export const HotbarIcon = observer(({ menuItems = [], size = 40, tooltip, ...pro
};
return (
<div className={cssNames("HotbarIcon flex", className, { disabled, contextMenuAvailable: menuItems.length > 0 })}>
<div className={cssNames(styles.HotbarIcon, className, { [styles.contextMenuAvailable]: menuItems.length > 0 })}>
{tooltip && <Tooltip targetId={id}>{tooltip}</Tooltip>}
<div
<Avatar
{...rest}
id={id}
onClick={(event) => {
if (!disabled) {
onClick?.(event);
}
}}
title={title}
colorHash={`${title}-${source}`}
className={cssNames(styles.avatar, { [styles.active]: active })}
disabled={disabled}
size={size}
src={src}
onClick={(event) => !disabled && onClick?.(event)}
>
<Avatar
{...rest}
title={title}
colorHash={`${title}-${source}`}
className={cssNames(active ? "active" : "default", { interactive: !!onClick })}
width={size}
height={size}
src={src}
>
{material && <Icon className="materialIcon" material={material} />}
</Avatar>
{children}
</div>
{material && <Icon material={material} />}
</Avatar>
{children}
<Menu
usePortal
htmlFor={id}
className="HotbarIconMenu"
isOpen={menuOpen}
toggleEvent="contextmenu"
position={{ right: true, bottom: true }} // FIXME: position does not work
@@ -105,7 +105,7 @@
}
&:not(:empty) {
.HotbarIcon {
> div {
animation: click .1s;
}
}