Web: Components: rewrited component SwitchButton to ViewSelector

This commit is contained in:
Artem Tarasov 2021-06-29 15:27:46 +03:00
parent 4d7fe086b5
commit d73bb73dab
18 changed files with 375 additions and 327 deletions

View File

@ -59,5 +59,6 @@ export { default as utils } from "./utils";
export { Icons } from "./icons";
export { default as SaveCancelButtons } from "./save-cancel-buttons";
export { default as DragAndDrop } from "./drag-and-drop";
export { default as ViewSelector } from "./view-selector";
export * as Themes from "./themes";
export { default as Portal } from "./portal";

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 15C0 15.5523 0.447715 16 1 16H15C15.5523 16 16 15.5523 16 15V14.7143C16 14.162 15.5523 13.7143 15 13.7143H1C0.447715 13.7143 0 14.162 0 14.7143V15ZM0 8.14286C0 8.69514 0.447715 9.14286 1 9.14286H15C15.5523 9.14286 16 8.69514 16 8.14286V7.85714C16 7.30486 15.5523 6.85714 15 6.85714H1C0.447715 6.85714 0 7.30486 0 7.85714V8.14286ZM1 2.28571C0.447715 2.28571 0 1.838 0 1.28571V1C0 0.447716 0.447715 0 1 0H15C15.5523 0 16 0.447716 16 1V1.28571C16 1.838 15.5523 2.28571 15 2.28571H1Z" fill="#A3A9AE"/>
</svg>

After

Width:  |  Height:  |  Size: 653 B

View File

@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0C0.447715 0 0 0.447715 0 0.999999V5.85714C0 6.40943 0.447715 6.85714 1 6.85714H5.85714C6.40943 6.85714 6.85714 6.40943 6.85714 5.85714V1C6.85714 0.447715 6.40943 0 5.85714 0H1ZM1 9.14286C0.447715 9.14286 0 9.59057 0 10.1429V15C0 15.5523 0.447715 16 1 16H5.85714C6.40943 16 6.85714 15.5523 6.85714 15V10.1429C6.85714 9.59057 6.40943 9.14286 5.85714 9.14286H1ZM9.14286 0.999999C9.14286 0.447715 9.59057 0 10.1429 0H15C15.5523 0 16 0.447715 16 1V5.85714C16 6.40943 15.5523 6.85714 15 6.85714H10.1429C9.59057 6.85714 9.14286 6.40943 9.14286 5.85714V0.999999ZM10.1429 9.14286C9.59057 9.14286 9.14286 9.59057 9.14286 10.1429V15C9.14286 15.5523 9.59057 16 10.1429 16H15C15.5523 16 16 15.5523 16 15V10.1429C16 9.59057 15.5523 9.14286 15 9.14286H10.1429Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 918 B

View File

@ -1,25 +0,0 @@
# SwitchButton
Actions with a button.
### Usage
```js
import { SwitchButton } from "app-components";
```
```jsx
<SwitchButton
disabled={false}
checked={false}
onChange={() => alert("SwitchButton clicked")}
/>
```
### Properties
| Props | Type | Required | Values | Default | Description |
| ---------- | :----: | :------: | :----: | :-----: | ---------------------------------------------- |
| `disabled` | `bool` | - | - | `false` | Disables the button default functionality |
| `onChange` | `func` | - | - | - | The event triggered when the button is clicked |
| `checked` | `bool` | - | - | `false` | Makes SwitchButton checked. |

View File

