added catalog-item

This commit is contained in:
Timofey Boyko 2021-09-23 16:08:08 +08:00
parent 9221136e50
commit b324f5fbf4
6 changed files with 672 additions and 0 deletions

View File

@ -0,0 +1,33 @@
# CatalogItem
Is a item of catalog
### Usage
```js
import CatalogItem from "@appserver/components/catalog-item";
```
```jsx
<CatalogItem />
```
Display catalog item. Can show only icon (showText property). If is it end of block - adding margin bottom.
### Properties
| Props | Type | Required | Values | Default | Description |
| -------------- | :------------: | :------: | :----: | :-----: | ----------------------------------------------------------------------------- |
| `className` | `string` | - | - | - | Accepts class |
| `id` | `string` | - | - | - | Accepts id |
| `style` | `obj`, `array` | - | - | - | Accepts css style |
| `icon` | `string` | - | - | - | Catalog item icon |
| `text` | `string` | - | - | - | Catalog item text |
| `showText` | `bool` | - | - | `false` | Tells when the catalog item should display text |
| `onClick` | `func` | - | - | - | What the catalog item will trigger when clicked |
| `showInitial` | `bool` | - | - | `false` | Tells when the catalog item should display initial text(first symbol of text) |
| `isEndOfBlock` | `bool` | - | - | `false` | Tells when the catalog item should be end of block (adding margin-bottom) |
| `showBadge` | `bool` | - | - | `false` | Tells when the catalog item should display badge |
| `labelBadge` | `string` | - | - | - | Label for badge |
| `iconBadge` | `string` | - | - | - | Icon for badge |
| `onClickBadge` | `func` | - | - | - | What the catalog item badge will trigger when clicked |

View File

@ -0,0 +1,148 @@
import React from "react";
import CatalogItem from "./";
export default {
title: "Components/CatalogItem",
component: CatalogItem,
parameters: {
docs: {
description: {
component:
"Display catalog item. Can show only icon. If is it end of block - adding margin bottom.",
},
},
},
};
const Template = (args) => {
return (
<div style={{ width: "250px" }}>
<CatalogItem
{...args}
icon={args.icon}
text={args.text}
showText={args.showText}
showBadge={args.showBadge}
onClick={() => {
console.log("clicked item");
}}
isEndOfBlock={args.isEndOfBlock}
labelBadge={args.labelBadge}
onClickBadge={() => {
console.log("clicked badge");
}}
/>
</div>
);
};
export const Default = Template.bind({});
Default.args = {
icon: "/static/images/catalog.folder.react.svg",
text: "Documents",
showText: true,
showBadge: true,
isEndOfBlock: false,
labelBadge: "2",
};
const OnlyIcon = () => {
return (
<div style={{ width: "52px" }}>
<CatalogItem
icon={"/static/images/catalog.folder.react.svg"}
text={"My documents"}
showText={false}
showBadge={false}
/>
</div>
);
};
export const IconWithoutBadge = OnlyIcon.bind({});
const OnlyIconWithBadge = () => {
return (
<div style={{ width: "52px" }}>
<CatalogItem
icon={"/static/images/catalog.guest.react.svg"}
text={"My documents"}
showText={false}
showBadge={true}
/>
</div>
);
};
export const IconWithBadge = OnlyIconWithBadge.bind({});
const InitialIcon = () => {
return (
<div style={{ width: "52px" }}>
<CatalogItem
icon={"/static/images/catalog.folder.react.svg"}
text={"Documents"}
showText={false}
showBadge={false}
showInitial={true}
onClick={() => {
console.log("clicked item");
}}
/>
</div>
);
};
export const IconWithInitialText = InitialIcon.bind({});
const WithBadgeIcon = () => {
return (
<div style={{ width: "250px" }}>
<CatalogItem
icon={"/static/images/catalog.folder.react.svg"}
text={"My documents"}
showText={true}
showBadge={true}
iconBadge={"/static/images/catalog.trash.react.svg"}
/>
</div>
);
};
export const ItemWithBadgeIcon = WithBadgeIcon.bind({});
const TwoItem = () => {
return (
<div style={{ width: "250px" }}>
<CatalogItem
icon={"/static/images/catalog.folder.react.svg"}
text={"My documents"}
showText={true}
showBadge={true}
onClick={() => {
console.log("clicked item");
}}
isEndOfBlock={true}
labelBadge={3}
onClickBadge={() => {
console.log("clicked badge");
}}
/>
<CatalogItem
icon={"/static/images/catalog.folder.react.svg"}
text={"Some text"}
showText={true}
showBadge={true}
onClick={() => {
console.log("clicked item");
}}
iconBadge={"/static/images/catalog.trash.react.svg"}
onClickBadge={() => {
console.log("clicked badge");
}}
/>
</div>
);
};
export const ItemIsEndOfBlock = TwoItem.bind({});

