Commit 86f4ef29 authored by Yannis Barlas's avatar Yannis Barlas
Browse files

Merge branch 'buttons' into 'master'

Buttons

See merge request !7
parents fbc17bdd bc35df27
......@@ -25,4 +25,9 @@ describe('Button', () => {
const tree = create(<Button primary>Hi</Button>).toJSON()
expect(tree).toHaveStyleRule('background', theme.colorPrimary)
})
test('Render icon correctly', () => {
const { queryAllByTestId } = render(<Button icon="plus" primary />)
expect(queryAllByTestId('iconButton')).toHaveLength(1)
})
})
import React from 'react'
import { fireEvent } from '@testing-library/react'
import 'jest-styled-components'
import { Dropdown } from '../ui'
import { create, render } from './_helpers'
describe('Dropdown', () => {
const itemsList = [
{
id: '1',
onClick: () => {},
title: 'item 1',
},
{
id: '2',
onClick: () => {},
title: 'item 2',
},
]
test('renders correctly', () => {
const tree = create(
<Dropdown itemsList={itemsList} primary>
Hello test
</Dropdown>,
).toJSON()
expect(tree).toMatchSnapshot()
})
test('runs function on click', () => {
const handleClickItem = jest.fn()
itemsList[0].onClick = handleClickItem
const { getByText } = render(
<Dropdown itemsList={itemsList}>Hello</Dropdown>,
)
expect(handleClickItem).toHaveBeenCalledTimes(0)
fireEvent.click(getByText('item 1'))
expect(handleClickItem).toHaveBeenCalledTimes(1)
})
})
......@@ -66,6 +66,12 @@ exports[`Button renders correctly 1`] = `
data-test-id="button"
type="button"
>
Hello
<span
data-testid="iconButton"
>
<span>
Hello
</span>
</span>
</button>
`;
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Dropdown renders correctly 1`] = `
.c0 {
display: inline-block;
position: relative;
}
.c1 {
background: #E7E7E7;
border: 1px solid #AAA;
border-radius: 0;
color: #111;
font-family: 'Fira Sans Condensed';
font-size: 16px;
line-height: calc(8px * 3);
min-width: calc(8px * 12);
padding: 8px;
background: #0B65CB;
color: #FFF;
line-height: calc(8px * 4);
min-width: calc(8px * 16);
text-transform: uppercase;
cursor: pointer;
display: inline-block;
white-space: nowrap;
}
.c1:focus,
.c1:hover {
background-color: hsl(0,0%,63.4%);
-webkit-transition: 0.2s ease;
transition: 0.2s ease;
}
.c1:active {
background-color: hsl(0,0%,45.3%);
}
.c1[disabled] {
cursor: not-allowed;
opacity: 0.5;
}
.c1[disabled]:focus,
.c1[disabled]:hover,
.c1[disabled]:active {
background: #E7E7E7;
}
.c1:focus,
.c1:hover {
background-color: hsl(211.89999999999998,89.7%,29.4%);
}
.c1:active {
background-color: hsl(211.89999999999998,89.7%,21%);
}
.c1[disabled]:focus,
.c1[disabled]:hover,
.c1[disabled]:active {
background: #0B65CB;
}
.c1:after {
border-left: calc(8px / 1.5) solid transparent;
border-right: calc(8px / 1.5) solid transparent;
border-top: calc(8px / 1.5) solid #E7E7E7;
content: '';
height: 0;
position: absolute;
right: calc(8px * 2);
top: 50%;
-webkit-transform: translateY(-50%);
-ms-transform: translateY(-50%);
transform: translateY(-50%);
width: 0;
}
.c2 {
background-color: white;
border-color: #AAA;
border-style: solid;
border-width: 1px;
color: #111;
display: none;
font-family: 'Fira Sans Condensed';
font-size: 16px;
list-style-type: none;
margin: calc(8px / 4) 0 0 0;
padding: 0;
position: absolute;
top: 100%;
width: 100%;
z-index: 9;
}
.c3 {
cursor: pointer;
font-family: 'Fira Sans Condensed';
font-size: 16px;
padding: 8px;
white-space: normal;
word-break: break-word;
}
.c3:hover {
background-color: #F1F1F1;
}
<div
className="c0"
>
<button
className="c1"
onBlur={[Function]}
onClick={[Function]}
type="button"
>
<span
data-testid="iconButton"
>
<span>
Hello test
</span>
</span>
</button>
<ul
className="c2"
>
<li
className="c3"
onClick={[Function]}
>
item 1
</li>
<li
className="c3"
onClick={[Function]}
>
item 2
</li>
</ul>
</div>
`;
......@@ -6,10 +6,31 @@ export const Primary = () => <Button primary>Hello th sdfgzsdfgdgsfis</Button>
export const PrimaryDisabled = () => (
<Button disabled primary>
Hello th sdfgzsdfgdgsfis
Hello disabled
</Button>
)
export const PrimaryWithIcon = () => (
<Button icon="plus-circle" primary>
Add
</Button>
)
export const Secondary = () => <Button>Hello</Button>
export const SecondaryDisabled = () => <Button disabled>Hello</Button>
export const SecondaryWithIcon = () => (
<Button icon="arrow_right" secondary>
Hello
</Button>
)
// When we pass only an icon parameter we get a round button
export const IconOnlyButtonPrimary = () => <Button icon="plus" primary />
export const IconOnlyButtonSecondary = () => <Button icon="plus" secondary />
export const IconAtTheEnd = () => (
<Button icon="plus" iconPosition="end" primary>
Add
</Button>
)
export default { title: 'Button' }
export default { component: Button, title: 'Button' }
import React from 'react'
import { Dropdown } from '../ui'
const itemsList = [
{
id: 1,
onClick: () => {},
title: 'item 1n asfasf asfkaskfh ',
},
{ id: 2, title: 'item 2' },
]
export const DropDownButtonPrimary = () => (
<Dropdown itemsList={itemsList} primary>
Test
</Dropdown>
)
export const DropDownButtonSecondary = () => (
<Dropdown itemsList={itemsList}>Test</Dropdown>
)
export const DropDownButtonPrimaryIcon = () => (
<Dropdown icon="user" itemsList={itemsList} primary>
Test
</Dropdown>
)
export const DropDownButtonIconEnd = () => (
<Dropdown icon="user" iconPosition="end" itemsList={itemsList} secondary>
Test
</Dropdown>
)
export default { component: Dropdown, title: 'Dropdown Button' }
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { Button as UIButton, Icon as UIIcon } from '@pubsweet/ui'
import { override, th } from '@pubsweet/ui-toolkit'
import { Button as UIButton } from '@pubsweet/ui'
const Icon = styled(UIIcon)`
font-family: ${th('fontInterface')};
font-size: ${th('fontSizeBase')};
height: ${th('fontSizeBase')};
vertical-align: top;
width: ${th('fontSizeBase')};
/* stylelint-disable-next-line order/properties-alphabetical-order */
${override('ui.Button.Icon')}
`
const Button = ({ children, ...props }) => (
<UIButton {...props}>{children}</UIButton>
)
const StyledButtonIcon = styled(UIButton)`
border-color: ${props =>
props.primary ? th('colorSecondary') : th('colorPrimary')};
border-radius: 50%;
border-style: ${th('borderStyle')};
border-width: ${th('borderWidth')};
font-family: ${th('fontInterface')};
font-size: ${th('fontSizeBase')};
height: calc(${th('gridUnit')} * 6);
line-height: calc(${th('gridUnit')} * 4);
min-width: calc(${th('gridUnit')} * 6);
padding: 0;
text-align: center;
vertical-align: top;
&:hover {
border-style: ${th('borderStyle')};
border-width: ${th('borderWidth')};
}
/* stylelint-disable-next-line order/properties-alphabetical-order */
${override('ui.Button.Icon')}
`
/**
* A component that can display a Button
*/
const Button = ({ children, icon, iconPosition, ...props }) =>
children == null && icon !== null ? (
<StyledButtonIcon {...props}>
<Icon
color={props.primary ? th('colorTextReverse') : th('colorPrimary')}
data-testid="iconButton"
{...props}
>
{icon}{' '}
</Icon>
</StyledButtonIcon>
) : (
<UIButton {...props}>
<span data-testid="iconButton">
{icon && iconPosition === 'start' && (
<Icon
color={props.primary ? th('colorTextReverse') : th('colorPrimary')}
data-testid="leftIconButton"
primary={props.primary}
{...props}
>
{icon}{' '}
</Icon>
)}
<span>{children}</span>
{icon && iconPosition === 'end' && (
<Icon
data-testid="rightIconButton"
{...props}
color={props.primary ? th('colorTextReverse') : th('colorPrimary')}
>
{icon}{' '}
</Icon>
)}
</span>
</UIButton>
)
Button.propTypes = {
/** The contents of the button (text, icon etc.) */
children: PropTypes.node.isRequired,
children: PropTypes.node,
/** ID to be used in test selectors */
'data-test-id': PropTypes.string,
/** Icon name (An icon name, from the Feather icon set.) */
icon: PropTypes.string,
/** Icon Position (Defines the position of the icon (if is is at the start of the button , or at the end)) */
iconPosition: PropTypes.oneOf(['start', 'end']),
/** Makes button a primary button */
primary: PropTypes.bool,
}
Button.defaultProps = {
children: null,
'data-test-id': 'button',
icon: null,
iconPosition: 'start',
primary: false,
}
......
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import { Button as UIButton, Icon as UIIcon } from '@pubsweet/ui'
import { override, th } from '@pubsweet/ui-toolkit'
const Icon = styled(UIIcon)`
font-family: ${th('fontInterface')};
font-size: ${th('fontSizeBase')};
height: ${th('fontSizeBase')};
vertical-align: top;
width: ${th('fontSizeBase')};
/* stylelint-disable-next-line order/properties-alphabetical-order */
${override('ui.Button.Icon')}
`
const StyledDropdown = styled.div`
display: inline-block;
position: relative;
/* stylelint-disable-next-line order/properties-alphabetical-order */
${override('ui.Dropdown.Wrapper')}
`
const DropdownTitle = styled(UIButton)`
cursor: pointer;
display: inline-block;
white-space: nowrap;
&:after {
border-left: calc(${th('gridUnit')} / 1.5) solid transparent;
border-right: calc(${th('gridUnit')} / 1.5) solid transparent;
border-top: calc(${th('gridUnit')} / 1.5) solid
${props => (props.primary ? th('colorSecondary') : th('colorText'))};
content: '';
height: 0;
position: absolute;
right: calc(${th('gridUnit')} * 2);
top: 50%;
transform: translateY(-50%);
width: 0;
}
/* stylelint-disable-next-line order/properties-alphabetical-order */
${override('ui.Dropdown.Title')}
`
const DropdownMenu = styled.ul`
background-color: ${th('colorBackground')};
border-color: ${th('colorBorder')};
border-style: ${th('borderStyle')};
border-width: ${th('borderWidth')};
color: ${th('colorText')};
display: ${props => (props.menuIsOpen ? 'block' : 'none')};
font-family: ${th('fontInterface')};
font-size: ${th('fontSizeBase')};
list-style-type: none;
margin: calc(${th('gridUnit')} / 4) 0 0 0;
padding: 0;
position: absolute;
top: 100%;
width: 100%;
z-index: 9;
/* stylelint-disable-next-line order/properties-alphabetical-order */
${override('ui.Dropdown.Menu')}
`
const Item = styled.li`
cursor: pointer;
font-family: ${th('fontInterface')};
font-size: ${th('fontSizeBase')};
padding: ${th('gridUnit')};
white-space: normal;
word-break: break-word;
&:hover {
background-color: ${th('colorBackgroundHue')};
}
/* stylelint-disable-next-line order/properties-alphabetical-order */
${override('ui.Dropdown.MenuItem')}
`
const Dropdown = ({ children, icon, iconPosition, itemsList, primary }) => {
const [menuIsOpen, setMenuIsOpen] = useState(false)
return (
<StyledDropdown>
<DropdownTitle
onBlur={() => setMenuIsOpen(false)}
onClick={() => setMenuIsOpen(!menuIsOpen)}
primary={primary}
>
<span data-testid="iconButton">
{icon && iconPosition === 'start' && (
<Icon
color={primary ? th('colorTextReverse') : th('colorPrimary')}
data-testid="leftIconButton"
primary={primary}
>
{icon}{' '}
</Icon>
)}
<span>{children}</span>
{icon && iconPosition === 'end' && (
<Icon
color={primary ? th('colorTextReverse') : th('colorPrimary')}
data-testid="rightIconButton"
>
{icon}{' '}
</Icon>
)}
</span>
</DropdownTitle>
<DropdownMenu menuIsOpen={menuIsOpen}>
{itemsList.map(item => (
<Item
key={item.id}
onClick={() => {
item.onClick()
setMenuIsOpen(false)
}}
{...item.props}
onMouseDown={e => e.preventDefault()}
>
{item.title}
</Item>
))}
</DropdownMenu>
</StyledDropdown>
)
}
Dropdown.propTypes = {
/** Content of the button of the dropdown */
children: PropTypes.string.isRequired,
/** Icon name (An icon name, from the Feather icon set.) */
icon: PropTypes.string,
/** Icon Position (Defines the position of the icon (if is is at the start of the button , or at the end)) */
iconPosition: PropTypes.oneOf(['start', 'end']),
/** List of the items for the drop down items */
itemsList: PropTypes.arrayOf(
PropTypes.shape({
/** The key for the item */
id: PropTypes.string.isRequired,
/** The click function for the item */
onClick: PropTypes.func.isRequired,
/** The title for the item */
title: PropTypes.node.isRequired,
}),
),
/** Primary property for the dropdown, if it is false(or not set) then the dropdown will be secondary */
primary: PropTypes.bool,
}
Dropdown.defaultProps = {
icon: null,
iconPosition: 'start',
itemsList: [],
primary: false,
}
export default Dropdown
export { default as Button } from './Button'
export { default as Dropdown } from './Dropdown'
export { default as Section } from './Section'
export { default as SectionItem } from './SectionItem'
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment