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,5 @@
|
||||
export * from './table'
|
||||
export * from './table-head'
|
||||
export * from './table-row'
|
||||
export * from './table-cell'
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
.TableCell {
|
||||
padding: $padding;
|
||||
word-break: break-all;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
&.checkbox {
|
||||
display: flex;
|
||||
flex: 0 0 32px !important;
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
|
||||
&:not(.disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
> .Checkbox {
|
||||
margin: 1px auto;
|
||||
}
|
||||
}
|
||||
|
||||
// fix: don't hide "sortIcon" when used with <TableHead nowrap> and there is not enough space to show all the content
|
||||
&.sorting.nowrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
> .content {
|
||||
flex-shrink: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
> .sortIcon {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Table.sortable > .TableHead & {
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
|
||||
&.sorting {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sortIcon {
|
||||
transition: 350ms opacity;
|
||||
opacity: .3;
|
||||
|
||||
&.enabled {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: $primary;
|
||||
transition: 150ms color;
|
||||
|
||||
&:hover {
|
||||
color: $textColorAccent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import "./table-cell.scss";
|
||||
|
||||
import React, { ReactNode } from "react";
|
||||
import { autobind, cssNames } from "../../utils";
|
||||
import { SortBy, SortParams } from "./table";
|
||||
import { Icon } from "../icon";
|
||||
import { Checkbox } from "../checkbox";
|
||||
|
||||
export type TableCellElem = React.ReactElement<TableCellProps>;
|
||||
|
||||
export interface TableCellProps extends React.DOMAttributes<HTMLDivElement> {
|
||||
className?: string;
|
||||
title?: ReactNode;
|
||||
checkbox?: boolean; // render cell with a checkbox
|
||||
isChecked?: boolean; // mark checkbox as checked or not
|
||||
sortBy?: SortBy; // column name, must be same as key in sortable object <Table sortable={}/>
|
||||
_sorting?: Partial<SortParams>; // <Table> sorting state, don't use this prop outside (!)
|
||||
_sort?(sortBy: SortBy): void; // <Table> sort function, don't use this prop outside (!)
|
||||
_nowrap?: boolean; // indicator, might come from parent <TableHead>, don't use this prop outside (!)
|
||||
}
|
||||
|
||||
export class TableCell extends React.Component<TableCellProps> {
|
||||
@autobind()
|
||||
onClick(evt: React.MouseEvent<HTMLDivElement>) {
|
||||
if (this.props.onClick) {
|
||||
this.props.onClick(evt);
|
||||
}
|
||||
if (this.isSortable) {
|
||||
this.props._sort(this.props.sortBy);
|
||||
}
|
||||
}
|
||||
|
||||
get isSortable() {
|
||||
const { _sorting, sortBy } = this.props;
|
||||
return _sorting && sortBy !== undefined;
|
||||
}
|
||||
|
||||
renderSortIcon() {
|
||||
const { sortBy, _sorting } = this.props;
|
||||
if (!this.isSortable) return;
|
||||
const sortActive = _sorting.sortBy === sortBy;
|
||||
const sortIconName = (!sortActive || _sorting.orderBy === "desc") ? "arrow_drop_down" : "arrow_drop_up";
|
||||
return (
|
||||
<Icon
|
||||
className={cssNames("sortIcon", { enabled: sortActive })}
|
||||
material={sortIconName}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
renderCheckbox() {
|
||||
const { checkbox, isChecked } = this.props;
|
||||
const showCheckbox = isChecked !== undefined;
|
||||
if (checkbox && showCheckbox) {
|
||||
return <Checkbox value={isChecked}/>
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, checkbox, isChecked, sortBy, _sort, _sorting, _nowrap, children, title, ...cellProps } = this.props;
|
||||
const classNames = cssNames("TableCell", className, {
|
||||
checkbox: checkbox,
|
||||
nowrap: _nowrap,
|
||||
sorting: this.isSortable,
|
||||
});
|
||||
const content = title || children;
|
||||
return (
|
||||
<div {...cellProps} className={classNames} onClick={this.onClick}>
|
||||
{this.renderCheckbox()}
|
||||
{_nowrap ? <div className="content">{content}</div> : content}
|
||||
{this.renderSortIcon()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
.TableHead {
|
||||
$border: 1px solid $layoutBackground;
|
||||
|
||||
background-color: $tableHeaderBackground;
|
||||
border-bottom-width: $tableHeaderBorderWidth;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: $tableHeaderBorderColor;
|
||||
color: $tableHeaderColor;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.topLine {
|
||||
border-top: $border;
|
||||
}
|
||||
|
||||
&.sticky {
|
||||
position: -webkit-sticky; // safari
|
||||
position: sticky;
|
||||
z-index: 1;
|
||||
top: 0; // turn on sticky behaviour
|
||||
}
|
||||
|
||||
&.nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.TableCell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.checkbox {
|
||||
> .Checkbox {
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import "./table-head.scss";
|
||||
|
||||
import * as React from "react";
|
||||
import { cssNames } from "../../utils";
|
||||
|
||||
export type TableHeadElem = React.ReactElement<TableHeadProps>;
|
||||
|
||||
export interface TableHeadProps extends React.DOMAttributes<HTMLDivElement> {
|
||||
className?: string;
|
||||
showTopLine?: boolean; // show border line at the top
|
||||
sticky?: boolean; // keep header on top when scrolling
|
||||
nowrap?: boolean; // white-space: nowrap, align inner <TableCell> in one line
|
||||
}
|
||||
|
||||
export class TableHead extends React.Component<TableHeadProps> {
|
||||
static defaultProps: TableHeadProps = {
|
||||
sticky: true,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { className, sticky, nowrap, showTopLine, children, ...headProps } = this.props;
|
||||
const classNames = cssNames("TableHead", className, {
|
||||
sticky, nowrap,
|
||||
topLine: showTopLine,
|
||||
});
|
||||
return (
|
||||
<div className={classNames} {...headProps}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
.TableRow {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
|
||||
&.disabled {
|
||||
opacity: .5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.nowrap {
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import "./table-row.scss";
|
||||
|
||||
import React, { CSSProperties } from "react";
|
||||
import { cssNames } from "../../utils";
|
||||
import { ItemObject } from "../../item.store";
|
||||
|
||||
export type TableRowElem = React.ReactElement<TableRowProps>;
|
||||
|
||||
export interface TableRowProps extends React.DOMAttributes<HTMLDivElement> {
|
||||
className?: string;
|
||||
selected?: boolean;
|
||||
style?: CSSProperties;
|
||||
nowrap?: boolean; // white-space: nowrap, align inner <TableCell> in one line
|
||||
sortItem?: ItemObject | any; // data for sorting callback in <Table sortable={}/>
|
||||
searchItem?: ItemObject | any; // data for searching filters in <Table searchable={}/>
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export class TableRow extends React.Component<TableRowProps> {
|
||||
render() {
|
||||
const { className, nowrap, selected, disabled, children, sortItem, searchItem, ...rowProps } = this.props;
|
||||
const classNames = cssNames("TableRow", className, { selected, nowrap, disabled });
|
||||
return (
|
||||
<div className={classNames} {...rowProps}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
@mixin table-cell-action {
|
||||
display: flex;
|
||||
padding: 0 !important;
|
||||
flex: 0 0 55px !important;
|
||||
|
||||
> * {
|
||||
margin: floor($margin / 1.3) auto;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin table-cell-warning {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-grow: 0.3;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@mixin table-cell-labels {
|
||||
.TableRow {
|
||||
.TableCell {
|
||||
&.labels {
|
||||
@include table-cell-labels-offsets;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin table-cell-labels-offsets {
|
||||
padding-top: $padding / 2;
|
||||
padding-bottom: 0;
|
||||
|
||||
.Badge + .Badge {
|
||||
margin-left: $padding / 2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
.Table {
|
||||
&.scrollable {
|
||||
&.light {
|
||||
@include custom-scrollbar(dark);
|
||||
}
|
||||
|
||||
@include custom-scrollbar();
|
||||
flex: 1 0 0; // hackfix: flex-basis must be "0" for proper work in firefox
|
||||
}
|
||||
|
||||
&.autoSize {
|
||||
.TableCell {
|
||||
flex: 1 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.selectable {
|
||||
.TableHead, .TableRow {
|
||||
padding: 0 $padding;
|
||||
}
|
||||
|
||||
.TableRow {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: $tableBgcSelected !important;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: $tableBgcSelected;
|
||||
color: $tableSelectedRowColor;
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
background: $primary;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.striped {
|
||||
.TableRow {
|
||||
&:nth-child(odd) {
|
||||
background-color: $tableBgcStripe;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
import "./table.scss";
|
||||
|
||||
import * as React from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { computed, observable } from "mobx";
|
||||
import { autobind, cssNames, noop } from "../../utils";
|
||||
import { TableRow, TableRowElem, TableRowProps } from "./table-row";
|
||||
import { TableHead, TableHeadElem, TableHeadProps } from "./table-head";
|
||||
import { TableCellElem } from "./table-cell";
|
||||
import { VirtualList } from "../virtual-list";
|
||||
import { navigation, setQueryParams } from "../../navigation";
|
||||
import orderBy from "lodash/orderBy"
|
||||
import { ItemObject } from "../../item.store";
|
||||
|
||||
// todo: refactor + decouple search from location
|
||||
|
||||
export type SortBy = string;
|
||||
export type OrderBy = "asc" | "desc" | string;
|
||||
export type SortParams = { sortBy: SortBy; orderBy: OrderBy }
|
||||
export type SortingCallback<D = any> = (data: D) => string | number | (string | number)[];
|
||||
|
||||
export interface TableProps extends React.DOMAttributes<HTMLDivElement> {
|
||||
items?: ItemObject[]; // Raw items data
|
||||
className?: string;
|
||||
autoSize?: boolean; // Setup auto-sizing for all columns (flex: 1 0)
|
||||
selectable?: boolean; // Highlight rows on hover
|
||||
scrollable?: boolean; // Use scrollbar if content is bigger than parent's height
|
||||
storageKey?: string; // Keep some data in localStorage & restore on page reload, e.g sorting params
|
||||
sortable?: {
|
||||
// Define sortable callbacks for every column in <TableHead><TableCell sortBy="someCol"><TableHead>
|
||||
// @sortItem argument in the callback is an object, provided in <TableRow sortItem={someColDataItem}/>
|
||||
[sortBy: string]: SortingCallback;
|
||||
};
|
||||
sortSyncWithUrl?: boolean; // sorting state is managed globally from url params
|
||||
sortByDefault?: Partial<SortParams>; // default sorting params
|
||||
onSort?: (params: SortParams) => void; // callback on sort change, default: global sync with url
|
||||
noItems?: React.ReactNode; // Show no items state table list is empty
|
||||
selectedItemId?: string; // Allows to scroll list to selected item
|
||||
virtual?: boolean; // Use virtual list component to render only visible rows
|
||||
rowPadding?: string;
|
||||
rowLineHeight?: string;
|
||||
customRowHeights?: (item: object, lineHeight: number, paddings: number) => number;
|
||||
getTableRow?: (uid: string) => React.ReactElement<TableRowProps>;
|
||||
}
|
||||
|
||||
@observer
|
||||
export class Table extends React.Component<TableProps> {
|
||||
static defaultProps: TableProps = {
|
||||
scrollable: true,
|
||||
autoSize: true,
|
||||
rowPadding: "8px",
|
||||
rowLineHeight: "17px",
|
||||
sortSyncWithUrl: true,
|
||||
}
|
||||
|
||||
@observable sortParamsLocal = this.props.sortByDefault;
|
||||
|
||||
@computed get sortParams(): Partial<SortParams> {
|
||||
if (this.props.sortSyncWithUrl) {
|
||||
const sortBy = navigation.searchParams.get("sortBy")
|
||||
const orderBy = navigation.searchParams.get("orderBy")
|
||||
return { sortBy, orderBy };
|
||||
}
|
||||
return this.sortParamsLocal || {};
|
||||
}
|
||||
|
||||
renderHead() {
|
||||
const { sortable, children } = this.props;
|
||||
const content = React.Children.toArray(children) as (TableRowElem | TableHeadElem)[];
|
||||
const headElem: React.ReactElement<TableHeadProps> = content.find(elem => elem.type === TableHead);
|
||||
if (headElem) {
|
||||
if (sortable) {
|
||||
const columns = React.Children.toArray(headElem.props.children) as TableCellElem[];
|
||||
return React.cloneElement(headElem, {
|
||||
children: columns.map(elem => {
|
||||
if (elem.props.checkbox) {
|
||||
return elem;
|
||||
}
|
||||
const title = elem.props.title || (
|
||||
// copy cell content to title if it's a string
|
||||
// usable if part of TableCell's content is hidden when there is not enough space
|
||||
typeof elem.props.children === "string" ? elem.props.children : undefined
|
||||
);
|
||||
return React.cloneElement(elem, {
|
||||
title: title,
|
||||
_sort: this.sort,
|
||||
_sorting: this.sortParams,
|
||||
_nowrap: headElem.props.nowrap,
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
return headElem;
|
||||
}
|
||||
}
|
||||
|
||||
getSorted(items: any[]) {
|
||||
const { sortParams } = this;
|
||||
const sortingCallback = this.props.sortable[sortParams.sortBy] || noop;
|
||||
return orderBy(
|
||||
items,
|
||||
sortingCallback,
|
||||
sortParams.orderBy as any
|
||||
);
|
||||
}
|
||||
|
||||
@autobind()
|
||||
protected onSort(params: SortParams) {
|
||||
const { sortSyncWithUrl, onSort } = this.props;
|
||||
if (sortSyncWithUrl) {
|
||||
setQueryParams(params)
|
||||
}
|
||||
else {
|
||||
this.sortParamsLocal = params;
|
||||
}
|
||||
if (onSort) {
|
||||
onSort(params)
|
||||
}
|
||||
}
|
||||
|
||||
@autobind()
|
||||
sort(colName: SortBy) {
|
||||
const { sortBy, orderBy } = this.sortParams;
|
||||
const sameColumn = sortBy == colName;
|
||||
const newSortBy: SortBy = colName;
|
||||
const newOrderBy: OrderBy = (!orderBy || !sameColumn || orderBy === "desc") ? "asc" : "desc";
|
||||
this.onSort({
|
||||
sortBy: String(newSortBy),
|
||||
orderBy: newOrderBy,
|
||||
});
|
||||
}
|
||||
|
||||
renderRows() {
|
||||
const { sortable, noItems, children, virtual, customRowHeights, rowLineHeight, rowPadding, items, getTableRow, selectedItemId, className } = this.props;
|
||||
const content = React.Children.toArray(children) as (TableRowElem | TableHeadElem)[];
|
||||
let rows: React.ReactElement<TableRowProps>[] = content.filter(elem => elem.type === TableRow);
|
||||
let sortedItems = rows.length ? rows.map(row => row.props.sortItem) : [...items];
|
||||
if (sortable) {
|
||||
sortedItems = this.getSorted(sortedItems);
|
||||
if (rows.length) {
|
||||
rows = sortedItems.map(item => rows.find(row => {
|
||||
return item == row.props.sortItem
|
||||
}))
|
||||
}
|
||||
}
|
||||
if (!rows.length && !items.length && noItems) {
|
||||
return noItems;
|
||||
}
|
||||
if (virtual) {
|
||||
const lineHeight = parseFloat(rowLineHeight);
|
||||
const padding = parseFloat(rowPadding);
|
||||
let rowHeights: number[] = Array(items.length).fill(lineHeight + padding * 2);
|
||||
if (customRowHeights) {
|
||||
rowHeights = sortedItems.map(item => {
|
||||
return customRowHeights(item, lineHeight, padding * 2);
|
||||
});
|
||||
}
|
||||
return (
|
||||
<VirtualList
|
||||
items={sortedItems}
|
||||
rowHeights={rowHeights}
|
||||
getTableRow={getTableRow}
|
||||
selectedItemId={selectedItemId}
|
||||
className={className}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { selectable, scrollable, sortable, autoSize, virtual } = this.props;
|
||||
let { className } = this.props;
|
||||
className = cssNames("Table flex column", className, {
|
||||
selectable, scrollable, sortable, autoSize, virtual,
|
||||
});
|
||||
return (
|
||||
<div className={className}>
|
||||
{this.renderHead()}
|
||||
{this.renderRows()}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user