@ -1,51 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import { StyledSwitchButton, StyledHiddenInput } from "./styled-switch-button";
import { SwitchButtonActiveUnchecked, SwitchButtonActiveChecked } from "./svg";
const SwitchButton = ({ checked, disabled, onChange, ...rest }) => {
const renderSwitch = () => {
return (
<>
{checked ? (
<SwitchButtonActiveChecked />
) : (
<SwitchButtonActiveUnchecked />
)}
</>
);
};
const btnSwitch = renderSwitch(checked);
//console.log('SwitchButton render');
return (
<StyledSwitchButton {...rest} checked={checked} disabled={disabled}>
{btnSwitch}
<StyledHiddenInput
type="checkbox"
defaultChecked={checked}
disabled={disabled}
onChange={onChange}
/>
</StyledSwitchButton>
);
};
SwitchButton.propTypes = {
/** Disables the button default functionality */
disabled: PropTypes.bool,
/** Makes SwitchButton checked. */
checked: PropTypes.bool,
/** The event triggered when the button is clicked */
onChange: PropTypes.func,
};
SwitchButton.defaultProps = {
disabled: false,
checked: false,
};
export default SwitchButton;

View File

@ -1,80 +0,0 @@
import styled, { css } from "styled-components";
import Base from "../themes/base";
const StyledSwitchButton = styled.label`
width: 64px;
height: 32px;
position: relative;
box-sizing: border-box;
svg {
rect {
fill: ${(props) => props.theme.switchButton.fillColor};
}
${(props) =>
!props.disabled
? !props.checked
? css`
path:nth-child(2) {
fill: ${(props) => props.theme.switchButton.checkedFillColor};
}
path:nth-child(3) {
fill: ${(props) => props.theme.switchButton.fillColor};
}
path:last-child {
fill: ${(props) => props.theme.switchButton.checkedFillColor};
}
`
: css`
path:nth-child(2) {
fill: ${(props) => props.theme.switchButton.checkedFillColor};
}
path:nth-child(3) {
fill: ${(props) => props.theme.switchButton.checkedFillColor};
}
path:last-child {
fill: ${(props) => props.theme.switchButton.fillColor};
}
`
: css`
rect {
fill: ${(props) => props.theme.switchButton.fillColorDisabled};
stroke: ${(props) => props.theme.switchButton.disabledFillColor};
}
path:nth-child(2) {
fill: ${(props) => props.theme.switchButton.disabledFillColor};
}
path:nth-child(3) {
fill: ${(props) =>
props.theme.switchButton.disabledFillColorInner};
}
path:last-child {
fill: ${(props) =>
props.theme.switchButton.disabledFillColorInner};
}
`}
}
&:hover {
${(props) =>
props.disabled
? css`
cursor: default;
`
: css`
cursor: pointer;
rect:first-child {
stroke: ${(props) => props.theme.switchButton.hoverBorderColor};
}
`}
}
`;
const StyledHiddenInput = styled.input`
opacity: 0.0001;
position: absolute;
right: 0;
z-index: -1;
`;
StyledSwitchButton.defaultProps = { theme: Base };
export { StyledSwitchButton, StyledHiddenInput };

View File

@ -1,2 +0,0 @@
export { default as SwitchButtonActiveUnchecked } from "./switch-button.unchecked.react.svg";
export { default as SwitchButtonActiveChecked } from "./switch-button.checked.react.svg";

View File

@ -1,6 +0,0 @@
<svg width="64" height="32" viewBox="0 0 64 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="63" height="31" rx="2.5" fill="white" stroke="#D0D5DA"/>
<path d="M64 3C64 1.34315 62.6569 0 61 0H32V32H61C62.6569 32 64 30.6569 64 29V3Z" fill="#A3A9AE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 23C8 23.5523 8.44772 24 9 24H23C23.5523 24 24 23.5523 24 23C24 22.4477 23.5523 22 23 22H9C8.44772 22 8 22.4477 8 23ZM8 16C8 16.5523 8.44772 17 9 17H23C23.5523 17 24 16.5523 24 16C24 15.4477 23.5523 15 23 15H9C8.44772 15 8 15.4477 8 16ZM9 10C8.44772 10 8 9.55229 8 9C8 8.44771 8.44772 8 9 8H23C23.5523 8 24 8.44771 24 9C24 9.55229 23.5523 10 23 10H9Z" fill="#A3A9AE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M41 8C40.4477 8 40 8.44772 40 9V14C40 14.5523 40.4477 15 41 15H46C46.5523 15 47 14.5523 47 14V9C47 8.44772 46.5523 8 46 8H41ZM41 17C40.4477 17 40 17.4477 40 18V23C40 23.5523 40.4477 24 41 24H46C46.5523 24 47 23.5523 47 23V18C47 17.4477 46.5523 17 46 17H41ZM49 9C49 8.44772 49.4477 8 50 8H55C55.5523 8 56 8.44772 56 9V14C56 14.5523 55.5523 15 55 15H50C49.4477 15 49 14.5523 49 14V9ZM50 17C49.4477 17 49 17.4477 49 18V23C49 23.5523 49.4477 24 50 24H55C55.5523 24 56 23.5523 56 23V18C56 17.4477 55.5523 17 55 17H50Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -1,6 +0,0 @@
<svg width="64" height="32" viewBox="0 0 64 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="63" height="31" rx="2.5" fill="white" stroke="#D0D5DA"/>
<path d="M0 3C0 1.34315 1.34315 0 3 0H32V32H3C1.34315 32 0 30.6569 0 29V3Z" fill="#A3A9AE"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8 23C8 23.5523 8.44772 24 9 24H23C23.5523 24 24 23.5523 24 23C24 22.4477 23.5523 22 23 22H9C8.44772 22 8 22.4477 8 23ZM8 16C8 16.5523 8.44772 17 9 17H23C23.5523 17 24 16.5523 24 16C24 15.4477 23.5523 15 23 15H9C8.44772 15 8 15.4477 8 16ZM9 10C8.44772 10 8 9.55229 8 9C8 8.44771 8.44772 8 9 8H23C23.5523 8 24 8.44771 24 9C24 9.55229 23.5523 10 23 10H9Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M41 8C40.4477 8 40 8.44772 40 9V14C40 14.5523 40.4477 15 41 15H46C46.5523 15 47 14.5523 47 14V9C47 8.44772 46.5523 8 46 8H41ZM41 17C40.4477 17 40 17.4477 40 18V23C40 23.5523 40.4477 24 41 24H46C46.5523 24 47 23.5523 47 23V18C47 17.4477 46.5523 17 46 17H41ZM49 9C49 8.44772 49.4477 8 50 8H55C55.5523 8 56 8.44772 56 9V14C56 14.5523 55.5523 15 55 15H50C49.4477 15 49 14.5523 49 14V9ZM50 17C49.4477 17 49 17.4477 49 18V23C49 23.5523 49.4477 24 50 24H55C55.5523 24 56 23.5523 56 23V18C56 17.4477 55.5523 17 55 17H50Z" fill="#A3A9AE"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,42 +0,0 @@
import React, { useState } from "react";
import SwitchButton from "./";
import Box from "../box";
export default {
title: "Components/SwitchButton",
component: SwitchButton,
parameters: {
docs: {
description: {
component: "Actions with a button.",
},
},
},
argTypes: {
onChange: {
action: "onChange",
},
},
};
const Template = ({ checked, onChange, ...rest }) => {
const [isChecked, setIsChecked] = useState(checked);
return (
<Box paddingProp="16px">
<SwitchButton
{...rest}
checked={isChecked}
onChange={(e) => {
onChange(e.target.checked);
setIsChecked(!isChecked);
}}
/>
</Box>
);
};
export const Default = Template.bind({});
Default.args = {
checked: false,
disabled: false,
};

View File

