Skip to content

Commit

Permalink
Add Pill, DeletablePill, EasyPill (#81)
Browse files Browse the repository at this point in the history
Bumped to alpha 12 

* Add Pill, DeletablePill, EasyPill
* Rename clear to times-circled, limited onDelete click to the icon
  • Loading branch information
pixelbandito authored Aug 1, 2019
1 parent defd0b6 commit f53fbf9
Show file tree
Hide file tree
Showing 27 changed files with 795 additions and 28 deletions.
51 changes: 50 additions & 1 deletion example/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import {
Responsive,
SideTray,
EasyTabMenu,
Pill,
DeletablePill,
EasyPill,
// IMPORT_INJECTOR
} from '@cision/rover-ui';

const App = () => {
Expand Down Expand Up @@ -140,7 +144,52 @@ const App = () => {
</section>
<section>
<h1>Icon</h1>
<Icon name="clear" />
<Icon name="times-circle" />
</section>
<section>
<h1>Pill</h1>
<Pill onClick={() => {}} checked>
Pill with addon
<Pill.Addon onClick={() => {}}></Pill.Addon>
</Pill>
</section>
<section>
<h1>DeletablePill</h1>
<DeletablePill onDelete={() => {}} checked>
DeletablePill
</DeletablePill>
</section>
<section>
<h1>EasyPill</h1>
<EasyPill
actions={[
{
label: '✓',
onClick: () => {},
},
{
label: '✰',
onClick: () => {},
},
]}
>
EasyPill
</EasyPill>{' '}
<EasyPill
actions={[
{
label: '✓',
onClick: () => {},
},
{
label: '✰',
onClick: () => {},
},
]}
checked
>
EasyPill checked
</EasyPill>
</section>
{/** USAGE_INJECTOR */}
</div>
Expand Down
5 changes: 5 additions & 0 deletions src/components/DeletablePill/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# DeletablePill

When checked, the pill has a small delete icon (⊗). Clicking it fires an `onDelete` function.

When unchecked, it behaves just like a regular pill.
37 changes: 37 additions & 0 deletions src/components/DeletablePill/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import PropTypes from 'prop-types';

import Icon from '../Icon';
import Pill from '../Pill';

export const DeletablePill = ({ children, onDelete, ...passedProps }) => {
return (
<Pill {...passedProps}>
{children}
{passedProps.checked && (
<Pill.Addon
onClick={e => {
e.stopPropagation();
onDelete(e);
}}
role="button"
tabIndex={0}
>
<Icon name="times-circle" style={{ display: 'block' }} />
</Pill.Addon>
)}
</Pill>
);
};

DeletablePill.propTypes = {
checked: PropTypes.bool,
children: PropTypes.node.isRequired,
onDelete: PropTypes.func.isRequired,
};

DeletablePill.defaultProps = {
checked: false,
};

export default DeletablePill;
32 changes: 32 additions & 0 deletions src/components/DeletablePill/story.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { boolean } from '@storybook/addon-knobs';

import DeletablePill from './';
import Readme from './README.md';

storiesOf('Star Systems/DeletablePill', module)
.addParameters({
readme: {
sidebar: Readme,
},
})
.add(
'Overview',
() => (
<DeletablePill
checked={boolean('checked', false)}
onClick={action('onClick')}
onDelete={action('onDelete')}
>
DeletablePill 1
</DeletablePill>
),
{
info: {
inline: true,
source: true,
},
}
);
48 changes: 48 additions & 0 deletions src/components/DeletablePill/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react';
import { mount, shallow } from 'enzyme';

import Icon from '../Icon';
import DeletablePill from './';

describe('DeletablePill', () => {
it('renders', () => {
shallow(<DeletablePill onDelete={() => {}}>DeletablePill</DeletablePill>);
});

describe('with children', () => {
it('renders its children', () => {
const wrapper = mount(
<DeletablePill onDelete={() => {}}>DeletablePill 1</DeletablePill>
);

expect(wrapper.children()).toHaveLength(1);
expect(wrapper.text()).toEqual('DeletablePill 1');
});
});

describe('when unchecked', () => {
it("doesn't show clear action", () => {
const wrapper = mount(
<DeletablePill onDelete={() => {}}>DeletablePill 1</DeletablePill>
);

const clear = wrapper.find(Icon);

expect(clear).toHaveLength(0);
});
});

describe('when checked', () => {
it('shows clear action', () => {
const wrapper = mount(
<DeletablePill checked onDelete={() => {}}>
DeletablePill 1
</DeletablePill>
);

const clear = wrapper.find(Icon);

expect(clear).toHaveLength(1);
});
});
});
12 changes: 12 additions & 0 deletions src/components/EasyPill/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# EasyPill

Easy pills are more opinionated than basic pills, to make it easy to get started on the most common use cases.
They take an `actions` prop that's an array of objects.
Each object has, at least, a callback function (`onClick`) and a text label (`label`).

Easy pills show those action labels in a list. Each one fires its callback function when clicked.
Easy pill actions may also have `children` props, in which case they'll render the `children` as a React node instead of the label. You still need to provide a unique text label, though, for use as an identifying key.

Easy pills come with a delete action by default. If you provide an `onDelete` function as a prop, it will show up automatically.

**TODO**: The easy pill's actions should be in a small dropdown with an ellipsis icon that triggers it.
57 changes: 57 additions & 0 deletions src/components/EasyPill/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import PropTypes from 'prop-types';

import Icon from '../Icon';
import Pill from '../Pill';

export const EasyPill = ({ actions, children, onDelete, ...passedProps }) => {
return (
<Pill {...passedProps}>
{children}
{passedProps.checked &&
!!actions.length &&
actions.map(action => (
<Pill.Addon
key={action.label}
onClick={e => {
e.stopPropagation();
action.onClick(e);
}}
>
{action.children || action.label}
</Pill.Addon>
))}
{passedProps.checked && onDelete && (
<Pill.Addon
onClick={e => {
e.stopPropagation();
onDelete(e);
}}
>
<Icon name="times-circle" />
</Pill.Addon>
)}
</Pill>
);
};

EasyPill.propTypes = {
actions: PropTypes.arrayOf(
PropTypes.shape({
children: PropTypes.string,
label: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
})
),
checked: PropTypes.bool,
children: PropTypes.node.isRequired,
onDelete: PropTypes.func,
};

EasyPill.defaultProps = {
actions: [],
checked: false,
onDelete: null,
};

export default EasyPill;
42 changes: 42 additions & 0 deletions src/components/EasyPill/story.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { boolean } from '@storybook/addon-knobs';

import EasyPill from './';
import Readme from './README.md';

storiesOf('Star Systems/EasyPill', module)
.addParameters({
readme: {
sidebar: Readme,
},
})
.add(
'Overview',
() => (
<EasyPill
actions={[
{
label: 'Boom',
onClick: action('Boom'),
},
{
label: 'Bang',
onClick: action('Bang'),
},
]}
checked={boolean('checked', false)}
onClick={action('onClick')}
onDelete={action('onDelete')}
>
EasyPill 1
</EasyPill>
),
{
info: {
inline: true,
source: true,
},
}
);
81 changes: 81 additions & 0 deletions src/components/EasyPill/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from 'react';
import { mount, shallow } from 'enzyme';

import Pill from '../Pill';
import EasyPill from './';

describe('EasyPill', () => {
it('renders', () => {
shallow(<EasyPill onDelete={() => {}}>EasyPill</EasyPill>);
});

describe('with children', () => {
it('renders its children', () => {
const wrapper = mount(
<EasyPill onDelete={() => {}}>EasyPill 1</EasyPill>
);

expect(wrapper.children()).toHaveLength(1);
expect(wrapper.text()).toEqual('EasyPill 1');
});
});

describe('with props.actions', () => {
describe('when unchecked', () => {
it("doesn't render actions", () => {
const wrapper = mount(
<EasyPill
actions={[
{
label: 'Boom',
onClick: () => {},
},
{
label: 'Bang',
onClick: () => {},
},
]}
onClick={() => {}}
>
EasyPill 1
</EasyPill>
);

// Should use css modules selector, but our version of react-scripts doesn't support it.
const addons = wrapper.find(Pill.Addon);

expect(addons.children()).toHaveLength(0);
});
});

describe('when checked', () => {
it('does render actions', () => {
const wrapper = mount(
<EasyPill
actions={[
{
label: 'Boom',
onClick: () => {},
},
{
label: 'Bang',
onClick: () => {},
},
]}
checked
onClick={() => {}}
>
EasyPill 1
</EasyPill>
);

// Should use css modules selector, but our version of react-scripts doesn't support it.
const addons = wrapper.find(Pill.Addon);

expect(addons).toHaveLength(2);
expect(addons.at(0).text()).toEqual('Boom');
expect(addons.at(1).text()).toEqual('Bang');
});
});
});
});
Loading

0 comments on commit f53fbf9

Please sign in to comment.