Skip to content

Commit

Permalink
Merge pull request #649 from Orfium/feat/NDS-705_number_field_v5
Browse files Browse the repository at this point in the history
[NDS-705] NumberField [v5]
  • Loading branch information
kostasdano authored Jul 27, 2023
2 parents bb95a04 + a45393c commit be1d40c
Show file tree
Hide file tree
Showing 14 changed files with 4,658 additions and 51 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@
"lodash": "^4.17.19",
"pluralize": "^8.0.0",
"polished": "^3.4.4",
"react-aria-components": "^1.0.0-alpha.5",
"react-fast-compare": "^3.2.0",
"react-highlight-words": "^0.17.0",
"react-input-mask": "^2.0.4",
Expand Down
172 changes: 172 additions & 0 deletions src/components/NumberField/NumberField.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import { Meta, Preview, Props, Story } from '@storybook/addon-docs';
import { boolean, number, select, text, withKnobs } from '@storybook/addon-knobs';
import { linkTo } from '@storybook/addon-links';
import NumberField from './NumberField';
import Stack from '../storyUtils/Stack';
import Icon from '../Icon';
import iconSelector from '../Icon/assets/iconSelector';
import Loader from '../Loader';
import { FIGMA_URL } from '../../utils/common';
import SectionHeader from '../../storybook/SectionHeader';

<Meta
title="Design System/NumberField"
component={NumberField}
parameters={{
design: [
{
type: 'figma',
url: `${FIGMA_URL}?node-id=10019%3A116505`,
},
],
}}
/>

<SectionHeader title={'NumberField'} />