View File

@ -0,0 +1,119 @@
import React from "react";
import { mount } from "enzyme";
import CatalogItem from ".";
const baseProps = {
icon: "/static/images/catalog.folder.react.svg",
text: "Documents",
showText: true,
onClick: () => jest.fn(),
showInitial: true,
showBadge: true,
isEndOfBlock: true,
labelBadge: "2",
iconBadge: "/static/images/catalog.trash.react.svg",
onClickBadge: () => jest.fn(),
};
describe("<CatalogItem />", () => {
it("renders without error", () => {
const wrapper = mount(<CatalogItem {...baseProps} />);
expect(wrapper).toExist();
});
it("render without text", () => {
const wrapper = mount(<CatalogItem {...baseProps} text="My profile" />);
expect(wrapper.prop("text")).toEqual("My profile");
});
it("render without text", () => {
const wrapper = mount(<CatalogItem {...baseProps} text="My profile" />);
expect(wrapper.prop("text")).toEqual("My profile");
});
it("render how not end of block", () => {
const wrapper = mount(<CatalogItem {...baseProps} isEndOfBlock={false} />);
expect(wrapper.prop("isEndOfBlock")).toEqual(false);
});
it("render without badge", () => {
const wrapper = mount(<CatalogItem {...baseProps} showBadge={false} />);
expect(wrapper.prop("showBadge")).toEqual(false);
});
it("render without initial", () => {
const wrapper = mount(<CatalogItem {...baseProps} showInitial={false} />);
expect(wrapper.prop("showInitial")).toEqual(false);
});
it("render without icon badge", () => {
const wrapper = mount(<CatalogItem {...baseProps} iconBadge="" />);
expect(wrapper.prop("iconBadge")).toEqual("");
});
it("render without label badge and icon badge", () => {
const wrapper = mount(
<CatalogItem {...baseProps} iconBadge="" iconLabel="" />
);
expect(wrapper.prop("iconBadge")).toEqual("");
expect(wrapper.prop("iconLabel")).toEqual("");
});
it("render without icon", () => {
const wrapper = mount(<CatalogItem {...baseProps} icon="" />);
expect(wrapper.prop("icon")).toEqual("");
});
it("accepts id", () => {
const wrapper = mount(<CatalogItem {...baseProps} id="testId" />);
expect(wrapper.prop("id")).toEqual("testId");
});
it("accepts className", () => {
const wrapper = mount(<CatalogItem {...baseProps} className="test" />);
expect(wrapper.prop("className")).toEqual("test");
});
it("accepts style", () => {
const wrapper = mount(
<CatalogItem {...baseProps} style={{ width: "100px" }} />
);
expect(wrapper.getDOMNode().style).toHaveProperty("width", "100px");
});
it("trigger click", () => {
const wrapper = mount(
<CatalogItem {...baseProps} style={{ width: "100px" }} />
);
expect(wrapper.simulate("click"));
});
it("trigger update", () => {
const wrapper = mount(
<CatalogItem {...baseProps} style={{ width: "100px" }} />
);
expect(wrapper.simulate("click"));
});
it("unmount without errors", () => {
const wrapper = mount(
<CatalogItem {...baseProps} style={{ width: "100px" }} />
);
wrapper.unmount();
});
});

View File

