-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
858f7f6
commit b0efbb2
Showing
10 changed files
with
332 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { Canvas, ArgTypes, PRIMARY_STORY } from '@storybook/addon-docs'; | ||
import { Primary, Currency } from './InputNumber.stories'; | ||
|
||
# Input Number | ||
|
||
## Primary | ||
|
||
<Canvas of={Primary} /> | ||
|
||
## Currency | ||
|
||
<Canvas of={Currency} /> | ||
|
||
## Props | ||
|
||
<ArgTypes story={PRIMARY_STORY} /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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: '<InputNumber v-bind="args" v-model="inputNumberModel" />' | ||
}); | ||
|
||
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: '<InputNumber v-bind="args" v-model="inputCurrencyModel" />' | ||
}); | ||
|
||
export const Currency = CurrencyTemplate.bind({}); | ||
Currency.args = { | ||
id: 'input-currency', | ||
name: 'input-currency', | ||
label: 'Input Currency', | ||
placeholder: 'Amount', | ||
mode: 'currency' | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
<template> | ||
<div | ||
class="uic-input-number-container" | ||
data-testid="uic-input-number-container" | ||
> | ||
<Label | ||
:label-for="id" | ||
:label="label" | ||
:required="required" | ||
:sr-only-label="hideLabel" | ||
data-testid="uic-input-number-label" | ||
/> | ||
<InputNumber | ||
:currency="currency" | ||
:disabled="disabled" | ||
:input-class="[ | ||
'uic-input-number', | ||
{ 'uic-error': error }, | ||
{ 'uic-success': success } | ||
]" | ||
:input-id="id" | ||
:input-props="computedInputProps" | ||
:invalid="error" | ||
:max="max" | ||
:min-fraction-digits="minFractionDigits" | ||
:min="min" | ||
:mode="mode" | ||
:model-value="modelValue" | ||
:placeholder="placeholder" | ||
:readonly="readonly" | ||
locale="en-US" | ||
unstyled | ||
@blur="emits('blur')" | ||
@focus="emits('focus')" | ||
@input="(e) => emits('update:modelValue', e.value as number)" | ||
/> | ||
<!-- The PrimeVue number input does not have helper text, I copied this from the PrimeVue text input. --> | ||
<small | ||
v-if="helperText" | ||
:id="helperTextId" | ||
:class="[ | ||
'uic-input-number-helper', | ||
{ 'uic-error': error }, | ||
{ 'uic-success': success } | ||
]" | ||
data-testid="uic-input-number-helper" | ||
> | ||
{{ helperText }} | ||
</small> | ||
</div> | ||
</template> | ||
|
||
<script lang="ts"> | ||
export default { | ||
inheritAttrs: false | ||
}; | ||
</script> | ||
<script setup lang="ts"> | ||
import InputNumber, { InputNumberProps } from 'primevue/inputnumber'; | ||
import { InputHTMLAttributes, computed, useAttrs } from 'vue'; | ||
import Label from '../Label/Label.vue'; | ||
import { InputNumberMode } from './constants'; | ||
const attrs = useAttrs(); | ||
const props = withDefaults( | ||
defineProps<{ | ||
disabled?: InputNumberProps['disabled']; | ||
error?: boolean; | ||
helperText?: string; | ||
hideLabel?: boolean; | ||
id: InputNumberProps['inputId']; | ||
label: string; | ||
max?: InputNumberProps['max']; | ||
maxFractionDigits?: InputNumberProps['maxFractionDigits']; | ||
min?: InputNumberProps['min']; | ||
minFractionDigits?: InputNumberProps['minFractionDigits']; | ||
mode?: InputNumberMode; | ||
modelValue?: number; | ||
name: string; | ||
placeholder?: InputNumberProps['placeholder']; | ||
readonly?: InputNumberProps['readonly']; | ||
required?: boolean; | ||
success?: boolean; | ||
}>(), | ||
{ | ||
disabled: false, | ||
error: false, | ||
helperText: undefined, | ||
hideLabel: false, | ||
max: undefined, | ||
maxFractionDigits: undefined, | ||
min: undefined, | ||
minFractionDigits: undefined, | ||
mode: InputNumberMode.DECIMAL, | ||
modelValue: 0, | ||
placeholder: undefined, | ||
readonly: false, | ||
required: false, | ||
success: false | ||
} | ||
); | ||
const emits = defineEmits<{ | ||
(e: 'update:modelValue', value: number): void; | ||
(e: 'change', value: number): void; | ||
(e: 'focus'): void; | ||
(e: 'blur'): void; | ||
}>(); | ||
const helperTextId = computed(() => `${props.id}-helper`); | ||
const computedInputProps = computed<InputHTMLAttributes>(() => { | ||
return { | ||
...attrs, | ||
'aria-describedby': props.helperText ? helperTextId.value : undefined, | ||
name: props.name, | ||
required: props.required | ||
}; | ||
}); | ||
const currency = computed(() => | ||
props.mode === InputNumberMode.CURRENCY ? 'USD' : undefined | ||
); | ||
</script> | ||
|
||
<style lang="scss"> | ||
.uic-input-number-container { | ||
@apply flex flex-col gap-1; | ||
} | ||
.uic-input-number { | ||
@apply px-4 py-3; | ||
@apply text-sm text-gray-800; | ||
@apply bg-white; | ||
@apply border border-gray-200 rounded; | ||
outline: none; | ||
&:hover { | ||
@apply border-gray-300; | ||
} | ||
&:focus-within { | ||
@apply border-gray-400; | ||
} | ||
&::placeholder { | ||
@apply text-gray-200; | ||
} | ||
&.uic-error { | ||
@apply bg-red-50; | ||
@apply border-red-600; | ||
@apply text-red-600; | ||
&::placeholder { | ||
@apply text-red-600; | ||
} | ||
} | ||
&.uic-success { | ||
@apply bg-green-50; | ||
@apply border-green-700; | ||
@apply text-green-700; | ||
&::placeholder { | ||
@apply text-green-700; | ||
} | ||
} | ||
&:disabled { | ||
@apply bg-gray-50; | ||
@apply text-gray-300; | ||
@apply border-gray-200; | ||
&::placeholder { | ||
@apply text-gray-300; | ||
} | ||
} | ||
} | ||
.uic-input-number-helper { | ||
@apply text-xs text-gray-500; | ||
&.uic-error { | ||
@apply text-red-600; | ||
} | ||
&.uic-success { | ||
@apply text-green-700; | ||
} | ||
} | ||
</style> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<typeof InputNumber> = { | ||
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'); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export const InputNumberMode = { | ||
CURRENCY: 'currency', | ||
DECIMAL: 'decimal' | ||
} as const; | ||
export type InputNumberMode = | ||
(typeof InputNumberMode)[keyof typeof InputNumberMode]; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters