Ensure all components are React.memo()-ed (#8815)
* Ensure all components are React.memo()-ed * Align composite creation * Adjust * Add Td colSpan & rowSpan attributes
This commit is contained in:
parent
af73c998ad
commit
4ed0167f6d
@ -1,158 +0,0 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ButtonProps } from './types';
|
||||
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Icon from '../Icon';
|
||||
import Spinner from '../Spinner';
|
||||
|
||||
function Button ({ activeOnEnter, children, className = '', dataTestId = '', icon, isBasic, isBusy, isCircular, isDisabled, isFull, isIcon, isSelected, isToplevel, label, onClick, isReadOnly = !onClick, onMouseEnter, onMouseLeave, tabIndex, withoutLink }: ButtonProps): React.ReactElement<ButtonProps> {
|
||||
const _onClick = useCallback(
|
||||
(): void => {
|
||||
!(isBusy || isDisabled) && onClick && onClick();
|
||||
},
|
||||
[isBusy, isDisabled, onClick]
|
||||
);
|
||||
|
||||
const _onMouseEnter = useCallback((): void => {
|
||||
onMouseEnter && onMouseEnter();
|
||||
}, [onMouseEnter]);
|
||||
|
||||
const _onMouseLeave = useCallback((): void => {
|
||||
onMouseLeave && onMouseLeave();
|
||||
}, [onMouseLeave]);
|
||||
|
||||
const listenKeyboard = useCallback((event: KeyboardEvent): void => {
|
||||
if (!isBusy && !isDisabled && event.key === 'Enter') {
|
||||
onClick && onClick();
|
||||
}
|
||||
}, [isBusy, isDisabled, onClick]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeOnEnter) {
|
||||
window.addEventListener('keydown', listenKeyboard, true);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (activeOnEnter) {
|
||||
window.removeEventListener('keydown', listenKeyboard, true);
|
||||
}
|
||||
};
|
||||
}, [activeOnEnter, listenKeyboard]);
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
className={`${className} ui--Button ${label ? 'hasLabel' : ''} ${isBasic ? 'isBasic' : ''} ${isCircular ? 'isCircular' : ''} ${isFull ? 'isFull' : ''} ${isIcon ? 'isIcon' : ''} ${(isBusy || isDisabled) ? 'isDisabled' : ''} ${isBusy ? 'isBusy' : ''} ${isReadOnly ? 'isReadOnly' : ''}${isSelected ? 'isSelected' : ''} ${isToplevel ? 'isToplevel' : ''} ${withoutLink ? 'withoutLink' : ''}`}
|
||||
data-testid={dataTestId}
|
||||
onClick={_onClick}
|
||||
onMouseEnter={_onMouseEnter}
|
||||
onMouseLeave={_onMouseLeave}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
{icon && <Icon icon={icon} />}
|
||||
{label}
|
||||
{children}
|
||||
{isBusy && (
|
||||
<Spinner
|
||||
className='ui--Button-spinner'
|
||||
variant='cover'
|
||||
/>
|
||||
)}
|
||||
</StyledButton>
|
||||
);
|
||||
}
|
||||
|
||||
const ICON_PADDING = 0.5;
|
||||
|
||||
const StyledButton = styled.button`
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
|
||||
&:not(.hasLabel) {
|
||||
padding: 0.7em;
|
||||
|
||||
.ui--Icon {
|
||||
padding: 0.6rem;
|
||||
margin: -0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.isCircular) {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline:0;
|
||||
}
|
||||
|
||||
&.hasLabel {
|
||||
padding: 0.7rem 1.1rem 0.7rem ${1.1 - ICON_PADDING}rem;
|
||||
|
||||
.ui--Icon {
|
||||
margin-right: 0.425rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.isBasic {
|
||||
background: var(--bg-table);
|
||||
}
|
||||
|
||||
&.isCircular {
|
||||
border-radius: 10rem;
|
||||
}
|
||||
|
||||
&.isDisabled, &.isReadOnly {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.isBusy {
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
&.isFull {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.isIcon {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.ui--Button-overlay {
|
||||
background: rgba(253, 252, 251, 0.75);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.ui--Icon {
|
||||
border-radius: 50%;
|
||||
box-sizing: content-box;
|
||||
height: 1rem;
|
||||
margin: -${ICON_PADDING}rem 0;
|
||||
padding: ${ICON_PADDING}rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
&.isDisabled {
|
||||
color: #bcbbba;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Button);
|
||||
@ -1,12 +1,16 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { GroupProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
function ButtonGroup ({ children, className = '', isCentered }: GroupProps): React.ReactElement<GroupProps> {
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
isCentered?: boolean;
|
||||
}
|
||||
|
||||
function ButtonGroup ({ children, className = '', isCentered }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Button-Group ${isCentered ? 'isCentered' : ''}`}>
|
||||
{children}
|
||||
|
||||
@ -1,12 +1,164 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ButtonType } from './types';
|
||||
import type { ButtonProps as Props } from './types';
|
||||
|
||||
import IButton from './Button';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Icon from '../Icon';
|
||||
import Spinner from '../Spinner';
|
||||
import Group from './Group';
|
||||
|
||||
const Button = IButton as unknown as ButtonType;
|
||||
function ButtonBase ({ activeOnEnter, children, className = '', dataTestId = '', icon, isBasic, isBusy, isCircular, isDisabled, isFull, isIcon, isSelected, isToplevel, label, onClick, isReadOnly = !onClick, onMouseEnter, onMouseLeave, tabIndex, withoutLink }: Props): React.ReactElement<Props> {
|
||||
const _onClick = useCallback(
|
||||
(): void => {
|
||||
!(isBusy || isDisabled) && onClick && onClick();
|
||||
},
|
||||
[isBusy, isDisabled, onClick]
|
||||
);
|
||||
|
||||
const _onMouseEnter = useCallback((): void => {
|
||||
onMouseEnter && onMouseEnter();
|
||||
}, [onMouseEnter]);
|
||||
|
||||
const _onMouseLeave = useCallback((): void => {
|
||||
onMouseLeave && onMouseLeave();
|
||||
}, [onMouseLeave]);
|
||||
|
||||
const listenKeyboard = useCallback((event: KeyboardEvent): void => {
|
||||
if (!isBusy && !isDisabled && event.key === 'Enter') {
|
||||
onClick && onClick();
|
||||
}
|
||||
}, [isBusy, isDisabled, onClick]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeOnEnter) {
|
||||
window.addEventListener('keydown', listenKeyboard, true);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (activeOnEnter) {
|
||||
window.removeEventListener('keydown', listenKeyboard, true);
|
||||
}
|
||||
};
|
||||
}, [activeOnEnter, listenKeyboard]);
|
||||
|
||||
return (
|
||||
<StyledButton
|
||||
className={`${className} ui--Button ${label ? 'hasLabel' : ''} ${isBasic ? 'isBasic' : ''} ${isCircular ? 'isCircular' : ''} ${isFull ? 'isFull' : ''} ${isIcon ? 'isIcon' : ''} ${(isBusy || isDisabled) ? 'isDisabled' : ''} ${isBusy ? 'isBusy' : ''} ${isReadOnly ? 'isReadOnly' : ''}${isSelected ? 'isSelected' : ''} ${isToplevel ? 'isToplevel' : ''} ${withoutLink ? 'withoutLink' : ''}`}
|
||||
data-testid={dataTestId}
|
||||
onClick={_onClick}
|
||||
onMouseEnter={_onMouseEnter}
|
||||
onMouseLeave={_onMouseLeave}
|
||||
tabIndex={tabIndex}
|
||||
>
|
||||
{icon && <Icon icon={icon} />}
|
||||
{label}
|
||||
{children}
|
||||
{isBusy && (
|
||||
<Spinner
|
||||
className='ui--Button-spinner'
|
||||
variant='cover'
|
||||
/>
|
||||
)}
|
||||
</StyledButton>
|
||||
);
|
||||
}
|
||||
|
||||
const ICON_PADDING = 0.5;
|
||||
|
||||
const StyledButton = styled.button`
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
margin: 0;
|
||||
outline: none;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
text-align: center;
|
||||
|
||||
&:not(.hasLabel) {
|
||||
padding: 0.7em;
|
||||
|
||||
.ui--Icon {
|
||||
padding: 0.6rem;
|
||||
margin: -0.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.isCircular) {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline:0;
|
||||
}
|
||||
|
||||
&.hasLabel {
|
||||
padding: 0.7rem 1.1rem 0.7rem ${1.1 - ICON_PADDING}rem;
|
||||
|
||||
.ui--Icon {
|
||||
margin-right: 0.425rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.isBasic {
|
||||
background: var(--bg-table);
|
||||
}
|
||||
|
||||
&.isCircular {
|
||||
border-radius: 10rem;
|
||||
}
|
||||
|
||||
&.isDisabled, &.isReadOnly {
|
||||
background: none;
|
||||
box-shadow: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.isBusy {
|
||||
cursor: wait;
|
||||
}
|
||||
|
||||
&.isFull {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&.isIcon {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.ui--Button-overlay {
|
||||
background: rgba(253, 252, 251, 0.75);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.ui--Icon {
|
||||
border-radius: 50%;
|
||||
box-sizing: content-box;
|
||||
height: 1rem;
|
||||
margin: -${ICON_PADDING}rem 0;
|
||||
padding: ${ICON_PADDING}rem;
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
&.isDisabled {
|
||||
color: #bcbbba;
|
||||
}
|
||||
`;
|
||||
|
||||
const Button = React.memo(ButtonBase) as unknown as typeof ButtonBase & {
|
||||
Group: typeof Group
|
||||
};
|
||||
|
||||
Button.Group = Group;
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { IconName } from '@fortawesome/fontawesome-svg-core';
|
||||
import type { BareProps } from '../types';
|
||||
|
||||
export type Button$Callback = () => void | Promise<void>;
|
||||
|
||||
@ -29,15 +28,3 @@ export interface ButtonProps {
|
||||
tooltip?: React.ReactNode;
|
||||
withoutLink?: boolean;
|
||||
}
|
||||
|
||||
export type DividerProps = BareProps;
|
||||
|
||||
export interface GroupProps {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
isCentered?: boolean;
|
||||
}
|
||||
|
||||
export type ButtonType = React.ComponentType<ButtonProps> & {
|
||||
Group: React.ComponentType<GroupProps>;
|
||||
};
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BaseProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
function BaseChart ({ children, className = '' }: BaseProps): React.ReactElement<BaseProps> {
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function BaseChart ({ children, className = '' }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Chart`}>
|
||||
{children}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { DoughnutProps } from './types';
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
import React from 'react';
|
||||
import { Doughnut } from 'react-chartjs-2';
|
||||
@ -10,6 +10,18 @@ import { bnToBn } from '@polkadot/util';
|
||||
|
||||
import Base from './Base';
|
||||
|
||||
interface DoughnutValue {
|
||||
colors: string[];
|
||||
label: string;
|
||||
value: number | BN;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
className?: string;
|
||||
size?: number;
|
||||
values: DoughnutValue[];
|
||||
}
|
||||
|
||||
interface Options {
|
||||
colorNormal: string[];
|
||||
colorHover: string[];
|
||||
@ -17,7 +29,7 @@ interface Options {
|
||||
labels: string[];
|
||||
}
|
||||
|
||||
function ChartDoughnut ({ className = '', size = 100, values }: DoughnutProps): React.ReactElement<DoughnutProps> {
|
||||
function ChartDoughnut ({ className = '', size = 100, values }: Props): React.ReactElement<Props> {
|
||||
const options: Options = {
|
||||
colorHover: [],
|
||||
colorNormal: [],
|
||||
@ -33,7 +45,7 @@ function ChartDoughnut ({ className = '', size = 100, values }: DoughnutProps):
|
||||
});
|
||||
|
||||
return (
|
||||
<Base className={className}>
|
||||
<Base className={`${className} ui--Chart-Doughnut`}>
|
||||
<Doughnut
|
||||
data={{
|
||||
datasets: [{
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ChartData, ChartOptions, TooltipItem } from 'chart.js';
|
||||
import type { HorizBarProps, HorizBarValue } from './types';
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Bar } from 'react-chartjs-2';
|
||||
@ -11,6 +11,22 @@ import { bnToBn, isNumber } from '@polkadot/util';
|
||||
|
||||
import { alphaColor } from './utils';
|
||||
|
||||
interface Value {
|
||||
colors: string[];
|
||||
label: string;
|
||||
tooltip?: string;
|
||||
value: number | BN;
|
||||
}
|
||||
|
||||
export interface Props {
|
||||
aspectRatio?: number;
|
||||
className?: string;
|
||||
max?: number;
|
||||
showLabels?: boolean;
|
||||
values: Value[];
|
||||
withColors?: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
chartData?: ChartData;
|
||||
chartOptions?: ChartOptions;
|
||||
@ -26,7 +42,7 @@ interface Config {
|
||||
}[];
|
||||
}
|
||||
|
||||
function calculateOptions (aspectRatio: number, values: HorizBarValue[], jsonValues: string, max: number, showLabels: boolean): State {
|
||||
function calculateOptions (aspectRatio: number, values: Value[], jsonValues: string, max: number, showLabels: boolean): State {
|
||||
const chartData = values.reduce((data, { colors: [normalColor = '#00f', hoverColor], label, value }): Config => {
|
||||
const dataset = data.datasets[0];
|
||||
|
||||
@ -70,7 +86,7 @@ function calculateOptions (aspectRatio: number, values: HorizBarValue[], jsonVal
|
||||
};
|
||||
}
|
||||
|
||||
function ChartHorizBar ({ aspectRatio = 8, className = '', max = 100, showLabels = false, values }: HorizBarProps): React.ReactElement<HorizBarProps> | null {
|
||||
function ChartHorizBar ({ aspectRatio = 8, className = '', max = 100, showLabels = false, values }: Props): React.ReactElement<Props> | null {
|
||||
const [{ chartData, chartOptions, jsonValues }, setState] = useState<State>({});
|
||||
|
||||
useEffect((): void => {
|
||||
@ -87,7 +103,7 @@ function ChartHorizBar ({ aspectRatio = 8, className = '', max = 100, showLabels
|
||||
|
||||
// HACK on width/height to get the aspectRatio to work
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={`${className} ui--Chart-HorizBar`}>
|
||||
<Bar
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
data={chartData as any}
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
|
||||
import type { ChartData, ChartOptions } from 'chart.js';
|
||||
import type { BN } from '@polkadot/util';
|
||||
import type { LineProps } from './types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import * as Chart from 'react-chartjs-2';
|
||||
@ -13,6 +12,15 @@ import { isBn, objectSpread } from '@polkadot/util';
|
||||
import ErrorBoundary from '../ErrorBoundary';
|
||||
import { alphaColor } from './utils';
|
||||
|
||||
export interface Props {
|
||||
colors?: (string | undefined)[];
|
||||
className?: string;
|
||||
labels: string[];
|
||||
legends: string[];
|
||||
options?: ChartOptions;
|
||||
values: (number | BN)[][];
|
||||
}
|
||||
|
||||
interface Dataset {
|
||||
data: number[];
|
||||
fill: boolean;
|
||||
@ -113,7 +121,7 @@ function getData (colors: (string | undefined)[] = [], legends: string[], labels
|
||||
}, { datasets: [] as Dataset[], labels });
|
||||
}
|
||||
|
||||
function LineChart ({ className, colors, labels, legends, options, values }: LineProps): React.ReactElement<LineProps> | null {
|
||||
function LineChart ({ className = '', colors, labels, legends, options, values }: Props): React.ReactElement<Props> | null {
|
||||
const chartOptions = useMemo(
|
||||
() => getOptions(options),
|
||||
[options]
|
||||
@ -125,7 +133,7 @@ function LineChart ({ className, colors, labels, legends, options, values }: Lin
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={`${className} ui--Chart-Line`}>
|
||||
<ErrorBoundary>
|
||||
<Chart.Line
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
|
||||
@ -1,48 +0,0 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
import ChartJs from 'chart.js';
|
||||
|
||||
export interface DoughnutValue {
|
||||
colors: string[];
|
||||
label: string;
|
||||
value: number | BN;
|
||||
}
|
||||
|
||||
export interface HorizBarValue {
|
||||
colors: string[];
|
||||
label: string;
|
||||
tooltip?: string;
|
||||
value: number | BN;
|
||||
}
|
||||
|
||||
export interface BaseProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface DoughnutProps {
|
||||
className?: string;
|
||||
size?: number;
|
||||
values: DoughnutValue[];
|
||||
}
|
||||
|
||||
export interface HorizBarProps {
|
||||
aspectRatio?: number;
|
||||
className?: string;
|
||||
max?: number;
|
||||
showLabels?: boolean;
|
||||
values: HorizBarValue[];
|
||||
withColors?: boolean;
|
||||
}
|
||||
|
||||
export interface LineProps {
|
||||
colors?: (string | undefined)[];
|
||||
className?: string;
|
||||
labels: string[];
|
||||
legends: string[];
|
||||
options?: ChartJs.ChartOptions;
|
||||
values: (number | BN)[][];
|
||||
}
|
||||
@ -57,7 +57,7 @@ function Column ({ children, className = '' }: Props): React.ReactElement<Props>
|
||||
);
|
||||
}
|
||||
|
||||
function Columar ({ children, className = '', is60, is100, isPadded = true, isReverse, size = 'default' }: Props): React.ReactElement<Props> {
|
||||
function ColumarBase ({ children, className = '', is60, is100, isPadded = true, isReverse, size = 'default' }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Columar ${is100 ? 'is100' : (is60 ? 'is60' : 'is50')} ${isPadded ? 'isPadded' : ''} ${isReverse ? 'isReverse' : ''} ${size}Size`}>
|
||||
{children}
|
||||
@ -133,6 +133,10 @@ const StyledDiv = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const Columar = React.memo(ColumarBase) as unknown as typeof ColumarBase & {
|
||||
Column: typeof Column
|
||||
};
|
||||
|
||||
Columar.Column = Column;
|
||||
|
||||
export default Columar;
|
||||
|
||||
@ -44,7 +44,7 @@ export type IDropdown<Option extends DropdownItemProps> = React.ComponentType<Pr
|
||||
Header: React.ComponentType<{ content: React.ReactNode }>;
|
||||
}
|
||||
|
||||
function Dropdown<Option extends DropdownItemProps> ({ allowAdd = false, children, className = '', defaultValue, dropdownClassName, isButton, isDisabled, isError, isFull, isMultiple, label, labelExtra, onAdd, onBlur, onChange, onClose, onSearch, options, placeholder, renderLabel, searchInput, tabIndex, transform, value, withEllipsis, withLabel }: Props<Option>): React.ReactElement<Props<Option>> {
|
||||
function DropdownBase<Option extends DropdownItemProps> ({ allowAdd = false, children, className = '', defaultValue, dropdownClassName, isButton, isDisabled, isError, isFull, isMultiple, label, labelExtra, onAdd, onBlur, onChange, onClose, onSearch, options, placeholder, renderLabel, searchInput, tabIndex, transform, value, withEllipsis, withLabel }: Props<Option>): React.ReactElement<Props<Option>> {
|
||||
const lastUpdate = useRef<string>('');
|
||||
const [stored, setStored] = useState<string | undefined>();
|
||||
|
||||
@ -169,6 +169,10 @@ const StyledLabelled = styled(Labelled)`
|
||||
}
|
||||
`;
|
||||
|
||||
const Dropdown = React.memo(DropdownBase) as unknown as typeof DropdownBase & {
|
||||
Header: typeof SUIDropdown.Header
|
||||
};
|
||||
|
||||
Dropdown.Header = SUIDropdown.Header;
|
||||
|
||||
export default Dropdown;
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BaseProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
function Base ({ children, className = '' }: BaseProps): React.ReactElement<BaseProps> {
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Menu`}>
|
||||
{children}
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 14.286rem;
|
||||
margin: 1rem 0;
|
||||
|
||||
& > *:not(.ui--Menu__Item):not(.ui--Menu__Divider) {
|
||||
margin-right: 1rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Base);
|
||||
@ -1,22 +1,36 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BaseProps, DividerProps, HeaderProps, ItemProps } from './types';
|
||||
import type { BaseProps, MenuType } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import MenuBase from './Base';
|
||||
import Divider from './Divider';
|
||||
import Header from './Header';
|
||||
import Item from './Item';
|
||||
|
||||
type MenuDef = React.FC<BaseProps> & {
|
||||
Divider: React.FC<DividerProps>;
|
||||
Item: React.FC<ItemProps>;
|
||||
Header: React.FC<HeaderProps>;
|
||||
};
|
||||
function Base ({ children, className = '' }: BaseProps): React.ReactElement<BaseProps> {
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Menu`}>
|
||||
{children}
|
||||
</StyledDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const Menu: MenuDef = MenuBase as unknown as MenuDef;
|
||||
const StyledDiv = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 14.286rem;
|
||||
margin: 1rem 0;
|
||||
|
||||
& > *:not(.ui--Menu__Item):not(.ui--Menu__Divider) {
|
||||
margin-right: 1rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const Menu = React.memo(Base) as unknown as MenuType;
|
||||
|
||||
Menu.Divider = Divider;
|
||||
Menu.Item = Item;
|
||||
|
||||
@ -26,3 +26,9 @@ export interface BaseProps {
|
||||
export interface DividerProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export type MenuType = React.FC<BaseProps> & {
|
||||
Divider: React.FC<DividerProps>;
|
||||
Item: React.FC<ItemProps>;
|
||||
Header: React.FC<HeaderProps>;
|
||||
};
|
||||
|
||||
@ -1,16 +1,19 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ActionsProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Button from '../Button';
|
||||
|
||||
function Actions ({ children, className = '' }: ActionsProps) {
|
||||
interface Props {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function Actions ({ children, className = '' }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledDiv className={className}>
|
||||
<StyledDiv className={`${className} ui--Modal-Actions`}>
|
||||
<Button.Group>
|
||||
{children}
|
||||
</Button.Group>
|
||||
|
||||
@ -1,109 +0,0 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ModalProps } from './types';
|
||||
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import styled, { createGlobalStyle } from 'styled-components';
|
||||
|
||||
import { useTheme } from '@polkadot/react-hooks';
|
||||
|
||||
import Header from './Header';
|
||||
|
||||
function Base (props: ModalProps): React.ReactElement<ModalProps> {
|
||||
const { themeClassName } = useTheme();
|
||||
const { children, className = '', header, onClose, size = 'medium', testId = 'modal' } = props;
|
||||
|
||||
const listenKeyboard = useCallback((event: KeyboardEvent) => {
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
if (event.key === 'Escape' || event.keyCode === 27) {
|
||||
onClose();
|
||||
}
|
||||
}, [onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', listenKeyboard, true);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', listenKeyboard, true);
|
||||
};
|
||||
}, [listenKeyboard]);
|
||||
|
||||
return createPortal(
|
||||
<StyledDiv
|
||||
className={`${className} ui--Modal ${size}Size ${themeClassName} `}
|
||||
data-testid={testId}
|
||||
>
|
||||
<DisableGlobalScroll />
|
||||
<div
|
||||
className='ui--Modal__overlay'
|
||||
onClick={onClose}
|
||||
/>
|
||||
<div className='ui--Modal__body'>
|
||||
<Header
|
||||
header={header}
|
||||
onClose={onClose}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
</StyledDiv>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
|
||||
const DisableGlobalScroll = createGlobalStyle`
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
|
||||
.ui--Modal__overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(96, 96, 96, 0.5);
|
||||
}
|
||||
|
||||
.ui--Modal__body {
|
||||
margin-top: 30px;
|
||||
background: var(--bg-page);
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
|
||||
max-width: 900px;
|
||||
width: calc(100% - 16px);
|
||||
|
||||
color: var(--color-text);
|
||||
font: var(--font-sans);
|
||||
}
|
||||
|
||||
&.smallSize .ui--Modal__body {
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
&.largeSize .ui--Modal__body {
|
||||
max-width: 1080px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Base);
|
||||
@ -1,12 +1,16 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ColumnsProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
function Columns ({ children, className = '', hint }: ColumnsProps): React.ReactElement<ColumnsProps> {
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
hint?: React.ReactNode;
|
||||
}
|
||||
|
||||
function Columns ({ children, className = '', hint }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Modal-Columns`}>
|
||||
<div>{children}</div>
|
||||
|
||||
@ -1,14 +1,17 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ContentProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
function Content ({ children, className = '' }: ContentProps) {
|
||||
interface Props {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
function Content ({ children, className = '' }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Modal__Content`}>
|
||||
<StyledDiv className={`${className} ui--Modal-Content`}>
|
||||
{children}
|
||||
</StyledDiv>
|
||||
);
|
||||
|
||||
@ -1,16 +1,20 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { HeaderProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Button from '../Button';
|
||||
|
||||
function Header ({ className = '', header, onClose }: HeaderProps) {
|
||||
interface Props {
|
||||
className?: string;
|
||||
header?: React.ReactNode;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
function Header ({ className = '', header, onClose }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledDiv className={`${className} ui--Modal__Header`}>
|
||||
<StyledDiv className={`${className} ui--Modal-Header`}>
|
||||
{header && (
|
||||
<h1>{header}</h1>
|
||||
)}
|
||||
|
||||
@ -1,14 +1,126 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ModalType } from './types';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import styled, { createGlobalStyle } from 'styled-components';
|
||||
|
||||
import { useTheme } from '@polkadot/react-hooks';
|
||||
|
||||
import Actions from './Actions';
|
||||
import ModalBase from './Base';
|
||||
import Columns from './Columns';
|
||||
import Content from './Content';
|
||||
import Header from './Header';
|
||||
|
||||
const Modal = ModalBase as unknown as ModalType;
|
||||
interface Props {
|
||||
size?: 'large' | 'medium' | 'small';
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
header?: React.ReactNode;
|
||||
open?: boolean;
|
||||
onClose: () => void;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
function ModalBase ({ children, className = '', header, onClose, size = 'medium', testId = 'modal' }: Props): React.ReactElement<Props> {
|
||||
const { themeClassName } = useTheme();
|
||||
|
||||
const listenKeyboard = useCallback((event: KeyboardEvent) => {
|
||||
// eslint-disable-next-line deprecation/deprecation
|
||||
if (event.key === 'Escape' || event.keyCode === 27) {
|
||||
onClose();
|
||||
}
|
||||
}, [onClose]);
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('keydown', listenKeyboard, true);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('keydown', listenKeyboard, true);
|
||||
};
|
||||
}, [listenKeyboard]);
|
||||
|
||||
return createPortal(
|
||||
<StyledDiv
|
||||
className={`${className} ui--Modal ${size}Size ${themeClassName} `}
|
||||
data-testid={testId}
|
||||
>
|
||||
<DisableGlobalScroll />
|
||||
<div
|
||||
className='ui--Modal__overlay'
|
||||
onClick={onClose}
|
||||
/>
|
||||
<div className='ui--Modal__body'>
|
||||
<Header
|
||||
header={header}
|
||||
onClose={onClose}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
</StyledDiv>,
|
||||
document.body
|
||||
);
|
||||
}
|
||||
|
||||
const DisableGlobalScroll = createGlobalStyle`
|
||||
body {
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
|
||||
.ui--Modal__overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(96, 96, 96, 0.5);
|
||||
}
|
||||
|
||||
.ui--Modal__body {
|
||||
margin-top: 30px;
|
||||
background: var(--bg-page);
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translate(-50%, 0);
|
||||
|
||||
max-width: 900px;
|
||||
width: calc(100% - 16px);
|
||||
|
||||
color: var(--color-text);
|
||||
font: var(--font-sans);
|
||||
}
|
||||
|
||||
&.smallSize .ui--Modal__body {
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
&.largeSize .ui--Modal__body {
|
||||
max-width: 1080px;
|
||||
}
|
||||
`;
|
||||
|
||||
const Modal = React.memo(ModalBase) as unknown as typeof ModalBase & {
|
||||
Actions: typeof Actions;
|
||||
Columns: typeof Columns;
|
||||
Content: typeof Content;
|
||||
};
|
||||
|
||||
Modal.Actions = Actions;
|
||||
Modal.Columns = Columns;
|
||||
|
||||
@ -1,49 +0,0 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export interface HeaderProps {
|
||||
className?: string;
|
||||
header?: React.ReactNode;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export interface ColumnsProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
hint?: React.ReactNode;
|
||||
}
|
||||
|
||||
type ModalSize = 'large' | 'medium' | 'small'
|
||||
|
||||
export interface ModalProps {
|
||||
size?: ModalSize;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
header?: React.ReactNode;
|
||||
open?: boolean;
|
||||
onClose: () => void;
|
||||
testId?: string;
|
||||
[index: string]: any;
|
||||
}
|
||||
|
||||
export interface ActionsProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface BodyProps {
|
||||
size: ModalSize;
|
||||
}
|
||||
|
||||
export interface ContentProps {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export type ModalType = React.FC<ModalProps> & {
|
||||
Actions: React.FC<ActionsProps>;
|
||||
Columns: React.FC<ColumnsProps>;
|
||||
Content: React.FC<ContentProps>;
|
||||
};
|
||||
@ -16,14 +16,16 @@ interface Props {
|
||||
}
|
||||
|
||||
function Body ({ children, className = '', empty, emptySpinner, noBodyTag }: Props): React.ReactElement<Props> {
|
||||
const bodyClassName = `${className} ui--Table-Body`;
|
||||
|
||||
return children
|
||||
? (
|
||||
noBodyTag
|
||||
? <>{children}</>
|
||||
: <tbody className={className}>{children}</tbody>
|
||||
: <tbody className={bodyClassName}>{children}</tbody>
|
||||
)
|
||||
: (
|
||||
<tbody className={className}>
|
||||
<tbody className={bodyClassName}>
|
||||
<tr>
|
||||
<td colSpan={100}>{
|
||||
isString(empty)
|
||||
|
||||
@ -1,15 +1,27 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ColBalanceProps as Props } from '../types';
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { FormatBalance } from '@polkadot/react-query';
|
||||
|
||||
function Balance ({ className = '', value, withLoading }: Props): React.ReactElement<Props> {
|
||||
export interface Props {
|
||||
className?: string;
|
||||
colSpan?: number;
|
||||
rowSpan?: number;
|
||||
value?: BN | null;
|
||||
withLoading?: boolean;
|
||||
}
|
||||
|
||||
function Balance ({ className = '', colSpan, rowSpan, value, withLoading }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<td className={`${className} ui--Table-Column-Balance number`}>
|
||||
<td
|
||||
className={`${className} ui--Table-Column-Balance number`}
|
||||
colSpan={colSpan}
|
||||
rowSpan={rowSpan}
|
||||
>
|
||||
{value
|
||||
? <FormatBalance value={value} />
|
||||
: withLoading && (
|
||||
|
||||
@ -1,18 +1,26 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ColExpandProps as Props } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Icon from '../../Icon';
|
||||
|
||||
function Expand ({ className = '', isExpanded, toggle }: Props): React.ReactElement<Props> {
|
||||
export interface Props {
|
||||
className?: string;
|
||||
colSpan?: number;
|
||||
rowSpan?: number;
|
||||
isExpanded: boolean;
|
||||
toggle: () => void;
|
||||
}
|
||||
|
||||
function Expand ({ className = '', colSpan, isExpanded, rowSpan, toggle }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledTd
|
||||
className={`${className} ui--Table-Column-Expand`}
|
||||
colSpan={colSpan}
|
||||
onClick={toggle}
|
||||
rowSpan={rowSpan}
|
||||
>
|
||||
<div>
|
||||
<Icon
|
||||
|
||||
@ -1,14 +1,21 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ColFavoriteProps as Props } from '../types';
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Icon from '../../Icon';
|
||||
|
||||
function Favorite ({ address, className = '', isFavorite, toggle }: Props): React.ReactElement<Props> {
|
||||
export interface Props {
|
||||
address: string;
|
||||
className?: string;
|
||||
colSpan?: number;
|
||||
isFavorite: boolean;
|
||||
rowSpan?: number;
|
||||
toggle: (address: string) => void;
|
||||
}
|
||||
|
||||
function Favorite ({ address, className = '', colSpan, isFavorite, rowSpan, toggle }: Props): React.ReactElement<Props> {
|
||||
const onClick = useCallback(
|
||||
() => toggle(address),
|
||||
[address, toggle]
|
||||
@ -17,7 +24,9 @@ function Favorite ({ address, className = '', isFavorite, toggle }: Props): Reac
|
||||
return (
|
||||
<StyledTd
|
||||
className={`${className} ui--Table-Column-Favorite`}
|
||||
colSpan={colSpan}
|
||||
onClick={onClick}
|
||||
rowSpan={rowSpan}
|
||||
>
|
||||
<Icon
|
||||
color={
|
||||
|
||||
@ -1,16 +1,27 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ColIdProps as Props } from '../types';
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
function Id ({ className = '', value }: Props): React.ReactElement<Props> {
|
||||
export interface Props {
|
||||
className?: string;
|
||||
colSpan?: number;
|
||||
rowSpan?: number;
|
||||
value: BN | number;
|
||||
}
|
||||
|
||||
function Id ({ className = '', colSpan, rowSpan, value }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<StyledTd className={`${className} ui--Table-Column-Id`}>
|
||||
<StyledTd
|
||||
className={`${className} ui--Table-Column-Id`}
|
||||
colSpan={colSpan}
|
||||
rowSpan={rowSpan}
|
||||
>
|
||||
<h2 className='--digits'>{formatNumber(value)}</h2>
|
||||
</StyledTd>
|
||||
);
|
||||
|
||||
@ -1,11 +0,0 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TableType } from '../types';
|
||||
|
||||
import Balance from './Balance';
|
||||
import Expand from './Expand';
|
||||
import Favorite from './Favorite';
|
||||
import Id from './Id';
|
||||
|
||||
export const Column: TableType['Column'] = { Balance, Expand, Favorite, Id };
|
||||
36
packages/react-components/src/Table/Column/index.tsx
Normal file
36
packages/react-components/src/Table/Column/index.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Balance from './Balance';
|
||||
import Expand from './Expand';
|
||||
import Favorite from './Favorite';
|
||||
import Id from './Id';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function ColumnBase ({ children, className = '' }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<td className={`${className} ui--Table-Column`}>
|
||||
{children}
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
const Column = React.memo(ColumnBase) as unknown as typeof ColumnBase & {
|
||||
Balance: typeof Balance,
|
||||
Expand: typeof Expand,
|
||||
Favorite: typeof Favorite,
|
||||
Id: typeof Id
|
||||
};
|
||||
|
||||
Column.Balance = Balance;
|
||||
Column.Expand = Expand;
|
||||
Column.Favorite = Favorite;
|
||||
Column.Id = Id;
|
||||
|
||||
export default Column;
|
||||
@ -16,7 +16,7 @@ function Foot ({ className = '', footer, isEmpty }: Props): React.ReactElement<P
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledTfoot className={className}>
|
||||
<StyledTfoot className={`${className} ui--Table-Foot`}>
|
||||
{footer}
|
||||
</StyledTfoot>
|
||||
);
|
||||
|
||||
@ -20,7 +20,7 @@ function Head ({ children, className = '', filter, header, isEmpty }: Props): Re
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledThead className={className}>
|
||||
<StyledThead className={`${className} ui--Table-Head`}>
|
||||
{filter && (
|
||||
<tr className='filter'>
|
||||
<th colSpan={100}>{filter}</th>
|
||||
|
||||
21
packages/react-components/src/Table/Row/index.tsx
Normal file
21
packages/react-components/src/Table/Row/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function RowBase ({ children, className = '' }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<tr className={`${className} ui--Table-Row`}>
|
||||
{children}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
const Row = React.memo(RowBase) as unknown as typeof RowBase;
|
||||
|
||||
export default Row;
|
||||
@ -1,13 +0,0 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TableType } from './types';
|
||||
|
||||
import { Column } from './Column';
|
||||
import TableBase from './Table';
|
||||
|
||||
const Table = TableBase as unknown as TableType;
|
||||
|
||||
Table.Column = Column;
|
||||
|
||||
export default Table;
|
||||
@ -1,15 +1,31 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TableProps as Props } from './types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Columar from '../Columar';
|
||||
import Body from './Body';
|
||||
import Column from './Column';
|
||||
import Foot from './Foot';
|
||||
import Head from './Head';
|
||||
import Row from './Row';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
empty?: React.ReactNode | false;
|
||||
emptySpinner?: React.ReactNode;
|
||||
filter?: React.ReactNode;
|
||||
footer?: React.ReactNode;
|
||||
header?: ([React.ReactNode?, string?, number?, (() => void)?] | false | null | undefined)[];
|
||||
headerChildren?: React.ReactNode;
|
||||
isFixed?: boolean;
|
||||
isInline?: boolean;
|
||||
isSplit?: boolean;
|
||||
legend?: React.ReactNode;
|
||||
noBodyTag?: boolean;
|
||||
}
|
||||
|
||||
function extractBodyChildren (children: React.ReactNode): [boolean, React.ReactNode | React.ReactNode[]] {
|
||||
if (!Array.isArray(children)) {
|
||||
@ -22,7 +38,7 @@ function extractBodyChildren (children: React.ReactNode): [boolean, React.ReactN
|
||||
return [isEmpty, isEmpty ? null : kids];
|
||||
}
|
||||
|
||||
function Table ({ children, className = '', empty, emptySpinner, filter, footer, header, headerChildren, isFixed, isInline, isSplit, legend, noBodyTag }: Props): React.ReactElement<Props> {
|
||||
function TableBase ({ children, className = '', empty, emptySpinner, filter, footer, header, headerChildren, isFixed, isInline, isSplit, legend, noBodyTag }: Props): React.ReactElement<Props> {
|
||||
const [isEmpty, bodyChildren] = extractBodyChildren(children);
|
||||
|
||||
const splitBody = useMemo(
|
||||
@ -525,4 +541,12 @@ const StyledDiv = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Table);
|
||||
const Table = React.memo(TableBase) as unknown as typeof TableBase & {
|
||||
Column: typeof Column,
|
||||
Row: typeof Row
|
||||
};
|
||||
|
||||
Table.Column = Column;
|
||||
Table.Row = Row;
|
||||
|
||||
export default Table;
|
||||
@ -1,53 +0,0 @@
|
||||
// Copyright 2017-2023 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { BN } from '@polkadot/util';
|
||||
|
||||
export interface ColBalanceProps {
|
||||
className?: string;
|
||||
value?: BN | null;
|
||||
withLoading?: boolean;
|
||||
}
|
||||
|
||||
export interface ColExpandProps {
|
||||
className?: string;
|
||||
isExpanded: boolean;
|
||||
toggle: () => void;
|
||||
}
|
||||
|
||||
export interface ColFavoriteProps {
|
||||
address: string;
|
||||
className?: string;
|
||||
isFavorite: boolean;
|
||||
toggle: (address: string) => void;
|
||||
}
|
||||
|
||||
export interface ColIdProps {
|
||||
className?: string;
|
||||
value: BN | number;
|
||||
}
|
||||
|
||||
export interface TableProps {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
empty?: React.ReactNode | false;
|
||||
emptySpinner?: React.ReactNode;
|
||||
filter?: React.ReactNode;
|
||||
footer?: React.ReactNode;
|
||||
header?: ([React.ReactNode?, string?, number?, (() => void)?] | false | null | undefined)[];
|
||||
headerChildren?: React.ReactNode;
|
||||
isFixed?: boolean;
|
||||
isInline?: boolean;
|
||||
isSplit?: boolean;
|
||||
legend?: React.ReactNode;
|
||||
noBodyTag?: boolean;
|
||||
}
|
||||
|
||||
export type TableType = React.ComponentType<TableProps> & {
|
||||
Column: {
|
||||
Balance: React.ComponentType<ColBalanceProps>;
|
||||
Expand: React.ComponentType<ColExpandProps>;
|
||||
Favorite: React.ComponentType<ColFavoriteProps>;
|
||||
Id: React.ComponentType<ColIdProps>;
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user