Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Forms): expiry field #2660

Merged
merged 94 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from 88 commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
fd1c133
create Expiry component
joakbjerk Sep 14, 2023
37260f8
add ref and keep_placeholder props to StringComponent
joakbjerk Sep 14, 2023
39762ff
add stories for Expiry field
joakbjerk Sep 14, 2023
917ee0d
add focus toggle for month and year in input
joakbjerk Sep 15, 2023
de08d44
fix month last digit bug
joakbjerk Sep 15, 2023
ca346d5
add empty placeholder option
joakbjerk Sep 15, 2023
33e9a30
fix placeholder type value
joakbjerk Sep 15, 2023
30892cf
rename divider to delimiter
joakbjerk Sep 19, 2023
960a2c4
add test for onchange
joakbjerk Sep 19, 2023
8e43171
add invisible characters to space placeholder
joakbjerk Sep 19, 2023
3a541bf
add tests for placeholder
joakbjerk Sep 19, 2023
8e7fc78
document prop and paramter types in typescript
joakbjerk Sep 19, 2023
e80f968
update onChange type description
joakbjerk Sep 19, 2023
c45d17a
add documentation to the portal
joakbjerk Sep 19, 2023
eff348d
adjusted width
joakbjerk Sep 19, 2023
9c9cb81
removed none placeholder type option
joakbjerk Sep 19, 2023
233563a
remove Value.Expiry documentation link
joakbjerk Sep 19, 2023
bf36caf
make tabbing inside input function more akin to DatePicker
joakbjerk Sep 20, 2023
1a7b36d
make Expiry cursor function like datepicker input
joakbjerk Sep 25, 2023
9fa0a09
fix cursor behaviour
joakbjerk Sep 25, 2023
0dae922
adjusted tab behaviour
joakbjerk Sep 25, 2023
98bcb66
decouple keyhandler into own hook
joakbjerk Sep 26, 2023
3b2a15e
add keyboard tests
joakbjerk Sep 26, 2023
7cf3f44
correct TextMask element props
joakbjerk Sep 26, 2023
fc6eab5
update style import paths
joakbjerk Sep 26, 2023
11d9d81
remove placeholder prop and add Field props
joakbjerk Sep 27, 2023
5bab995
add context and useDataValue hook
joakbjerk Sep 27, 2023
7a082ae
clean up documentation
joakbjerk Sep 27, 2023
b4fd15f
make placeholder char be based on locales
joakbjerk Sep 27, 2023
5fbb049
fix onChange behaviour
joakbjerk Sep 27, 2023
d624d72
update documentation
joakbjerk Sep 27, 2023
b8eb248
fix styling for storybook
joakbjerk Sep 27, 2023
cc12ecf
fix lint errors
joakbjerk Sep 27, 2023
737e69c
remove duplicate innerRef
joakbjerk Sep 27, 2023
d48ae5d
add value prop
joakbjerk Sep 28, 2023
d43fafb
update tests to accomendate for value prop
joakbjerk Sep 28, 2023
d73d818
fix linting issue
joakbjerk Sep 28, 2023
4c95f49
add placeholder_character to locales to prevent unnecessary logic han…
joakbjerk Sep 28, 2023
2cbb227
update documentation description
joakbjerk Sep 28, 2023
eda6045
add FieldBlock to expiry date
joakbjerk Sep 29, 2023
8419ed0
add default label for expiry date
joakbjerk Sep 29, 2023
5e40484
fix status color highlighting
joakbjerk Sep 29, 2023
4b180a1
update docs examples
joakbjerk Sep 29, 2023
2a1d88d
fix typo
joakbjerk Sep 29, 2023
a1fd202
fix onChange value type
joakbjerk Sep 29, 2023
d040c5c
add required prop
joakbjerk Sep 29, 2023
2662f12
fix disabled state
joakbjerk Sep 29, 2023
512a7ae
fix FieldBlock import
joakbjerk Sep 29, 2023
72bed36
replaced TextMask with InputMasked
joakbjerk Oct 3, 2023
c6b38cc
fix portal crash
joakbjerk Oct 3, 2023
8d90dd8
remove date-picker style dependency
joakbjerk Oct 4, 2023
e4c7850
sanitize input value
joakbjerk Oct 4, 2023
f8c9a6b
move useHandleCursorPositionHook to its own file
joakbjerk Oct 5, 2023
dcd131d
fix test warnings
joakbjerk Oct 5, 2023
c1fd1e1
fix style lint
joakbjerk Oct 5, 2023
db101d7
update value in documentation
joakbjerk Oct 5, 2023
f4a08ab
make onChange only fire when input is filled out or emptied
joakbjerk Oct 9, 2023
d35c287
update description
joakbjerk Oct 6, 2023
e4a2ef7
fix spacing in onChange description
joakbjerk Oct 6, 2023
5a8a8f2
simplify value sanitation
joakbjerk Oct 9, 2023
cc9cde1
fix tests
joakbjerk Oct 10, 2023
2ea630d
replace Expiry custom child components with MultiInputMask
joakbjerk Nov 6, 2023
1b6e475
move Expiry to new folder structure
joakbjerk Nov 6, 2023
05c2bed
add suffix ti MultiInputMask
joakbjerk Nov 7, 2023
c141e81
add suffix to Expiry
joakbjerk Nov 7, 2023
66ecc4c
update tests
joakbjerk Nov 7, 2023
97c9241
add title for demos page
joakbjerk Nov 7, 2023
13cf332
update documentation
joakbjerk Nov 7, 2023
eb10f41
fix style import
joakbjerk Nov 9, 2023
3fa99aa
remove Field scope from examples
joakbjerk Nov 10, 2023
c889985
correct date format documentation
joakbjerk Nov 10, 2023
4961a40
remove unecessary index.scss file
joakbjerk Nov 10, 2023
699a0e3
make value be string instead of object
joakbjerk Nov 10, 2023
0bd6469
add warning triggered by value length being longer than four
joakbjerk Nov 10, 2023
8ac9822
add expiry validator
joakbjerk Nov 10, 2023
bf48103
update documentation to reflect value changes
joakbjerk Nov 10, 2023
cc2512f
update tests to reflect value change
joakbjerk Nov 10, 2023
223371f
add tests for input validation
joakbjerk Nov 10, 2023
8c9b720
improve month mask to remove custom validation
joakbjerk Nov 13, 2023
201117e
ensure unique id for markup elements
joakbjerk Nov 13, 2023
5b3bc26
fix value of error example
joakbjerk Nov 13, 2023
f51f73a
correct required test
joakbjerk Nov 13, 2023
a131c9a
fix lint error
joakbjerk Nov 13, 2023
a9ae7fb
remove wrongly merged type
joakbjerk Nov 13, 2023
34568bd
fix space in type defenition
joakbjerk Nov 13, 2023
74e34d0
add wronly removed style import during merge
joakbjerk Nov 13, 2023
5d28a3f
use id instead of label for markupId
joakbjerk Nov 13, 2023
881cbf8
update MultiInputMask tests
joakbjerk Nov 13, 2023
f9b911c
remove value limit warning
joakbjerk Nov 14, 2023
adb2cce
rename id to idRef to clearly communicate variable type
joakbjerk Nov 14, 2023
76a6704
add default value to layout
joakbjerk Nov 14, 2023
92537ec
make onChange test more concise
joakbjerk Nov 14, 2023
293d042
add specific styling
joakbjerk Nov 14, 2023
7e45d6a
add visual tests
joakbjerk Nov 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Read more about other addons [on the open-source project](https://github.com/tex
| `delimiter` | _(optional)_ character that seperates the input inputs. |
| `status` | _(optional)_ text with a status message. The style defaults to an error message. You can use true to only get the status color, without a message.`. |
| `statusState` | _(optional)_ defines the state of the status. Currently, there are two statuses [error, info]. Defaults to error. |
| `suffix` | _(optional)_ Text describing the content of the input more than the label. you can also send in a React component, so it gets wrapped inside the Input component. |

### MultiInputMask inputs properties

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
title: 'Expiry'
description: '`Field.Expiry` is a wrapper component for the input of strings, with user experience tailored for expiry dates for payment cards.'
showTabs: true
tabs:
- title: Info
key: '/info'
- title: Demos
key: '/demos'
- title: Properties
key: '/properties'
- title: Events
key: '/events'
breadcrumb:
- text: Forms
href: /uilib/extensions/forms/
- text: Feature fields
href: /uilib/extensions/forms/feature-fields/
- text: Expiry
href: /uilib/extensions/forms/feature-fields/Expiry/
---

import Info from 'Docs/uilib/extensions/forms/feature-fields/Expiry/info'
import Demos from 'Docs/uilib/extensions/forms/feature-fields/Expiry/demos'

<Info />
<Demos />
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import ComponentBox from '../../../../../../shared/tags/ComponentBox'
import { Field } from '@dnb/eufemia/src/extensions/forms'
import { FormError } from '@dnb/eufemia/src/extensions/forms/types'

export const Empty = () => {
return (
<ComponentBox>
<Field.Expiry
onChange={(expiry) => console.log('onChange', expiry)}
/>
</ComponentBox>
)
}

export const Label = () => {
return (
<ComponentBox>
<Field.Expiry
label="Label text"
onChange={(expiry) => console.log('onChange', expiry)}
/>
</ComponentBox>
)
}

export const WithHelp = () => {
return (
<ComponentBox>
<Field.Expiry
label="Label text"
help={{
title: 'Help is available',
contents:
'Kindness and helping others will return to you when you least expect it, and maybe when you need it.',
}}
onChange={(expiry) => console.log('onChange', expiry)}
/>
</ComponentBox>
)
}

export const Disabled = () => {
return (
<ComponentBox>
<Field.Expiry
value="0826"
label="Label text"
onChange={(expiry) => console.log('onChange', expiry)}
disabled
/>
</ComponentBox>
)
}

export const Error = () => {
return (
<ComponentBox scope={{ FormError }}>
<Field.Expiry
value="0326"
label="Label text"
onChange={(expiry) => console.log('onChange', expiry)}
error={new FormError('This is what is wrong...')}
/>
</ComponentBox>
)
}

export const ValidationRequired = () => {
return (
<ComponentBox>
<Field.Expiry
value="0826"
label="Label text"
onChange={(expiry) => console.log('onChange', expiry)}
required
joakbjerk marked this conversation as resolved.
Show resolved Hide resolved
/>
</ComponentBox>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
showTabs: true
---

import ChangeLocale from 'dnb-design-system-portal/src/core/ChangeLocale'
import * as Examples from './Examples'

## Demos

The locale is what determines the components `placeholder` format .e.g. `mm/åå` in norwegian, `mm/yy` in english.

<ChangeLocale bottom label="Locale used in the demos:" showUS={true} />
joakbjerk marked this conversation as resolved.
Show resolved Hide resolved

English (US) is not included in Eufemia by default. You can include it like:

```jsx
import enUS from '@dnb/eufemia/shared/locales/en-US'
<EufemiaProvider locale={enUS} ...>
App
</EufemiaProvider>
```

## Demos

### Empty

<Examples.Empty />

### Label

<Examples.Label />

### With help

<Examples.WithHelp />

### Disabled

<Examples.Disabled />

### Error

<Examples.Error />

### Validation - Required

<Examples.ValidationRequired />
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
showTabs: true
---

import DataValueWriteEvents from 'Docs/uilib/extensions/forms/data-value-write-events.mdx'

## Events

| Event | Description |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `onChange` | _(optional)_ Will be called when the component values changes. Passes a string representing the date, without the delimiter as parameters, E.g. `0826` |
| `onFocus` | _(optional)_ Will be called when the component gets into focus. Like clicking inside a text input or opening a dropdown. Called with active value as argument. |
| `onBlur` | _(optional)_ Will be called when the component stop being in focus. Like when going to next field, or closing a dropdown. Called with active value as argument. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
showTabs: true
---

## Description

`Field.Expiry` is a wrapper component for the input of strings, with user experience tailored for expiry dates for payment cards.

```jsx
import { Field } from '@dnb/eufemia/extensions/forms'
render(<Field.Expiry />)
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
showTabs: true
---

import DataValueReadwriteProperties from '../../data-value-readwrite-properties.mdx'

## Properties

<DataValueReadwriteProperties type="string" />
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any properties we should omit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, will double check!

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { SpacingProps } from '../space/types'
import { createSpacingClasses } from '../space/SpacingHelper'
import { FormStatusState, FormStatusText } from '../FormStatus'
import { useMultiInputValue } from './hooks/useMultiInputValues'
import { makeUniqueId } from '../../shared/component-helper'

export type MultiInputMaskInput<T extends string> = {
/**
Expand Down Expand Up @@ -66,6 +67,10 @@ export type MultiInputMaskProps<T extends string> = {
* Defines the state of the status. Currently, there are two statuses `[error, info]`. Defaults to `error`.
*/
statusState?: FormStatusState
/**
* Text describing the content of the input more than the label. you can also send in a React component, so it gets wrapped inside the Input component.
*/
suffix?: React.ReactNode
} & Omit<
React.HTMLProps<HTMLInputElement>,
'onChange' | 'ref' | 'value' | 'label'
Expand All @@ -83,6 +88,7 @@ function MultiInputMask<T extends string>({
statusState,
values: defaultValues,
className,
suffix,
...props
}: MultiInputMaskProps<T>) {
const [values, onChange] = useMultiInputValue({
Expand Down Expand Up @@ -131,6 +137,7 @@ function MultiInputMask<T extends string>({
disabled={disabled}
status={status}
status_state={statusState}
suffix={suffix}
input_element={inputs.map((input, index) => (
<Fragment key={input.id}>
<MultiInputMaskInput
Expand Down Expand Up @@ -184,11 +191,11 @@ function MultiInputMask<T extends string>({
// So that useHandleCursorPosition can do a per character test to see if the pressed key should be handeled or not
return inputs.reduce(
(keys, { id, mask }) => {
keys[`${id}__input`] = mask
keys[id] = mask

return keys
},
{} as Record<`${T}__input`, RegExp[]>
{} as Record<T, RegExp[]>
)
}

Expand Down Expand Up @@ -232,10 +239,13 @@ function MultiInputMaskInput<T extends string>({
onKeyDown,
onChange,
}: MultiInputMaskInputProps<T>) {
const markupId = `${id}-${makeUniqueId()}`

return (
<>
<TextMask
id={`${id}__input`}
id={`${markupId}__input`}
data-mask-id={id}
className={classnames(
'dnb-input__input',
'dnb-multi-input-mask__input',
Expand All @@ -249,7 +259,7 @@ function MultiInputMaskInput<T extends string>({
guide={true}
showMask={true}
keepCharPositions={false} // so we can overwrite next value, if it already exists
aria-labelledby={`${id}__label`}
aria-labelledby={`${markupId}__label`}
ref={inputRef}
onKeyDown={onKeyDown}
onFocus={onFocus}
Expand All @@ -260,7 +270,11 @@ function MultiInputMaskInput<T extends string>({
)
}}
/>
<label id={`${id}__label`} htmlFor={`${id}__input`} hidden>
<label
id={`${markupId}__label`}
htmlFor={`${markupId}__input`}
hidden
>
{label}
</label>
{delimiter && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export type TextMaskInputElement =
| React.ReactNode
| ((...args: any[]) => any);
export type TextMaskValue = string | number;
export interface TextMaskProps extends React.HTMLProps<HTMLElement> {
export interface TextMaskProps extends React.HTMLProps<HTMLInputElement> {
mask: TextMaskMask;
inputRef?: React.MutableRefObject<HTMLInputElement>;
inputElement?: TextMaskInputElement;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,13 @@ describe('MultiInputMask', () => {
document.querySelectorAll('.dnb-multi-input-mask__input')
) as HTMLInputElement[]

expect(first.id).toBe('day__input')
expect(first.id).toMatch(new RegExp(/^day-id-\w+__input/))
expect(first.tagName).toBe('INPUT')

expect(second.id).toBe('month__input')
expect(second.id).toMatch(new RegExp(/^month-id-\w+__input/))
expect(second.tagName).toBe('INPUT')

expect(third.id).toBe('year__input')
expect(third.id).toMatch(new RegExp(/^year-id-\w+__input/))
expect(third.tagName).toBe('INPUT')
})

Expand All @@ -133,15 +133,15 @@ describe('MultiInputMask', () => {
) as HTMLInputElement[]

expect(first.nextElementSibling).toHaveTextContent('the day')
expect(first.labels[0].id).toBe('day__label')
expect(first.labels[0].id).toMatch(new RegExp(/^day-id-\w+__label/))
expect(first.nextElementSibling.tagName).toBe('LABEL')

expect(second.nextElementSibling).toHaveTextContent('the month')
expect(second.labels[0].id).toBe('month__label')
expect(second.labels[0].id).toMatch(new RegExp(/^month-id-\w+__label/))
expect(second.nextElementSibling.tagName).toBe('LABEL')

expect(third.nextElementSibling).toHaveTextContent('the year')
expect(third.labels[0].id).toBe('year__label')
expect(third.labels[0].id).toMatch(new RegExp(/^year-id-\w+__label/))
expect(third.nextElementSibling.tagName).toBe('LABEL')
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ function getKeysToHandle({ keysToHandle, input }: GetKeysToHandleParams) {
return keysToHandle
}

const masks = keysToHandle[input.id]
const masks = keysToHandle[input.dataset.maskId]

const selection =
input.selectionStart === input.selectionEnd
Expand Down
Loading
Loading