From b0efbb275ee157049171d9c56f391ad7bd59f4b2 Mon Sep 17 00:00:00 2001 From: Nathaniel Waldschmidt Date: Wed, 14 Feb 2024 09:04:41 -0600 Subject: [PATCH] feat: add input number --- CHANGELOG.md | 5 + package-lock.json | 4 +- package.json | 2 +- .../CurrencyInput/CurrencyInput.mdx | 4 +- src/components/InputNumber/InputNumber.mdx | 16 ++ .../InputNumber/InputNumber.stories.js | 50 +++++ src/components/InputNumber/InputNumber.vue | 191 ++++++++++++++++++ .../InputNumber/__tests__/InputNumber.spec.ts | 57 ++++++ src/components/InputNumber/constants.ts | 6 + src/components/index.js | 1 + 10 files changed, 332 insertions(+), 4 deletions(-) create mode 100644 src/components/InputNumber/InputNumber.mdx create mode 100644 src/components/InputNumber/InputNumber.stories.js create mode 100644 src/components/InputNumber/InputNumber.vue create mode 100644 src/components/InputNumber/__tests__/InputNumber.spec.ts create mode 100644 src/components/InputNumber/constants.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 75f861f55..28eeff506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG +## v2.0.12 + +- Deprecate the `CurrencyInput` component +- Add the `InputNumber` component + ## v2.0.11 - Update linting to understand Vue globals diff --git a/package-lock.json b/package-lock.json index 69d6e3abb..6570d438a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@lob/ui-components", - "version": "2.0.11", + "version": "2.0.12", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@lob/ui-components", - "version": "2.0.11", + "version": "2.0.12", "dependencies": { "date-fns": "^2.29.3", "date-fns-holiday-us": "^0.3.1", diff --git a/package.json b/package.json index 970a54d0d..09fd01440 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lob/ui-components", - "version": "2.0.11", + "version": "2.0.12", "engines": { "node": ">=20.2.0", "npm": ">=10.2.0" diff --git a/src/components/CurrencyInput/CurrencyInput.mdx b/src/components/CurrencyInput/CurrencyInput.mdx index 1289c5596..9102c8461 100644 --- a/src/components/CurrencyInput/CurrencyInput.mdx +++ b/src/components/CurrencyInput/CurrencyInput.mdx @@ -1,6 +1,8 @@ -import { Canvas, Story, ArgTypes, PRIMARY_STORY } from '@storybook/addon-docs'; +import { Canvas, ArgTypes, PRIMARY_STORY } from '@storybook/addon-docs'; import { Primary } from './CurrencyInput.stories'; +**NOTE:** This component is deprecated in favor of `InputNumber`. + # Currency Input A currency input component for billing form workflows. diff --git a/src/components/InputNumber/InputNumber.mdx b/src/components/InputNumber/InputNumber.mdx new file mode 100644 index 000000000..831a529cc --- /dev/null +++ b/src/components/InputNumber/InputNumber.mdx @@ -0,0 +1,16 @@ +import { Canvas, ArgTypes, PRIMARY_STORY } from '@storybook/addon-docs'; +import { Primary, Currency } from './InputNumber.stories'; + +# Input Number + +## Primary + + + +## Currency + + + +## Props + + diff --git a/src/components/InputNumber/InputNumber.stories.js b/src/components/InputNumber/InputNumber.stories.js new file mode 100644 index 000000000..8c3833aad --- /dev/null +++ b/src/components/InputNumber/InputNumber.stories.js @@ -0,0 +1,50 @@ +import InputNumber from './InputNumber.vue'; +import mdx from './InputNumber.mdx'; + +export default { + title: 'Components/Input Number', + component: InputNumber, + parameters: { + docs: { + page: mdx + } + } +}; + +const inputNumberModel = 5000; + +const PrimaryTemplate = (args, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { InputNumber }, + setup: () => ({ args }), + data: () => ({ inputNumberModel }), + template: '' +}); + +export const Primary = PrimaryTemplate.bind({}); +Primary.args = { + id: 'input-number', + name: 'input-number', + label: 'Input Number', + helperText: 'Helper text', + placeholder: 'Amount' +}; + +const inputCurrencyModel = 5000; + +const CurrencyTemplate = (args, { argTypes }) => ({ + props: Object.keys(argTypes), + components: { InputNumber }, + setup: () => ({ args }), + data: () => ({ inputCurrencyModel }), + template: '' +}); + +export const Currency = CurrencyTemplate.bind({}); +Currency.args = { + id: 'input-currency', + name: 'input-currency', + label: 'Input Currency', + placeholder: 'Amount', + mode: 'currency' +}; diff --git a/src/components/InputNumber/InputNumber.vue b/src/components/InputNumber/InputNumber.vue new file mode 100644 index 000000000..dac2a37fe --- /dev/null +++ b/src/components/InputNumber/InputNumber.vue @@ -0,0 +1,191 @@ + + + + + + diff --git a/src/components/InputNumber/__tests__/InputNumber.spec.ts b/src/components/InputNumber/__tests__/InputNumber.spec.ts new file mode 100644 index 000000000..318ce95b0 --- /dev/null +++ b/src/components/InputNumber/__tests__/InputNumber.spec.ts @@ -0,0 +1,57 @@ +import '@testing-library/jest-dom'; +import { fireEvent, render } from '@testing-library/vue'; +import InputNumber from '../InputNumber.vue'; +import { ExtractPropTypes } from 'vue'; + +describe('InputNumber', () => { + const DEFAULT_PROPS: ExtractPropTypes = { + label: 'Test Input Number', + id: 'test-input-number', + name: 'test', + 'data-testid': 'uic-input-number', + helperText: 'Test helper text' + }; + + it('renders', () => { + const { getByTestId } = render(InputNumber, { props: DEFAULT_PROPS }); + expect(getByTestId('uic-input-number-container')).toBeVisible(); + const label = getByTestId('uic-input-number-label'); + expect(label).toBeVisible(); + expect(label.textContent).toContain(DEFAULT_PROPS.label); + expect(getByTestId('uic-input-number')).toBeVisible(); + const helperText = getByTestId('uic-input-number-helper'); + expect(helperText).toBeVisible(); + expect(helperText.textContent).toContain(DEFAULT_PROPS.helperText); + }); + + it('updates', () => { + const { getByTestId } = render(InputNumber, { + props: { ...DEFAULT_PROPS, modelValue: 50 } + }); + const numberInput = getByTestId('uic-input-number'); + + expect(numberInput).toHaveValue('50'); + fireEvent.update(numberInput, '123'); + expect(numberInput).toHaveValue('123'); + }); + + it('emits focus', () => { + const { getByTestId, emitted } = render(InputNumber, { + props: DEFAULT_PROPS + }); + const numberInput = getByTestId('uic-input-number'); + + fireEvent.focus(numberInput); + expect(emitted()).toHaveProperty('focus'); + }); + + it('emits blur', () => { + const { getByTestId, emitted } = render(InputNumber, { + props: DEFAULT_PROPS + }); + const numberInput = getByTestId('uic-input-number'); + + fireEvent.blur(numberInput); + expect(emitted()).toHaveProperty('blur'); + }); +}); diff --git a/src/components/InputNumber/constants.ts b/src/components/InputNumber/constants.ts new file mode 100644 index 000000000..2591568ab --- /dev/null +++ b/src/components/InputNumber/constants.ts @@ -0,0 +1,6 @@ +export const InputNumberMode = { + CURRENCY: 'currency', + DECIMAL: 'decimal' +} as const; +export type InputNumberMode = + (typeof InputNumberMode)[keyof typeof InputNumberMode]; diff --git a/src/components/index.js b/src/components/index.js index b3e7e652e..160a2ae85 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -13,6 +13,7 @@ export { default as FileUpload } from './FileUpload/FileUpload'; export { default as FilterContent } from './FilterContent/FilterContent'; export { default as Icon } from './Icon/Icon.vue'; export * from './Icons'; +export { default as InputNumber } from './InputNumber/InputNumber.vue'; export { default as LegacyModal } from './LegacyModal/LegacyModal.vue'; export { default as LoadingIndicator } from './LoadingIndicator/LoadingIndicator'; export { default as LobLink } from './Link/Link';