Skip to content

Commit

Permalink
feat: add percent column component using intl formatter (#622)
Browse files Browse the repository at this point in the history
* feat: add percent column component using intl formatter

* fix: change title to container

* fix: useLocale import from react-rainbow-component

* feat: add cellAlignment prop

Co-authored-by: Jose Leandro Torres <jtorressicilia@gmail.com>
  • Loading branch information
yvmunayev and LeandroTorresSicilia authored Nov 10, 2022
1 parent 1bdec4f commit e5bfe3a
Show file tree
Hide file tree
Showing 11 changed files with 284 additions and 5 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
"react-dom": "^17.0.0",
"react-intl": "^4.4.0",
"react-query": "^3.34.0",
"react-rainbow-components": "1.31.0-canary.082c6c0",
"react-rainbow-components": "1.31.0-canary.6ccebb7",
"react-redux": "^7.2.0",
"react-router-dom": "^5.0.0",
"redux": "^4.0.5",
Expand Down
19 changes: 19 additions & 0 deletions packages/listview/docs/components/PercentColumn.story.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks';
import PercentColumn from '../../src/components/PercentColumn';
import * as stories from '../stories/PercentColumn.story.js';

<Meta title="Modules/Listview/Components/PercentColumn" component={PercentColumn} />

# Overview

`PercentColumn` is a column that renders the percent number passed as value.

### This is an example with the PercentColumn.

<Preview>
<Story name="PercentColumn">{stories.percentColumn()}</Story>
</Preview>

# Component props

<Props of={PercentColumn} />
125 changes: 125 additions & 0 deletions packages/listview/docs/stories/PercentColumn.story.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import React from 'react';
import styled from 'styled-components';
import { Table, Column, Application } from 'react-rainbow-components';
import PercentColumn from '../../src/components/PercentColumn';
import ColoredStatusColumn from '../../src/components/ColoredStatusColumn';

const initialData = [
{
name: 'Carls Smith',
status: 'canceled',
company: 'Google',
percent: 0.1,
createdAt: '09/06/2020 09:00 AM',
},
{
name: 'John Snow',
status: 'delivered',
company: 'Google',
percent: 0.0025,
createdAt: '09/06/2020 09:00 AM',
},
{
name: 'Anna Adams',
status: 'pending',
company: 'Google',
percent: 0.3045,
createdAt: '09/06/2020 09:00 AM',
},
{
name: 'William Adams',
status: 'arrived',
company: 'Google',
percent: 0,
createdAt: '09/06/2020 09:00 AM',
},
{
name: 'Joe Smith',
status: 'arrived',
company: 'Google',
percent: 0.8,
createdAt: '09/06/2020 09:00 AM',
},
{
name: 'John Doe',
status: 'arrived',
company: 'Google',
percent: 1,
createdAt: '09/06/2020 09:00 AM',
},
{
name: 'Jane Adams',
status: 'arrived',
company: 'Google',
percent: 1.2,
createdAt: '09/06/2020 09:00 AM',
},
];

const Container = styled.div`
padding: 2rem;
`;

const colors = {
canceled: { backgroundColor: '#f2707a', color: 'rgba(255, 255, 255)' },
delivered: '#009900',
pending: { backgroundColor: '#EBC665', color: '#fff' },
arrived: { backgroundColor: '#4dc9cb', color: '#fff' },
};

export const percentColumn = () => {
return (
<Application>
<Container>
<Table data={initialData} keyField="id" variant="listview" showCheckboxColumn>
<Column header="Created At" field="createdAt" />
<Column header="Name" field="name" />
<Column header="Company" field="company" />
<Column header="Percent" field="percent" component={PercentColumn} />
<Column
header="Status"
field="status"
colors={colors}
component={ColoredStatusColumn}
/>
</Table>
</Container>
</Application>
);
};

export const percentWithIntlOptionColumn = () => {
return (
<Application>
<Container>
<Table data={initialData} keyField="id" variant="listview" showCheckboxColumn>
<Column header="Created At" field="createdAt" />
<Column header="Name" field="name" />
<Column header="Company" field="company" />
<Column
header="Percent"
field="percent"
component={PercentColumn}
minimumFractionDigits={1}
maximumFractionDigits={2}
/>
<Column
header="Status"
field="status"
colors={colors}
component={ColoredStatusColumn}
/>
</Table>
</Container>
</Application>
);
};

export default {
title: 'Modules/Listview/Stories/PercentColumn',
parameters: {
viewOnGithub: {
fileName: __filename,
},
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { mount } from 'enzyme';
import PercentColumn from '../index';
import { StyledCellContainer } from '../styled';

describe('<PercentColumn />', () => {
it('should render a PercentColumn with the value passed', () => {
const wrapper = mount(<PercentColumn value={1} />);
const output = wrapper.find(StyledCellContainer);
expect(output.exists()).toBe(true);
expect(output.text()).toBe('100%');
});

it('should render a PercentColumn with the value passed and intl options', () => {
const wrapper = mount(
<PercentColumn value={0.5025} minimumFractionDigits={1} maximumFractionDigits={2} />,
);
const output = wrapper.find(StyledCellContainer);
expect(output.exists()).toBe(true);
expect(output.text()).toBe('50.25%');
});

it('should render a center text whne cellAlignment is center', () => {
const wrapper = mount(<PercentColumn value={1} cellAlignment="center" />);
const output = wrapper.find(StyledCellContainer);
expect(output.exists()).toBe(true);
expect(output.prop('cellAlignment')).toBe('center');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
interface Options {
minimumIntegerDigits?: number;
minimumFractionDigits?: number;
maximumFractionDigits?: number;
minimumSignificantDigits?: number;
maximumSignificantDigits?: number;
}

const formatPercent = (value: number, locale: string, options: Options): string => {
return new Intl.NumberFormat(locale, {
style: 'percent',
...options,
}).format(value);
};

export default formatPercent;
65 changes: 65 additions & 0 deletions packages/listview/src/components/PercentColumn/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useLocale } from 'react-rainbow-components';
import formatPercent from './helpers/formatPercent';
import { StyledCellContainer } from './styled';
import { PercentColumnProps } from './types';

const PercentColumn: React.FC<PercentColumnProps> = (props: PercentColumnProps) => {
const { value, locale: localeProp, className, style, cellAlignment, ...rest } = props;
const locale = useLocale(localeProp);
const content = formatPercent(value ?? 0, locale, rest);

return (
<StyledCellContainer
className={className}
style={style}
title={content}
cellAlignment={cellAlignment}
>
{content}
</StyledCellContainer>
);
};

PercentColumn.propTypes = {
/** A number that comes from the data and is displayed in the table cell */
value: PropTypes.number,
/** The PercentColumn locale. Defaults to browser's language. */
locale: PropTypes.string,
/** The minimum number of integer digits to use.
* A value with a smaller number of integer digits than this number will be left-padded with zeros (to the specified
* length) when formatted. Possible values are from 1 to 21; The default is 1. */
minimumIntegerDigits: PropTypes.number,
/** The minimum number of fraction digits to use. Possible values are from 0 to 20;
* the default for percent formatting is 0; */
minimumFractionDigits: PropTypes.number,
/** The maximum number of fraction digits to use. Possible values are from 0 to 20;
* the default for percent formatting is the larger of minimumFractionDigits and 0. */
maximumFractionDigits: PropTypes.number,
/** The minimum number of significant digits to use. Possible values are from 1 to 21; The default is 1. */
minimumSignificantDigits: PropTypes.number,
/** The maximum number of significant digits to use. Possible values are from 1 to 21; The default is 21. */
maximumSignificantDigits: PropTypes.number,
/** A CSS class for the outer element, in addition to the component's base classes. */
className: PropTypes.string,
/** An object with custom style applied to the outer element. */
style: PropTypes.object,
/** Determines the alignment of the text in each column cell. */
cellAlignment: PropTypes.oneOf(['left', 'right', 'center']),
};

PercentColumn.defaultProps = {
value: undefined,
locale: undefined,
minimumIntegerDigits: undefined,
minimumFractionDigits: undefined,
maximumFractionDigits: undefined,
minimumSignificantDigits: undefined,
maximumSignificantDigits: undefined,
className: undefined,
style: undefined,
cellAlignment: 'right',
};

export default PercentColumn;
9 changes: 9 additions & 0 deletions packages/listview/src/components/PercentColumn/styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/* eslint-disable import/prefer-default-export */
import styled from 'styled-components';

export const StyledCellContainer = styled.div<{ cellAlignment?: 'left' | 'right' | 'center' }>`
padding: 0 5px;
overflow: hidden;
text-overflow: ellipsis;
text-align: ${(props) => props.cellAlignment};
`;
14 changes: 14 additions & 0 deletions packages/listview/src/components/PercentColumn/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { CSSProperties } from 'react';

export interface PercentColumnProps {
value?: number;
locale?: string;
minimumIntegerDigits?: number;
minimumFractionDigits?: number;
maximumFractionDigits?: number;
minimumSignificantDigits?: number;
maximumSignificantDigits?: number;
className?: string;
style?: CSSProperties;
cellAlignment?: 'left' | 'right' | 'center';
}
1 change: 1 addition & 0 deletions packages/listview/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ export { default as filterByFields } from './helpers/filterByFields';
export { default as MarkdownColumn } from './components/MarkdownColumn';
export { default as VirtualizedTable, useTableDataSource } from './components/VirtualizedTable';
export { default as ColumnHeaderFilterText } from './components/ColumnHeaderFilterText';
export { default as PercentColumn } from './components/PercentColumn';
1 change: 1 addition & 0 deletions packages/listview/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export { default as VirtualizedTable, useTableDataSource } from './components/Vi
export { default as ColumnHeaderFilterText } from './components/ColumnHeaderFilterText';
export { default as ColumnHeaderFilterMultiselect } from './components/ColumnHeaderFilterMultiselect';
export { default as DownloadModal } from './components/DownloadModal';
export { default as PercentColumn } from './components/PercentColumn';
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -18613,10 +18613,10 @@ react-query@^3.34.0:
broadcast-channel "^3.4.1"
match-sorter "^6.0.2"

react-rainbow-components@1.31.0-canary.082c6c0:
version "1.31.0-canary.082c6c0"
resolved "https://registry.yarnpkg.com/react-rainbow-components/-/react-rainbow-components-1.31.0-canary.082c6c0.tgz#cea930987fc567d9aa289264d63b722e34f861b9"
integrity sha512-2YGhvhjtalJZrBfVcYf+NkjJ9PT750Wo6eaZlNFjJ7d2/sQD2dD3cFvJlghHRAgSauI8wqjVZtDumodBGLHdYQ==
react-rainbow-components@1.31.0-canary.6ccebb7:
version "1.31.0-canary.6ccebb7"
resolved "https://registry.yarnpkg.com/react-rainbow-components/-/react-rainbow-components-1.31.0-canary.6ccebb7.tgz#ad85a9264f5d86864bb71e730496e8181d6dafca"
integrity sha512-L82R9j0G35p3oa1Ji66UZ55xfcHAaFTLOaghja7gIaQz6jeximwcY9AebI/s0CM+OuhdrZV2mc9mpBV4lWFGzw==
dependencies:
"@rainbow-modules/hooks" "^0.12.0"
"@rainbow-modules/validation" "^0.53.0"
Expand Down

0 comments on commit e5bfe3a

Please sign in to comment.