@ -0,0 +1,131 @@
import React from "react";
import PropTypes from "prop-types";
import { ReactSVG } from "react-svg";
import Badge from "../badge/";
import {
StyledCatalogItemContainer,
StyledCatalogItemImg,
StyledCatalogItemSibling,
StyledCatalogItemBadgeWrapper,
StyledCatalogItemText,
StyledCatalogItemInitialText,
} from "./styled-catalog-item";
const getInitial = (text) => text.substring(0, 1);
const CatalogItem = (props) => {
// console.log("render");
const {
className,
id,
style,
icon,
text,
showText,
onClick,
isEndOfBlock,
showInitial,
showBadge,
labelBadge,
iconBadge,
onClickBadge,
} = props;
const onClickAction = (e) => {
onClick && onClick(e);
};
const onClickBadgeAction = (e) => {
onClickBadge && onClickBadge(e);
};
return (
<StyledCatalogItemContainer
className={className}
id={id}
style={style}
showText={showText}
isEndOfBlock={isEndOfBlock}
>
<StyledCatalogItemSibling
onClick={onClickAction}
></StyledCatalogItemSibling>
<StyledCatalogItemImg>
<ReactSVG src={icon} />
{!showText && (
<>
{showInitial && (
<StyledCatalogItemInitialText>
{getInitial(text)}
</StyledCatalogItemInitialText>
)}
{showBadge && !iconBadge && (
<StyledCatalogItemBadgeWrapper
onClick={onClickBadgeAction}
showText={showText}
></StyledCatalogItemBadgeWrapper>
)}
</>
)}
</StyledCatalogItemImg>
{showText && <StyledCatalogItemText>{text}</StyledCatalogItemText>}
{showBadge && showText && (
<StyledCatalogItemBadgeWrapper
showText={showText}
onClick={onClickBadgeAction}
>
{!iconBadge ? (
<Badge label={labelBadge} />
) : (
<ReactSVG src={iconBadge} />
)}
</StyledCatalogItemBadgeWrapper>
)}
</StyledCatalogItemContainer>
);
};
CatalogItem.propTypes = {
/** Accepts className */
className: PropTypes.string,
/** Accepts id */
id: PropTypes.string,
/** Accepts css style */
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
/** Catalog item icon */
icon: PropTypes.string,
/** Catalog item text */
text: PropTypes.string,
/** Tells when the catalog item should display text */
showText: PropTypes.bool,
/** Call function when user clicked on catalog item */
onClick: PropTypes.func,
/** Tells when the catalog item should display initial on icon, text should be hidden */
showInitial: PropTypes.bool,
/** Tells when the catalog item should be end of block */
isEndOfBlock: PropTypes.bool,
/** Tells when the catalog item should display badge */
showBadge: PropTypes.bool,
/** Label in catalog item badge */
labelBadge: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/** Special icon for badge, change default badge */
iconBadge: PropTypes.string,
/** Call function when user clicked on catalog item badge */
onClickBadge: PropTypes.func,
};
CatalogItem.defaultProps = {
showText: false,
showBadge: false,
showInitial: false,
isEndOfBlock: false,
};
export default React.memo(CatalogItem);

View File

