Web:Components:MenuItem: create menu item component
This commit is contained in:
parent
94a466ed48
commit
20dcdde09b
37
packages/asc-web-components/menu-item/README.md
Normal file
37
packages/asc-web-components/menu-item/README.md
Normal file
@ -0,0 +1,37 @@
|
||||
# DropDownItem
|
||||
|
||||
Is a item of DropDown or ContextMenu component
|
||||
|
||||
### Usage
|
||||
|
||||
```js
|
||||
import MenuItem from "@appserver/components/menu-item";
|
||||
```
|
||||
|
||||
```jsx
|
||||
<MenuItem
|
||||
isSeparator={false}
|
||||
isHeader={false}
|
||||
label="Button 1"
|
||||
icon="static/images/nav.logo.react.svg"
|
||||
onClick={() => console.log("Button 1 clicked")}
|
||||
/>
|
||||
```
|
||||
|
||||
An item can act as separator, header, line, line with arrow or container.
|
||||
|
||||
When used as container, it will retain all styling features and positioning. To disable hover effects in container mode, you can use _noHover_ property.`
|
||||
|
||||
### Properties
|
||||
|
||||
| Props | Type | Required | Values | Default | Description |
|
||||
| ------------- | :------------: | :------: | :----: | :-------------: | ---------------------------------------------------------- |
|
||||
| `className` | `string` | - | - | - | Accepts class |
|
||||
| `id` | `string` | - | - | - | Accepts id |
|
||||
| `icon` | `string` | - | - | - | Dropdown item icon |
|
||||
| `label` | `string` | - | - | `Dropdown item` | Dropdown item text |
|
||||
| `isHeader` | `bool` | - | - | `false` | Tells when the dropdown item should display like header |
|
||||
| `isSeparator` | `bool` | - | - | `false` | Tells when the dropdown item should display like separator |
|
||||
| `noHover` | `bool` | - | - | `false` | Disable default style hover effect |
|
||||
| `onClick` | `func` | - | - | - | What the dropdown item will trigger when clicked |
|
||||
| `style` | `obj`, `array` | - | - | - | Accepts css style |
|
80
packages/asc-web-components/menu-item/index.js
Normal file
80
packages/asc-web-components/menu-item/index.js
Normal file
@ -0,0 +1,80 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { ReactSVG } from "react-svg";
|
||||
|
||||
import { StyledMenuItem, StyledText, IconWrapper } from "./styled-menu-item";
|
||||
|
||||
//TODO: Add arrow type
|
||||
const MenuItem = (props) => {
|
||||
//console.log("MenuItem render");
|
||||
const {
|
||||
isHeader,
|
||||
isSeparator,
|
||||
label,
|
||||
icon,
|
||||
children,
|
||||
onClick,
|
||||
className,
|
||||
} = props;
|
||||
|
||||
const onClickAction = (e) => {
|
||||
onClick && onClick(e);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledMenuItem {...props} className={className} onClick={onClickAction}>
|
||||
{icon && (
|
||||
<IconWrapper isHeader={isHeader}>
|
||||
<ReactSVG src={icon} className="drop-down-item_icon" />
|
||||
</IconWrapper>
|
||||
)}
|
||||
{isSeparator ? (
|
||||
<></>
|
||||
) : label ? (
|
||||
<StyledText isHeader={isHeader} truncate={true}>
|
||||
{label}
|
||||
</StyledText>
|
||||
) : (
|
||||
children && children
|
||||
)}
|
||||
</StyledMenuItem>
|
||||
);
|
||||
};
|
||||
|
||||
MenuItem.propTypes = {
|
||||
/** Tells when the menu item should display like separator */
|
||||
isSeparator: PropTypes.bool,
|
||||
/** Tells when the menu item should display like header */
|
||||
isHeader: PropTypes.bool,
|
||||
/** Accepts tab-index */
|
||||
tabIndex: PropTypes.number,
|
||||
/** menu item text */
|
||||
label: PropTypes.string,
|
||||
/** menu item icon */
|
||||
icon: PropTypes.string,
|
||||
/** Disable default style hover effect */
|
||||
noHover: PropTypes.bool,
|
||||
/** What the menu item will trigger when clicked */
|
||||
onClick: PropTypes.func,
|
||||
/** Children elements */
|
||||
children: PropTypes.any,
|
||||
/** Accepts class */
|
||||
className: PropTypes.string,
|
||||
/** Accepts id */
|
||||
id: PropTypes.string,
|
||||
/** Accepts css style */
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
||||
/** Accepts css text-overflow */
|
||||
textOverflow: PropTypes.bool,
|
||||
};
|
||||
|
||||
MenuItem.defaultProps = {
|
||||
isSeparator: false,
|
||||
isHeader: false,
|
||||
noHover: false,
|
||||
textOverflow: false,
|
||||
tabIndex: -1,
|
||||
label: "",
|
||||
};
|
||||
|
||||
export default MenuItem;
|
51
packages/asc-web-components/menu-item/menu-item.stories.js
Normal file
51
packages/asc-web-components/menu-item/menu-item.stories.js
Normal file
@ -0,0 +1,51 @@
|
||||
import React from "react";
|
||||
import MenuItem from ".";
|
||||
|
||||
export default {
|
||||
title: "Components/MenuItem",
|
||||
component: MenuItem,
|
||||
|
||||
argTypes: {
|
||||
onClick: { action: "onClick" },
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: `Is a item of DropDown or ContextMenu component
|
||||
|
||||
An item can act as separator, header, line, line with arrow or container.
|
||||
|
||||
When used as container, it will retain all styling features and positioning. To disable hover effects in container mode, you can use _noHover_ property.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = () => {
|
||||
return (
|
||||
<div style={{ width: "250px", position: "relative" }}>
|
||||
<MenuItem
|
||||
icon={"static/images/nav.logo.react.svg"}
|
||||
label="Header(tablet or mobile)"
|
||||
isHeader={true}
|
||||
onClick={() => console.log("Header clicked")}
|
||||
noHover={true}
|
||||
/>
|
||||
<MenuItem
|
||||
icon={"static/images/nav.logo.react.svg"}
|
||||
label="First item"
|
||||
onClick={() => console.log("Button 1 clicked")}
|
||||
/>
|
||||
<MenuItem isSeparator={true} />
|
||||
<MenuItem
|
||||
icon={"static/images/nav.logo.react.svg"}
|
||||
label="Item after separator"
|
||||
onClick={() => console.log("Button 2 clicked")}
|
||||
/>
|
||||
<MenuItem onClick={() => console.log("Button 3 clicked")}>
|
||||
<div>some child without styles</div>
|
||||
</MenuItem>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export const Default = Template.bind({});
|
76
packages/asc-web-components/menu-item/menu-item.test.js
Normal file
76
packages/asc-web-components/menu-item/menu-item.test.js
Normal file
@ -0,0 +1,76 @@
|
||||
import React from "react";
|
||||
import { mount, shallow } from "enzyme";
|
||||
import MenuItem from ".";
|
||||
|
||||
const baseProps = {
|
||||
isSeparator: false,
|
||||
isHeader: false,
|
||||
tabIndex: -1,
|
||||
label: "test",
|
||||
disabled: false,
|
||||
icon: "static/images/nav.logo.react.svg",
|
||||
noHover: false,
|
||||
onClick: jest.fn(),
|
||||
};
|
||||
|
||||
describe("<MenuItem />", () => {
|
||||
it("renders without error", () => {
|
||||
const wrapper = mount(<MenuItem {...baseProps} />);
|
||||
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
it("check isSeparator props", () => {
|
||||
const wrapper = mount(<MenuItem {...baseProps} isSeparator={true} />);
|
||||
|
||||
expect(wrapper.prop("isSeparator")).toEqual(true);
|
||||
});
|
||||
|
||||
it("check isHeader props", () => {
|
||||
const wrapper = mount(<MenuItem {...baseProps} isHeader={true} />);
|
||||
|
||||
expect(wrapper.prop("isHeader")).toEqual(true);
|
||||
});
|
||||
|
||||
it("check noHover props", () => {
|
||||
const wrapper = mount(<MenuItem {...baseProps} noHover={true} />);
|
||||
|
||||
expect(wrapper.prop("noHover")).toEqual(true);
|
||||
});
|
||||
|
||||
it("causes function onClick()", () => {
|
||||
const onClick = jest.fn();
|
||||
|
||||
const wrapper = shallow(
|
||||
<MenuItem id="test" {...baseProps} onClick={onClick} />
|
||||
);
|
||||
|
||||
wrapper.find("#test").simulate("click");
|
||||
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("render without child", () => {
|
||||
const wrapper = shallow(<MenuItem>test</MenuItem>);
|
||||
|
||||
expect(wrapper.props.children).toEqual(undefined);
|
||||
});
|
||||
|
||||
it("accepts id", () => {
|
||||
const wrapper = mount(<MenuItem {...baseProps} id="testId" />);
|
||||
|
||||
expect(wrapper.prop("id")).toEqual("testId");
|
||||
});
|
||||
|
||||
it("accepts className", () => {
|
||||
const wrapper = mount(<MenuItem {...baseProps} className="test" />);
|
||||
|
||||
expect(wrapper.prop("className")).toEqual("test");
|
||||
});
|
||||
|
||||
it("accepts style", () => {
|
||||
const wrapper = mount(<MenuItem {...baseProps} style={{ color: "red" }} />);
|
||||
|
||||
expect(wrapper.getDOMNode().style).toHaveProperty("color", "red");
|
||||
});
|
||||
});
|
163
packages/asc-web-components/menu-item/styled-menu-item.js
Normal file
163
packages/asc-web-components/menu-item/styled-menu-item.js
Normal file
@ -0,0 +1,163 @@
|
||||
import styled, { css } from "styled-components";
|
||||
import Base from "../themes/base";
|
||||
|
||||
import Text from "../text/";
|
||||
|
||||
import { tablet } from "../utils/device";
|
||||
import { isMobile } from "react-device-detect";
|
||||
|
||||
const styledHeaderText = css`
|
||||
font-size: ${(props) => props.theme.menuItem.text.header.fontSize};
|
||||
line-height: ${(props) => props.theme.menuItem.text.header.lineHeight};
|
||||
`;
|
||||
|
||||
const styledMobileText = css`
|
||||
font-size: ${(props) => props.theme.menuItem.text.mobile.fontSize};
|
||||
line-height: ${(props) => props.theme.menuItem.text.mobile.lineHeight};
|
||||
`;
|
||||
|
||||
const StyledText = styled(Text)`
|
||||
font-weight: ${(props) => props.theme.menuItem.text.fontWeight};
|
||||
font-size: ${(props) => props.theme.menuItem.text.fontSize};
|
||||
line-height: ${(props) => props.theme.menuItem.text.lineHeight};
|
||||
margin: ${(props) => props.theme.menuItem.text.margin};
|
||||
color: ${(props) => props.theme.menuItem.text.color};
|
||||
text-align: left;
|
||||
text-transform: none;
|
||||
text-decoration: none;
|
||||
user-select: none;
|
||||
${isMobile
|
||||
? (props) => (props.isHeader ? styledHeaderText : styledMobileText)
|
||||
: null}
|
||||
|
||||
@media ${tablet} {
|
||||
${(props) => (props.isHeader ? styledHeaderText : styledMobileText)}
|
||||
}
|
||||
`;
|
||||
StyledText.defaultProps = { theme: Base };
|
||||
|
||||
const StyledMenuItem = styled.div`
|
||||
display: ${(props) =>
|
||||
props.isHeader && !isMobile
|
||||
? "none"
|
||||
: props.textOverflow
|
||||
? "block"
|
||||
: "flex"};
|
||||
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: ${(props) =>
|
||||
isMobile
|
||||
? props.isHeader
|
||||
? props.theme.menuItem.header.height
|
||||
: props.theme.menuItem.mobile.height
|
||||
: props.theme.menuItem.height};
|
||||
max-height: ${(props) =>
|
||||
isMobile
|
||||
? props.isHeader
|
||||
? props.theme.menuItem.header.height
|
||||
: props.theme.menuItem.mobile.height
|
||||
: props.theme.menuItem.height};
|
||||
border: none;
|
||||
border-bottom: ${(props) =>
|
||||
isMobile && props.isHeader
|
||||
? props.theme.menuItem.header.borderBottom
|
||||
: props.theme.menuItem.borderBottom};
|
||||
cursor: ${(props) => (isMobile && props.isHeader ? "default" : "pointer")};
|
||||
margin: 0;
|
||||
margin-bottom: ${(props) =>
|
||||
isMobile && props.isHeader
|
||||
? props.theme.menuItem.header.marginBottom
|
||||
: props.theme.menuItem.marginBottom};
|
||||
padding: ${(props) =>
|
||||
isMobile
|
||||
? props.theme.menuItem.mobile.padding
|
||||
: props.theme.menuItem.padding};
|
||||
box-sizing: border-box;
|
||||
background: none;
|
||||
outline: 0 !important;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
|
||||
@media ${tablet} {
|
||||
display: ${(props) => (props.textOverflow ? "block" : "flex")};
|
||||
height: ${(props) =>
|
||||
props.isHeader
|
||||
? props.theme.menuItem.header.height
|
||||
: props.theme.menuItem.mobile.height};
|
||||
max-height: ${(props) =>
|
||||
props.isHeader
|
||||
? props.theme.menuItem.header.height
|
||||
: props.theme.menuItem.mobile.height};
|
||||
padding: ${(props) => props.theme.menuItem.mobile.padding};
|
||||
border: none;
|
||||
border-bottom: ${(props) =>
|
||||
props.isHeader
|
||||
? props.theme.menuItem.header.borderBottom
|
||||
: props.theme.menuItem.borderBottom};
|
||||
margin-bottom: ${(props) =>
|
||||
props.isHeader
|
||||
? props.theme.menuItem.header.marginBottom
|
||||
: props.theme.menuItem.marginBottom};
|
||||
cursor: ${(props) => (props.isHeader ? "default" : "pointer")};
|
||||
}
|
||||
|
||||
.drop-down-item_icon {
|
||||
path {
|
||||
fill: ${(props) => props.theme.menuItem.svgFill};
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: ${(props) =>
|
||||
props.noHover
|
||||
? props.theme.menuItem.background
|
||||
: props.theme.menuItem.hover};
|
||||
}
|
||||
${(props) =>
|
||||
props.isSeparator &&
|
||||
css`
|
||||
border-bottom: ${props.theme.menuItem.separator.borderBottom};
|
||||
cursor: default !important;
|
||||
margin: ${props.theme.menuItem.separator.margin};
|
||||
height: ${props.theme.menuItem.separator.height};
|
||||
width: ${props.theme.menuItem.separator.width};
|
||||
&:hover {
|
||||
cursor: default !important;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
StyledMenuItem.defaultProps = { theme: Base };
|
||||
|
||||
const IconWrapper = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: ${(props) =>
|
||||
props.isHeader && isMobile
|
||||
? props.theme.menuItem.iconWrapper.header.width
|
||||
: props.theme.menuItem.iconWrapper.width};
|
||||
height: ${(props) =>
|
||||
props.isHeader && isMobile
|
||||
? props.theme.menuItem.iconWrapper.header.height
|
||||
: props.theme.menuItem.iconWrapper.height};
|
||||
|
||||
@media ${tablet} {
|
||||
width: ${(props) =>
|
||||
props.isHeader
|
||||
? props.theme.menuItem.iconWrapper.header.width
|
||||
: props.theme.menuItem.iconWrapper.width};
|
||||
height: ${(props) =>
|
||||
props.isHeader
|
||||
? props.theme.menuItem.iconWrapper.header.height
|
||||
: props.theme.menuItem.iconWrapper.height};
|
||||
}
|
||||
|
||||
svg {
|
||||
&:not(:root) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
`;
|
||||
IconWrapper.defaultProps = { theme: Base };
|
||||
|
||||
export { StyledMenuItem, StyledText, IconWrapper };
|
@ -0,0 +1,3 @@
|
||||
<svg width="4" height="8" viewBox="0 0 4 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.5255 4L0.153211 1.10054C-0.0755111 0.820987 -0.0446084 0.400121 0.222235 0.160507C0.489078 -0.0791068 0.890813 -0.0467326 1.11954 0.232817L3.8468 3.56614C4.05107 3.8158 4.05107 4.1842 3.8468 4.43386L1.11954 7.76718C0.890813 8.04673 0.489078 8.07911 0.222235 7.83949C-0.0446084 7.59988 -0.0755111 7.17901 0.153211 6.89946L2.5255 4Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 460 B |
@ -0,0 +1,3 @@
|
||||
<svg width="4" height="8" viewBox="0 0 4 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.146447 7.35355C-0.0488156 7.15829 -0.0488156 6.84171 0.146447 6.64645L2.79289 4L0.146446 1.35355C-0.0488158 1.15829 -0.0488158 0.841709 0.146446 0.646447C0.341708 0.451184 0.658291 0.451184 0.853553 0.646447L3.85355 3.64645C4.04882 3.84171 4.04882 4.15829 3.85355 4.35355L0.853553 7.35355C0.658291 7.54882 0.341709 7.54882 0.146447 7.35355Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 510 B |
@ -1613,6 +1613,54 @@ const Base = {
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
|
||||
menuItem: {
|
||||
iconWrapper: {
|
||||
width: "16px",
|
||||
height: "16px",
|
||||
header: {
|
||||
width: "24px",
|
||||
height: "24px",
|
||||
},
|
||||
},
|
||||
separator: {
|
||||
borderBottom: `1px solid ${grayLightMid} !important`,
|
||||
margin: "6px 16px 6px 16px !important",
|
||||
height: "1px !important",
|
||||
width: "calc(100% - 32px) !important",
|
||||
},
|
||||
text: {
|
||||
header: {
|
||||
fontSize: "15px",
|
||||
lineHeight: "16px",
|
||||
},
|
||||
mobile: {
|
||||
fontSize: "13px",
|
||||
lineHeight: "36px",
|
||||
},
|
||||
fontSize: "12px",
|
||||
lineHeight: "30px",
|
||||
fontWeight: "600",
|
||||
margin: "0 0 0 8px",
|
||||
color: black,
|
||||
},
|
||||
hover: grayLight,
|
||||
background: "none",
|
||||
svgFill: black,
|
||||
header: {
|
||||
height: "55px",
|
||||
borderBottom: `1px solid ${grayLightMid}`,
|
||||
marginBottom: "6px",
|
||||
},
|
||||
height: "30px",
|
||||
borderBottom: "none",
|
||||
marginBottom: "0",
|
||||
padding: "0 12px",
|
||||
mobile: {
|
||||
height: "36px",
|
||||
padding: "0 16px",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default Base;
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit b1063eae56d183b5c0b6eb887115c378f3941ebe
|
||||
Subproject commit 8177bad15d567d997a79478a65d32662a6f773b1
|
Loading…
Reference in New Issue
Block a user