Skip to content
This repository has been archived by the owner on Oct 6, 2020. It is now read-only.

Commit

Permalink
feat(Dropdown): Style updates (#68)
Browse files Browse the repository at this point in the history
  • Loading branch information
mathewmorris authored Sep 6, 2019
1 parent 40eeffb commit ca36b19
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 225 deletions.
125 changes: 56 additions & 69 deletions src/Dropdown/Dropdown.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { Manager, Reference, Popper } from 'react-popper';
import { css, keyframes } from 'styled-components';
import Box from '../Box';
import Portal from '../Portal';
import Icon from '../Icon';
import { useKeyPress } from '../hooks';
import { createComponent, themeGet, findNextFocusableElement, findPreviousFocusableElement } from '../utils';
import { createComponent, findNextFocusableElement, findPreviousFocusableElement } from '../utils';

const DropdownContext = React.createContext({});

Expand Down Expand Up @@ -238,6 +239,7 @@ const DropdownMenu = createComponent({
opacity: 0.75;
transform: scale(0.75);
transform-origin: ${PLACEMENT_TRANSITION_ORIGINS[placement]};
padding: 8px;
${(transitionState === 'entering' || transitionState === 'entered') &&
css`
Expand All @@ -246,57 +248,25 @@ const DropdownMenu = createComponent({
`,
});

const DropdownHeader = createComponent({
name: 'DropdownHeader',
tag: 'header',
style: css`
padding: 0.75rem 1rem 0;
`,
});

const DropdownHeaderInner = createComponent({
name: 'DropdownHeaderInner',
style: css`
padding: 0 0 0.25rem;
border-bottom: 2px solid ${p => p.theme.colors.greyLight};
Dropdown.Divider = createComponent({
name: 'DropdownDivider',
tag: 'hr',
style: ({ theme }) => css`
background: ${theme.colors.greyLight};
height: 1px;
border: 0;
margin-left: -8px;
margin-right: -8px;
`,
});

Dropdown.Title = createComponent({
name: 'DropdownTitle',
tag: 'span',
style: css`
display: block;
font-weight: bold;
font-size: 1rem;
margin: 0;
`,
});

Dropdown.Header = ({ title, children }) => (
<DropdownHeader>
<DropdownHeaderInner>
{title && <Dropdown.Title>{title}</Dropdown.Title>}
{children}
</DropdownHeaderInner>
</DropdownHeader>
);

Dropdown.Body = createComponent({
name: 'DropdownBody',
as: Box,
style: css`
padding: 1rem;
`,
});

Dropdown.SectionTitle = createComponent({
name: 'DropdownSectionTitle',
tag: 'span',
style: ({ theme }) => css`
display: block;
font-weight: 600;
color: ${theme.colors.primary};
font-weight: 700;
color: ${theme.colors.greyDark};
`,
});

Expand All @@ -307,37 +277,62 @@ const StyledDropdownItem = createComponent({
role: 'button',
}),
as: Box,
style: ({ disabled, theme }) => css`
style: ({ disabled, theme, color, icon, iconProps, selected }) => css`
display: block;
width: calc(100% + 2rem);
flex: 1;
opacity: ${disabled ? 0.3 : 1};
pointer-events: ${disabled ? 'none' : 'initial'};
user-select: ${disabled ? 'none' : 'initial'};
text-decoration: none;
color: inherit;
color: ${color || theme.colors.greyDarkest};
cursor: pointer;
margin: 0 calc(-1rem);
padding: 0.25rem 1rem;
margin: 0;
padding: 8px;
transition: 125ms background;
outline: none;
appearance: none;
border: 0;
border-radius: ${theme.radius}px;
font: inherit;
font-size: 14px;
font-weight: 500;
text-align: left;
& + ${Dropdown.SectionTitle} {
margin-top: 1rem;
}
position: relative;
&:hover,
&:focus {
color: inherit;
color: ${color || theme.colors.greyDarkest};
background: ${theme.colors.greyLightest};
}
${icon &&
css`
padding-left: ${(iconProps.size || 16) + 16}px;
`}
${selected &&
css`
color: ${theme.colors.primary};
&:hover,
&:active,
&:focus {
color: ${theme.colors.primary};
}
`}
`,
});

Dropdown.Item = function DropdownItem({ closeOnClick = true, onClick, ...props }) {
const StyledIcon = createComponent({
name: 'DropdownIcon',
as: Icon,
style: css`
position: absolute;
left: 8px;
`,
});

Dropdown.Item = ({ closeOnClick = true, onClick, children, icon, iconProps = {}, ...props }) => {
const { close } = useContext(DropdownContext);
const handleClick = () => {
if (closeOnClick) {
Expand All @@ -347,18 +342,10 @@ Dropdown.Item = function DropdownItem({ closeOnClick = true, onClick, ...props }
onClick();
}
};
return <StyledDropdownItem onClick={handleClick} {...props} />;
return (
<StyledDropdownItem onClick={handleClick} icon={icon} iconProps={iconProps} {...props}>
{icon && <StyledIcon name={icon} {...iconProps} />}
{children}
</StyledDropdownItem>
);
};

Dropdown.Footer = createComponent({
name: 'DropdownFooter',
as: Box,
props: () => ({
as: 'footer',
}),
style: ({ theme }) => css`
background: ${theme.colors.greyLightest};
padding: 0.75rem 1rem;
border-radius: 0 0 ${themeGet('radius')}px ${themeGet('radius')}px;
`,
});
14 changes: 6 additions & 8 deletions src/Dropdown/Dropdown.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,10 @@ describe('<Dropdown />', () => {
wrapper.setAttribute('tabindex', 1);
const utils = renderWithTheme(
<Dropdown {...props} portalNode={wrapper} trigger={<div>Trigger</div>}>
<Dropdown.Header>Header</Dropdown.Header>
<Dropdown.Body>
<Dropdown.Item data-testid="item-one">One</Dropdown.Item>
<Dropdown.Item data-testid="item-two">Two</Dropdown.Item>
</Dropdown.Body>
<Dropdown.Footer>Footer</Dropdown.Footer>
<Dropdown.Title>Title</Dropdown.Title>
<Dropdown.Item data-testid="item-one">One</Dropdown.Item>
<Dropdown.Divider data-testid="divider" />
<Dropdown.Item data-testid="item-two">Two</Dropdown.Item>
</Dropdown>,
{
container: document.body.appendChild(wrapper),
Expand All @@ -44,12 +42,12 @@ describe('<Dropdown />', () => {

const assertDropdownOpen = (utils = renderUtils) =>
wait(() => {
expect(utils.queryByText('Header')).toBeInTheDocument();
expect(utils.queryByText('Title')).toBeInTheDocument();
});

const assertDropdownClosed = (utils = renderUtils) =>
wait(() => {
expect(utils.queryByText('Header')).not.toBeInTheDocument();
expect(utils.queryByText('Title')).not.toBeInTheDocument();
});

const openDropdown = async (utils = renderUtils) => {
Expand Down
164 changes: 72 additions & 92 deletions src/Dropdown/Dropdown.stories.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useState } from 'react';
import Dropdown, { PLACEMENT_TRANSITION_ORIGINS } from './Dropdown';
import Icon from '../Icon';
import Flex from '../Flex';
import RadioGroup from '../Form/RadioGroup';
import Button from '../Button';
Expand All @@ -11,96 +10,77 @@ export default {
};

export const Basic = () => {
function Example() {
const [placement, setPlacement] = useState('top');
return (
<>
<Flex justifyContent="space-between">
<RadioGroup
label={<strong>Placement</strong>}
value={placement}
choices={Object.keys(PLACEMENT_TRANSITION_ORIGINS).map(placement => ({
value: placement,
label: placement,
}))}
onChange={(_, val) => setPlacement(val)}
/>
<Flex mr={3}>
<Dropdown placement={placement} width={250} trigger={<Button variant="success">Open Dropdown</Button>}>
<Dropdown.Header title="Dropdown" />

<Dropdown.Body>
<Dropdown.SectionTitle>Section One</Dropdown.SectionTitle>
<Dropdown.Item closeOnClick={false}>I don't close when clicked</Dropdown.Item>
<Dropdown.Item as="button">Item Two</Dropdown.Item>

<Dropdown.SectionTitle>Section Two</Dropdown.SectionTitle>
<Dropdown.Item as="a" closeOnClick href="http://google.com" target="_blank">
Item One
</Dropdown.Item>
<Dropdown.Item disabled>Item Two</Dropdown.Item>
</Dropdown.Body>

<Dropdown.Footer>Footer</Dropdown.Footer>
</Dropdown>
</Flex>

<Flex mr={3}>
<Dropdown width={250} trigger={<Button>Open Other Dropdown</Button>}>
<Dropdown.Header title="Dropdown" />

<Dropdown.Body>
<Dropdown.SectionTitle>Section One</Dropdown.SectionTitle>
<Dropdown.Item>Item One</Dropdown.Item>
<Dropdown.Item>Item Two</Dropdown.Item>

<Dropdown.SectionTitle>Section Two</Dropdown.SectionTitle>
<Dropdown.Item>Item One</Dropdown.Item>
<Dropdown.Item disabled>Item Two</Dropdown.Item>
</Dropdown.Body>

<Dropdown.Footer>Footer</Dropdown.Footer>
</Dropdown>
</Flex>

<Flex mr={3}>
<Dropdown width={250} placement="top" trigger={<Icon name="information-outline" />}>
<Dropdown.Header title="Dropdown" />

<Dropdown.Body>
<Dropdown.SectionTitle>Section One</Dropdown.SectionTitle>
<Dropdown.Item>Item One</Dropdown.Item>
<Dropdown.Item>Item Two</Dropdown.Item>
</Dropdown.Body>

<Dropdown.Footer>Footer</Dropdown.Footer>
</Dropdown>
</Flex>

<Flex flex={1}>
<Dropdown
width={250}
placement="top"
styles={{ Trigger: { display: 'flex', flex: 1 } }}
trigger={
<Flex flex={1} justifyContent="space-between">
<Button style={{ width: `100%` }}>Open Flex Dropdown</Button>
</Flex>
}>
<Dropdown.Header title="Dropdown" />
const [ placement, setPlacement ] = useState('bottom-start')
return (
<Flex justifyContent="center">
<Flex mr={5}>
<RadioGroup
label={<strong>Placement</strong>}
value={placement}
choices={Object.keys(PLACEMENT_TRANSITION_ORIGINS).map(placement => ({
value: placement,
label: placement,
}))}
onChange={(_, val) => setPlacement(val)}
/>
</Flex>
<Flex alignSelf="center">
<Dropdown
placement={placement}
width={250}
trigger={
<Button mr={3} variant="primary">
Basic Dropdown
</Button>
}>
<Dropdown.Item>Dropdown Item</Dropdown.Item>
<Dropdown.Item>Dropdown Item</Dropdown.Item>
<Dropdown.Item>Dropdown Item</Dropdown.Item>
<Dropdown.Item>Dropdown Item</Dropdown.Item>
<Dropdown.Item color="red">Cancel</Dropdown.Item>
</Dropdown>
</Flex>
</Flex>
);
};

<Dropdown.Body>
<Dropdown.SectionTitle>Section One</Dropdown.SectionTitle>
<Dropdown.Item>Item One</Dropdown.Item>
<Dropdown.Item>Item Two</Dropdown.Item>
</Dropdown.Body>
export const WithTitles = () => (
<Flex>
<Dropdown placement="bottom-start" width={250} trigger={<Button variant="danger">Dropdown w/Titles</Button>}>
<Dropdown.Title>Section Title</Dropdown.Title>
<Dropdown.Item selected closeOnClick={false}>Dropdown Item</Dropdown.Item>
<Dropdown.Item>Dropdown Item</Dropdown.Item>
<Dropdown.Item>Dropdown Item</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Title>Section Title</Dropdown.Title>
<Dropdown.Item>Dropdown Item</Dropdown.Item>
<Dropdown.Item>Dropdown Item</Dropdown.Item>
<Dropdown.Item>Dropdown Item</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item color="red">Cancel</Dropdown.Item>
</Dropdown>
</Flex>
);

<Dropdown.Footer>Footer</Dropdown.Footer>
</Dropdown>
</Flex>
</Flex>
</>
);
}
return <Example />;
};
export const WithIcons = () => (
<Flex>
<Dropdown
placement="bottom-start"
width={250}
trigger={
<Button mr={3} variant="success">
Dropdown w/Icons
</Button>
}>
<Dropdown.Item icon="account-circle">Dropdown Item</Dropdown.Item>
<Dropdown.Item icon="pencil">Dropdown Item</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item icon="stethoscope">Dropdown Item</Dropdown.Item>
<Dropdown.Item icon="bell">Dropdown Item</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item icon="settings">Dropdown Item</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item icon="trash-can" color="red">Cancel</Dropdown.Item>
</Dropdown>
</Flex>
);
Loading

0 comments on commit ca36b19

Please sign in to comment.