@ -1,76 +0,0 @@
import React from "react";
import { mount } from "enzyme";
import "jest-styled-components";
import SwitchButton from ".";
const baseProps = {
checked: false,
disabled: false,
onChange: jest.fn(),
};
describe("<SwitchButton />", () => {
it("renders without error", () => {
const wrapper = mount(<SwitchButton {...baseProps} />);
expect(wrapper).toExist();
});
it("render with disabled", () => {
const wrapper = mount(
<SwitchButton onClick={jest.fn()} disabled={true} checked={false} />
);
expect(wrapper).toExist();
});
it("render without checked", () => {
const wrapper = mount(
<SwitchButton onClick={jest.fn()} disabled={false} />
);
expect(wrapper).toExist();
});
it("id, className, style is exist", () => {
const wrapper = mount(
<SwitchButton
{...baseProps}
id="testId"
className="test"
style={{ color: "red" }}
/>
);
expect(wrapper.prop("id")).toEqual("testId");
expect(wrapper.prop("className")).toEqual("test");
expect(wrapper.getDOMNode().style).toHaveProperty("color", "red");
});
it("accepts disabled", () => {
const wrapper = mount(<SwitchButton {...baseProps} disabled />);
expect(wrapper.prop("disabled")).toEqual(true);
});
it("accepts checked", () => {
const wrapper = mount(<SwitchButton {...baseProps} checked />);
expect(wrapper.prop("checked")).toEqual(true);
const wrapper2 = mount(<SwitchButton {...baseProps} checked={false} />);
expect(wrapper2.prop("checked")).toEqual(false);
});
it("accepts checked and disabled", () => {
const wrapper = mount(<SwitchButton {...baseProps} checked disabled />);
expect(wrapper.prop("checked")).toEqual(true);
expect(wrapper.prop("disabled")).toEqual(true);
});
it("onChange() test", () => {
const wrapper = mount(<SwitchButton {...baseProps} />);
expect(wrapper.props().checked).toBe(false);
const input = wrapper.find('input[type="checkbox"]');
input.simulate("change");
expect(baseProps.onChange).toHaveBeenCalled();
});
});

View File

@ -381,16 +381,25 @@ const Base = {
// arrowColor: grayMid,
// },
// switchButton: {
// fillColor: white,
// checkedFillColor: gray,
switchButton: {
fillColor: white,
checkedFillColor: gray,
fillColorDisabled: grayLight,
disabledFillColor: grayLightMid,
disabledFillColorInner: grayMid,
hoverBorderColor: gray,
borderColor: grayLight,
},
// fillColorDisabled: grayLight,
// disabledFillColor: grayLightMid,
// disabledFillColorInner: grayMid,
// hoverBorderColor: gray,
// },
viewSelector: {
fillColor: white,
checkedFillColor: gray,
fillColorDisabled: grayLight,
disabledFillColor: grayLightMid,
disabledFillColorInner: grayMid,
hoverBorderColor: gray,
borderColor: grayMid,
},
radioButton: {
textColor: black,
@ -1561,17 +1570,6 @@ const Base = {
background: lightCumulus,
},
switchButton: {
fillColor: white,
checkedFillColor: gray,
fillColorDisabled: grayLight,
disabledFillColor: grayLightMid,
disabledFillColorInner: grayMid,
hoverBorderColor: gray,
},
// phoneInput: {
// width: "304px",
// height: "44px",

View File

@ -345,16 +345,17 @@ const Dark = {
hoverIndeterminateColor: black,
},
// switchButton: {
// fillColor: white,
// checkedFillColor: gray,
viewSelector: {
fillColor: white,
checkedFillColor: gray,
// fillColorDisabled: grayLight,
// disabledFillColor: grayLightMid,
// disabledFillColorInner: grayMid,
fillColorDisabled: grayLight,
disabledFillColor: grayLightMid,
disabledFillColorInner: grayMid,
// hoverBorderColor: gray,
// },
hoverBorderColor: gray,
borderColor: grayLight,
},
radioButton: {
color: white,
@ -1349,17 +1350,6 @@ const Dark = {
background: lightCumulus,
},
switchButton: {
fillColor: white,
checkedFillColor: gray,
fillColorDisabled: grayLight,
disabledFillColor: grayLightMid,
disabledFillColorInner: grayMid,
hoverBorderColor: gray,
},
// phoneInput: {
// width: "304px",
// height: "44px",

View File

@ -0,0 +1,44 @@
# ViewSelector
Actions with a button.
### Usage
```js
import { ViewSelector } from "app-components";
```
### View Settings
```js
const viewSettings = [
{
key: "row",
icon: "/static/images/row.react.svg",
},
{
key: "tile",
icon: "/static/images/tile.react.svg",
callback: createThumbnails,
},
];
```
```jsx
<ViewSelector
isDisabled={false}
onChangeView={(view) => console.log("current view:", view)}
viewSettings={viewSettings}
viewAs="row"
/>
```
### Properties
| Props | Type | Required | Values | Default | Description |
| -------------- | :------: | :------: | :----: | :-----: | ---------------------------------------------- |
| `isDisabled` | `bool` | - | - | - | Disables the button default functionality |
| `onChangeView` | `func` | - | - | - | The event triggered when the button is clicked |
| `viewSettings` | `arr` | - | - | - | Array containing view settings. |
| `viewAs` | `string` | - | - | - | Current application view |

View File

@ -0,0 +1,70 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import { ReactSVG } from "react-svg";
import { StyledViewSelector, IconWrapper } from "./styled-view-selector";
const ViewSelector = ({
isDisabled,
viewSettings,
viewAs,
onChangeView,
...rest
}) => {
const [currentView, setCurrentView] = useState(viewAs);
const onChangeViewHandler = (e) => {
if (isDisabled) return;
const el = e.target.closest(".view-selector-icon");
const view = el.dataset.view;
if (view !== currentView) {
const option = viewSettings.find((setting) => view === setting.key);
option.callback && option.callback();
setCurrentView(view);
onChangeView(view);
}
};
const lastIndx = viewSettings && viewSettings.length - 1;
return (
<StyledViewSelector
{...rest}
isDisabled={isDisabled}
onClick={onChangeViewHandler}
>
{viewSettings &&
viewSettings.map((el, indx) => {
const { key, icon } = el;
return (
<IconWrapper
isDisabled={isDisabled}
isChecked={currentView === key}
firstItem={indx === 0}
lastItem={indx === lastIndx}
key={key}
className="view-selector-icon"
data-view={key}
>
<ReactSVG src={icon} />
</IconWrapper>
);
})}
</StyledViewSelector>
);
};
ViewSelector.propTypes = {
/* Disables the button default functionality */
isDisabled: PropTypes.bool,
/* The event triggered when the button is clicked */
onChangeView: PropTypes.func,
/* Object containing view settings */
viewSettings: PropTypes.arrayOf(PropTypes.object).isRequired,
/* Current application view */
viewAs: PropTypes.string.isRequired,
};
export default ViewSelector;

View File

@ -0,0 +1,98 @@
import styled, { css } from "styled-components";
import Base from "../themes/base";
const StyledViewSelector = styled.div`
height: 32px;
position: relative;
box-sizing: border-box;
display: flex;
`;
const firstItemStyle = css`
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
border-right: 1px solid rgba(255, 255, 255, 0);
`;
const lastItemStyle = css`
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
border-left: 1px solid rgba(255, 255, 255, 0);
`;
const middleItemsStyle = css`
border-left: 1px solid rgba(255, 255, 255, 0);
border-right: 1px solid rgba(255, 255, 255, 0);
`;
const IconWrapper = styled.div`
position: relative;
width: 32px;
height: 100%;
padding: 8px;
box-sizing: border-box;
border: 1px solid;
border-color: ${(props) =>
props.isDisabled
? props.theme.viewSelector.disabledFillColor
: props.isChecked
? props.theme.viewSelector.checkedFillColor
: props.theme.viewSelector.borderColor};
${(props) => !props.firstItem && !props.lastItem && middleItemsStyle}
${(props) => props.firstItem && firstItemStyle}
${(props) => props.lastItem && lastItemStyle}
background-color: ${(props) =>
props.isChecked
? props.isDisabled
? props.theme.viewSelector.disabledFillColor
: props.theme.viewSelector.checkedFillColor
: props.isDisabled
? props.theme.viewSelector.fillColorDisabled
: props.theme.viewSelector.fillColor};
&:hover {
${(props) =>
props.isDisabled || props.isChecked
? css`
cursor: default;
`
: css`
cursor: pointer;
border: 1px solid
${(props) => props.theme.viewSelector.hoverBorderColor};
`}
}
svg {
width: 15px;
height: 15px;
${(props) =>
!props.isDisabled
? !props.isChecked
? css`
path {
fill: ${(props) => props.theme.viewSelector.checkedFillColor};
}
`
: css`
path {
fill: ${(props) => props.theme.viewSelector.fillColor};
}
`
: css`
path {
fill: ${(props) =>
props.theme.viewSelector.disabledFillColorInner};
}
`};
}
`;
IconWrapper.defaultProps = { theme: Base };
StyledViewSelector.defaultProps = { theme: Base };
export { StyledViewSelector, IconWrapper };

View File

@ -0,0 +1,68 @@
import React, { useState } from "react";
import Box from "../box";
import ViewSelector from "./";
export default {
title: "Components/ViewSelector",
component: ViewSelector,
parameters: {
docs: {
description: {
component: "Actions with a button.",
},
},
},
argTypes: {
onChangeView: {
action: "onChangeView",
},
},
};
const Template = ({
onChangeView,
viewAs,
viewSettings,
isDisabled,
...rest
}) => {
const [view, setView] = useState(viewAs);
return (
<Box paddingProp="16px">
<ViewSelector
{...rest}
isDisabled={isDisabled}
viewSettings={viewSettings}
viewAs={view}
onChangeView={(view) => {
onChangeView(view);
setView(view);
}}
/>
</Box>
);
};
export const Default = Template.bind({});
Default.args = {
viewSettings: [
{
key: "row",
icon: "/static/images/view-rows.react.svg",
},
{
key: "tile",
icon: "/static/images/view-tiles.react.svg",
callback: () => console.log("callback tile click"),
},
{
key: "some",
icon: "/static/images/eye.react.svg",
callback: () => console.log("callback some click"),
},
],
viewAs: "row",
isDisabled: false,
};

View File

@ -0,0 +1,61 @@
import React from "react";
import { mount } from "enzyme";
import "jest-styled-components";
import ViewSelector from ".";
const baseProps = {
isDisabled: false,
onChangeView: jest.fn(),
viewAs: "row",
viewSettings: [
{
key: "row",
},
{
key: "tile",
},
{
key: "some",
},
],
};
describe("<ViewSelector />", () => {
it("renders without error", () => {
const wrapper = mount(<ViewSelector {...baseProps} />);
expect(wrapper).toExist();
});
it("render with disabled", () => {
const wrapper = mount(
<ViewSelector onClick={jest.fn()} {...baseProps} isDisabled={true} />
);
expect(wrapper).toExist();
});
it("id, className, style is exist", () => {
const wrapper = mount(
<ViewSelector
{...baseProps}
id="testId"
className="test"
style={{ color: "red" }}
/>
);
expect(wrapper.prop("id")).toEqual("testId");
expect(wrapper.prop("className")).toEqual("test");
expect(wrapper.getDOMNode().style).toHaveProperty("color", "red");
});
it("accepts isDisabled", () => {
const wrapper = mount(<ViewSelector {...baseProps} isDisabled />);
expect(wrapper.prop("isDisabled")).toEqual(true);
});
it("accepts viewAs", () => {
const wrapper = mount(<ViewSelector {...baseProps} viewAs="tile" />);
expect(wrapper.prop("viewAs")).toEqual("tile");
});
});