- [Overview](#overview)
- [Props](#props)
- [Usage](#usage)
- [Variants](#variants)

## Overview

A universal NumberField that only allows for numerical characters, designed to handle numerical data entry.

## Props

<Props of={NumberField} />

## Usage

<UsageGuidelines
guidelines={[
'Number input may be an absolute number or decimals',
'Number field may feature suffix (e.g. “%”) or increments',
'It also supports input masking to enforce a specific format for the numeric input',
'If the user is required to enter non-numeric data, such as text or dates use TextField or DatePicker instead.',
]}
/>

## Variants

### NumberField with number format

Formatting options for the value displayed in the number field can be configured based on
[Intl.NumberFormatOptions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat)

<Preview>
<Story name="NumberField with number format">
<Stack>
<NumberField
label={'Label'}
formatOptions={{
minimumFractionDigits: number('minimumFractionDigits', 2),
maximumFractionDigits: number('maximumFractionDigits', 2),
}}
/>
</Stack>
</Story>
</Preview>

### NumberField with suffix or Stepper

NumberField can either have custom suffix (as in TextField) or a stepper with increment and decrement buttons. Step defaults to 1 if not provided.

<Preview>
<Story name="NumberField with suffix or Stepper">
<Stack>
<NumberField label={'Label'} suffix={<div style={{ color: 'gray' }}>%</div>} />
<NumberField label={'Label'} hasStepper step={number('Step', undefined)} />
</Stack>
</Story>
</Preview>

## NumberField with min and max values

NumberField can have min and max values provided as props.

<Preview>
<Story name="NumberField with min and max values">
<Stack>
<NumberField
label={'Label'}
minValue={number('minValue', undefined)}
maxValue={number('maxValue', undefined)}
suffix={<div style={{ color: 'gray' }}>%</div>}
/>
</Stack>
</Story>
</Preview>

### NumberField with Label and Placeholder

NumberField with label options. Label that will float, with or without Placeholder

<Preview>
<Story name="NumberField with Label and Placeholder">
<Stack>
<NumberField label={'Label'} />
<NumberField label={'Label'} placeholder={'Placeholder'} />
</Stack>
</Story>
</Preview>

### NumberField with Statuses

NumberField inherits all TextField statuses behavior.

<Preview>
<Story name="NumberField with Label, Placeholder and Statuses">
<Stack>
<NumberField
label={'Label'}
status={{
type: 'normal',
hintMessage: text('Custom Hint Message', 'Hint in Text Field'),
}}
/>
<NumberField
label={'Label'}
status={{
type: 'error',
hintMessage: text('Custom Error Message', 'Error in Text Field'),
}}
/>
<NumberField
label={'Label'}
status={{
type: 'read-only',
}}
/>
<NumberField label={'Label'} isDisabled />
</Stack>
</Story>
</Preview>

### Playground

<Preview>
<Story name="Playground" parameters={{ decorators: [withKnobs] }}>
<Stack>
<NumberField
label={text('Label', 'Label')}
isDisabled={boolean('isDisabled', false)}
isRequired={boolean('isRequired', false)}
hasStepper={boolean('hasStepper', false)}
minValue={number('minValue', undefined)}
maxValue={number('maxValue', undefined)}
step={number('step', undefined)}
formatOptions={{
minimumFractionDigits: number('minimumFractionDigits', 2),
maximumFractionDigits: number('maximumFractionDigits', 2),
}}
suffix={select('Suffix icon name', ['', ...Object.keys(iconSelector)], 'info')}
status={{
type: select('Status', ['error', 'normal', 'read-only'], 'normal'),
hintMessage: text('Hint/Error message', 'Message in Number Field'),
}}
/>
</Stack>
</Story>
</Preview>
9 changes: 9 additions & 0 deletions src/components/NumberField/NumberField.style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { css, SerializedStyles } from '@emotion/react';
import { rem } from 'theme/utils';

export const groupStyles = (): SerializedStyles => {
return css`
/** @TODO: add tokens instead of rem */
width: ${`calc(100% - ${rem(44)})`};
`;
};
82 changes: 82 additions & 0 deletions src/components/NumberField/NumberField.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import userEvent from '@testing-library/user-event';
import React from 'react';
import NumberField, { NumberFieldProps as Props } from './NumberField';
import { render, screen } from '../../test';

const renderNumberField = (props: Partial<Props> = {}) => {
const defaultProps = {
label: 'Label',
formatOptions: { minimumFractionDigits: 2, maximumFractionDigits: 2 },
};

return render(<NumberField {...{ ...defaultProps, ...props }} />);
};

describe('NumberField', () => {
let input: HTMLInputElement;
let increase: HTMLButtonElement;
let decrease: HTMLButtonElement;

it('formats the number input based on the given format', async () => {
renderNumberField();
input = screen.getByTestId('input') as HTMLInputElement;

userEvent.type(input, '12.3456');
userEvent.click(document.body);
expect(input).toHaveValue('12.35');
});

it('it rounds number input based on step', async () => {
renderNumberField({ step: 0.5 });
input = screen.getByTestId('input') as HTMLInputElement;

userEvent.type(input, '12.21');
userEvent.click(document.body);
expect(input).toHaveValue('12.00');
});

it('increases and decreases the number through stepper', async () => {
renderNumberField({ step: 0.5, hasStepper: true });
input = screen.getByTestId('input') as HTMLInputElement;
increase = screen.getByTestId('number_increment') as HTMLButtonElement;
decrease = screen.getByTestId('number_decrement') as HTMLButtonElement;

userEvent.type(input, '12.00');
userEvent.click(document.body);
userEvent.click(increase);
expect(input).toHaveValue('12.50');
userEvent.click(decrease);
expect(input).toHaveValue('12.00');
});

it('increases and decreases the number through keyboard', async () => {
renderNumberField({ step: 0.5, hasStepper: true });
input = screen.getByTestId('input') as HTMLInputElement;

userEvent.type(input, '12.00');
userEvent.click(document.body);

userEvent.click(input);

userEvent.type(input, '{arrowup}');
expect(input).toHaveValue('12.50');

userEvent.type(input, '{arrowdown}');
expect(input).toHaveValue('12.00');
});

it('it rejects number input outside min and max given', async () => {
renderNumberField({ minValue: 10, maxValue: 20 });
input = screen.getByTestId('input') as HTMLInputElement;

userEvent.type(input, '5');
userEvent.click(document.body);
expect(input).toHaveValue('10.00');

userEvent.clear(input);

userEvent.type(input, '25');
userEvent.click(document.body);
expect(input).toHaveValue('20.00');
});
});
Loading

0 comments on commit be1d40c

Please sign in to comment.