@ -0,0 +1,167 @@
import styled, { css } from "styled-components";
import Base from "../themes/base";
import { tablet } from "../utils/device";
import Text from "@appserver/components/text";
const badgeWithoutText = css`
position: absolute;
top: ${(props) => props.theme.catalogItem.badgeWithoutText.position};
right: ${(props) => props.theme.catalogItem.badgeWithoutText.position};
border-radius: 1000px;
background-color: ${(props) =>
props.theme.catalogItem.badgeWithoutText.backgroundColor};
@media ${tablet} {
min-width: ${(props) =>
props.theme.catalogItem.badgeWithoutText.tablet.size};
min-height: ${(props) =>
props.theme.catalogItem.badgeWithoutText.tablet.size};
top: ${(props) => props.theme.catalogItem.badgeWithoutText.tablet.position};
right: ${(props) =>
props.theme.catalogItem.badgeWithoutText.tablet.position};
}
`;
const StyledCatalogItemBadgeWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
min-width: ${(props) =>
props.showText
? props.theme.catalogItem.badgeWrapper.size
: props.theme.catalogItem.badgeWithoutText.size};
min-height: ${(props) =>
props.showText
? props.theme.catalogItem.badgeWrapper.size
: props.theme.catalogItem.badgeWithoutText.size};
margin-left: ${(props) =>
props.showText && props.theme.catalogItem.badgeWrapper.marginLeft};
z-index: 3;
${(props) => !props.showText && badgeWithoutText}
@media ${tablet} {
min-width: ${(props) =>
props.showText && props.theme.catalogItem.badgeWrapper.tablet.width};
min-height: ${(props) =>
props.showText && props.theme.catalogItem.badgeWrapper.tablet.height};
}
`;
StyledCatalogItemBadgeWrapper.defaultProps = { theme: Base };
const StyledCatalogItemInitialText = styled(Text)`
position: absolute;
top: 3px;
left: 0;
text-align: center;
width: ${(props) => props.theme.catalogItem.initialText.width};
line-height: ${(props) => props.theme.catalogItem.initialText.lineHeight};
color: ${(props) => props.theme.catalogItem.initialText.color};
font-size: ${(props) => props.theme.catalogItem.initialText.fontSize};
font-weight: ${(props) => props.theme.catalogItem.initialText.fontWeight};
pointer-events: none;
@media ${tablet} {
width: ${(props) => props.theme.catalogItem.initialText.tablet.width};
line-height: ${(props) =>
props.theme.catalogItem.initialText.tablet.lineHeight};
font-size: ${(props) =>
props.theme.catalogItem.initialText.tablet.fontSize};
}
`;
StyledCatalogItemInitialText.defaultProps = { theme: Base };
const StyledCatalogItemText = styled(Text)`
width: ${(props) => props.theme.catalogItem.text.width};
margin-left: ${(props) => props.theme.catalogItem.text.marginLeft};
line-height: ${(props) => props.theme.catalogItem.text.lineHeight};
z-index: 1;
pointer-events: none;
color: ${(props) => props.theme.catalogItem.text.color};
font-size: ${(props) => props.theme.catalogItem.text.fontSize};
font-weight: ${(props) => props.theme.catalogItem.text.fontWeight};
@media ${tablet} {
margin-left: ${(props) => props.theme.catalogItem.text.tablet.marginLeft};
line-height: ${(props) => props.theme.catalogItem.text.tablet.lineHeight};
font-size: ${(props) => props.theme.catalogItem.text.tablet.fontSize};
font-weight: ${(props) => props.theme.catalogItem.text.tablet.fontWeight};
}
`;
StyledCatalogItemText.defaultProps = { theme: Base };
const StyledCatalogItemImg = styled.div`
position: relative;
z-index: 1;
pointer-events: none;
svg {
width: ${(props) => props.theme.catalogItem.img.svg.width};
height: ${(props) => props.theme.catalogItem.img.svg.height};
}
@media ${tablet} {
svg {
width: ${(props) => props.theme.catalogItem.img.svg.tablet.width};
height: ${(props) => props.theme.catalogItem.img.svg.tablet.height};
}
}
`;
StyledCatalogItemImg.defaultProps = { theme: Base };
const StyledCatalogItemSibling = styled.div`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
&:hover {
background-color: ${(props) =>
props.theme.catalogItem.sibling.hover.backgroundColor};
}
`;
StyledCatalogItemSibling.defaultProps = { theme: Base };
const StyledCatalogItemContainer = styled.div`
background-color: ${(props) =>
props.theme.catalogItem.container.backgroundColor};
display: flex;
justify-content: ${(props) => (props.showText ? "space-between" : "center")};
align-items: center;
width: ${(props) => props.theme.catalogItem.container.width};
height: ${(props) => props.theme.catalogItem.container.height};
position: relative;
box-sizing: border-box;
padding: ${(props) =>
props.showText && props.theme.catalogItem.container.padding};
margin-bottom: ${(props) =>
props.isEndOfBlock && props.theme.catalogItem.container.marginBottom};
cursor: pointer;
@media ${tablet} {
height: ${(props) => props.theme.catalogItem.container.tablet.height};
padding: ${(props) =>
props.showText && props.theme.catalogItem.container.tablet.padding};
margin-bottom: ${(props) =>
props.isEndOfBlock &&
props.theme.catalogItem.container.tablet.marginBottom};
}
`;
StyledCatalogItemContainer.defaultProps = { theme: Base };
export {
StyledCatalogItemContainer,
StyledCatalogItemImg,
StyledCatalogItemInitialText,
StyledCatalogItemText,
StyledCatalogItemSibling,
StyledCatalogItemBadgeWrapper,
};

View File

@ -1623,6 +1623,80 @@ const Base = {
// },
// },
// },
catalogItem: {
container: {
width: "100%",
height: "36px",
padding: "0 20px",
marginBottom: "20px",
backgroundColor: grayLight,
tablet: {
height: "44px",
padding: "0 16px",
marginBottom: "24px",
},
},
sibling: {
hover: {
backgroundColor: lightGrayishStrongBlue,
},
},
img: {
svg: {
width: "16px",
height: "16px",
tablet: {
width: "20px",
height: "20px",
},
},
},
text: {
width: "100%",
marginLeft: "8px",
lineHeight: "20px",
color: cyanBlueDarkShade,
fontSize: "13px",
fontWeight: 600,
tablet: {
marginLeft: "12px",
lineHeight: "16px",
fontSize: "14px",
fontWeight: "bold",
},
},
initialText: {
color: white,
width: "16px",
lineHeight: "15px",
fontSize: "9px",
fontWeight: 700,
tablet: {
width: "20px",
lineHeight: "19px",
fontSize: "11px",
},
},
badgeWrapper: {
size: "20px",
marginLeft: "20px",
tablet: {
width: "44px",
height: "44px",
},
},
badgeWithoutText: {
size: "6px",
position: "-3px",
backgroundColor: orangeMain,
tablet: {
size: "8px",
position: "-4px",
},
},
},
};
export default Base;