mirror of
https://github.com/lensapp/lens.git
synced 2025-05-20 05:10:56 +00:00
Lens app source code (#119)
Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
1 parent
936cbd53d4
commit
1d0815abd2
797 files changed
+73714
-215
No files matched your search
@@ -0,0 +1,2 @@
|
||||
export * from './tooltip'
|
||||
export * from './withTooltip'
|
||||
@@ -0,0 +1,104 @@
|
||||
|
||||
.Tooltip {
|
||||
--tooltip-bgc: #{$contentColor};
|
||||
--tooltip-color: #{$textColorSecondary};
|
||||
--tooltip-margin: #{$padding / 2 $padding / 3};
|
||||
|
||||
position: absolute;
|
||||
background: var(--tooltip-bgc);
|
||||
font-size: small;
|
||||
font-weight: normal;
|
||||
border: 1px solid $borderColor;
|
||||
border-radius: $radius;
|
||||
color: var(--tooltip-color);
|
||||
margin: var(--tooltip-margin);
|
||||
white-space: normal;
|
||||
padding: .5em;
|
||||
text-align: center;
|
||||
transition-delay: 100ms; // delay for .Animate
|
||||
pointer-events: none;
|
||||
z-index: 1000;
|
||||
|
||||
* {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
&:empty {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.following {
|
||||
position: fixed;
|
||||
}
|
||||
|
||||
&:not(.following) {
|
||||
&.left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&.right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&.top {
|
||||
bottom: 100%;
|
||||
}
|
||||
|
||||
&.bottom {
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
&.center {
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.TooltipContent {
|
||||
&.nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&.narrow {
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
&.small {
|
||||
font-size: $font-size-small;
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: $colorError;
|
||||
}
|
||||
|
||||
&.tableView {
|
||||
min-width: 200px;
|
||||
|
||||
> :not(:last-child) {
|
||||
margin-bottom: var(--flex-gap);
|
||||
}
|
||||
|
||||
.title {
|
||||
color: $textColorAccent;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.name {
|
||||
color: $textColorAccent;
|
||||
text-align: right;
|
||||
flex: 0 0 35%;
|
||||
}
|
||||
|
||||
.value {
|
||||
text-align: left;
|
||||
max-width: 300px;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import './tooltip.scss'
|
||||
|
||||
import React from "react"
|
||||
import { observer } from "mobx-react";
|
||||
import { observable } from "mobx";
|
||||
import { createPortal } from "react-dom"
|
||||
import { autobind, cssNames } from "../../utils";
|
||||
import { Animate } from "../animate";
|
||||
|
||||
export interface TooltipProps {
|
||||
htmlFor: string;
|
||||
className?: string;
|
||||
position?: Position;
|
||||
useAnimation?: boolean;
|
||||
following?: boolean; // tooltip is following mouse position
|
||||
style?: React.CSSProperties;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface Position {
|
||||
left?: boolean;
|
||||
right?: boolean;
|
||||
top?: boolean;
|
||||
bottom?: boolean;
|
||||
center?: boolean;
|
||||
}
|
||||
|
||||
const defaultProps: Partial<TooltipProps> = {
|
||||
useAnimation: true,
|
||||
position: {
|
||||
center: true,
|
||||
bottom: true,
|
||||
}
|
||||
};
|
||||
|
||||
@observer
|
||||
export class Tooltip extends React.Component<TooltipProps> {
|
||||
static defaultProps = defaultProps as object;
|
||||
|
||||
public anchor: HTMLElement;
|
||||
public elem: HTMLElement;
|
||||
|
||||
@observable isVisible = false;
|
||||
|
||||
componentDidMount() {
|
||||
const { htmlFor } = this.props;
|
||||
this.anchor = htmlFor ? document.getElementById(htmlFor) : this.elem.parentElement;
|
||||
if (this.anchor) {
|
||||
if (window.getComputedStyle(this.anchor).position === "static") {
|
||||
this.anchor.style.position = "relative"
|
||||
}
|
||||
this.anchor.addEventListener("mouseenter", this.onMouseEnter);
|
||||
this.anchor.addEventListener("mouseleave", this.onMouseLeave);
|
||||
this.anchor.addEventListener("mousemove", this.onMouseMove);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.anchor) {
|
||||
this.anchor.removeEventListener("mouseenter", this.onMouseEnter);
|
||||
this.anchor.removeEventListener("mouseleave", this.onMouseLeave);
|
||||
this.anchor.removeEventListener("mousemove", this.onMouseMove);
|
||||
}
|
||||
}
|
||||
|
||||
@autobind()
|
||||
onMouseEnter(evt: MouseEvent) {
|
||||
this.isVisible = true;
|
||||
this.onMouseMove(evt);
|
||||
}
|
||||
|
||||
@autobind()
|
||||
onMouseLeave(evt: MouseEvent) {
|
||||
this.isVisible = false;
|
||||
}
|
||||
|
||||
@autobind()
|
||||
onMouseMove(evt: MouseEvent) {
|
||||
if (!this.props.following) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offsetX = -10;
|
||||
const offsetY = 10;
|
||||
const { clientX, clientY } = evt;
|
||||
const { innerWidth, innerHeight } = window;
|
||||
|
||||
const initialPos: Partial<CSSStyleDeclaration> = {
|
||||
top: "auto",
|
||||
left: "auto",
|
||||
right: (innerWidth - clientX + offsetX) + "px",
|
||||
bottom: (innerHeight - clientY + offsetY) + "px",
|
||||
}
|
||||
|
||||
Object.assign(this.elem.style, initialPos);
|
||||
|
||||
// correct position if not fits to viewport
|
||||
const { left, top } = this.elem.getBoundingClientRect();
|
||||
if (left < 0) {
|
||||
this.elem.style.left = clientX + offsetX + "px";
|
||||
this.elem.style.right = "auto"
|
||||
}
|
||||
if (top < 0) {
|
||||
this.elem.style.top = clientY + offsetY + "px";
|
||||
this.elem.style.bottom = "auto"
|
||||
}
|
||||
}
|
||||
|
||||
@autobind()
|
||||
bindRef(elem: HTMLElement) {
|
||||
this.elem = elem;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isVisible } = this;
|
||||
const { useAnimation, position, following, style, children } = this.props;
|
||||
let { className } = this.props;
|
||||
className = cssNames('Tooltip', position, { following }, className);
|
||||
const tooltip = (
|
||||
<Animate enter={isVisible} enabled={useAnimation}>
|
||||
<div className={className} ref={this.bindRef} style={style}>
|
||||
{children}
|
||||
</div>
|
||||
</Animate>
|
||||
);
|
||||
if (following) {
|
||||
return createPortal(tooltip, document.body);
|
||||
}
|
||||
return tooltip;
|
||||
}
|
||||
}
|
||||
|
||||
interface TooltipContentProps {
|
||||
className?: string;
|
||||
narrow?: boolean; // max-width
|
||||
warning?: boolean; // color
|
||||
small?: boolean; // font-size
|
||||
nowrap?: boolean; // white-space
|
||||
tableView?: boolean;
|
||||
}
|
||||
|
||||
export class TooltipContent extends React.Component<TooltipContentProps> {
|
||||
render() {
|
||||
const { className, children, ...modifiers } = this.props;
|
||||
return (
|
||||
<div className={cssNames("TooltipContent", className, modifiers)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
// Tooltip decorator for simple composition with other components
|
||||
|
||||
import React, { HTMLAttributes, ReactNode } from "react"
|
||||
import hoistNonReactStatics from "hoist-non-react-statics";
|
||||
import { Tooltip, TooltipProps } from "./tooltip";
|
||||
import { isReactNode } from "../../utils/isReactNode";
|
||||
import uniqueId from "lodash/uniqueId"
|
||||
|
||||
export interface TooltipDecoratorProps {
|
||||
tooltip?: ReactNode | Omit<TooltipProps, "htmlFor">;
|
||||
}
|
||||
|
||||
export function withTooltip<T extends React.ComponentType<any>>(Target: T): T {
|
||||
const DecoratedComponent = class extends React.Component<HTMLAttributes<any> & TooltipDecoratorProps> {
|
||||
static displayName = `withTooltip(${Target.displayName || Target.name})`;
|
||||
|
||||
protected tooltipId = uniqueId("tooltip_target_");
|
||||
|
||||
render() {
|
||||
const { tooltip, ...targetProps } = this.props;
|
||||
|
||||
if (tooltip) {
|
||||
const tooltipId = targetProps.id || this.tooltipId;
|
||||
const tooltipProps: TooltipProps = {
|
||||
htmlFor: tooltipId,
|
||||
following: true,
|
||||
...(isReactNode(tooltip) ? { children: tooltip } : tooltip),
|
||||
};
|
||||
if (!tooltipProps.following) {
|
||||
targetProps.style = {
|
||||
position: "relative",
|
||||
...(targetProps.style || {})
|
||||
}
|
||||
}
|
||||
targetProps.id = tooltipId;
|
||||
targetProps.children = (
|
||||
<>
|
||||
{targetProps.children}
|
||||
<Tooltip {...tooltipProps}/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return <Target {...targetProps as any}/>;
|
||||
}
|
||||
}
|
||||
|
||||
return hoistNonReactStatics(DecoratedComponent, Target) as any;
|
||||
}
|
||||
Reference in new issue
Block a user