-
Notifications
You must be signed in to change notification settings - Fork 32
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(Forms): introduce
Form.Status
components for showing error and…
… success messages (receipt) Co-authored-by: Anders <[email protected]>
- Loading branch information
1 parent
2561acb
commit 30d2b88
Showing
14 changed files
with
760 additions
and
0 deletions.
There are no files selected for viewing
25 changes: 25 additions & 0 deletions
25
packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Status.mdx
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,25 @@ | ||
--- | ||
title: 'Status' | ||
description: 'The `Form.Status` can be used to show success or error messages.' | ||
showTabs: true | ||
tabs: | ||
- title: Info | ||
key: '/info' | ||
- title: Demos | ||
key: '/demos' | ||
- title: Properties | ||
key: '/properties' | ||
breadcrumb: | ||
- text: Forms | ||
href: /uilib/extensions/forms/ | ||
- text: Form | ||
href: /uilib/extensions/forms/Form/ | ||
- text: Status | ||
href: /uilib/extensions/forms/Form/Status | ||
--- | ||
|
||
import Info from 'Docs/uilib/extensions/forms/Form/Status/info' | ||
import Demos from 'Docs/uilib/extensions/forms/Form/Status/demos' | ||
|
||
<Info /> | ||
<Demos /> |
74 changes: 74 additions & 0 deletions
74
packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Status/Examples.tsx
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,74 @@ | ||
import ComponentBox from '../../../../../../shared/tags/ComponentBox' | ||
import { Field, Form } from '@dnb/eufemia/src/extensions/forms' | ||
import { createRequest } from '../SubmitIndicator/Examples' | ||
import { Button } from '@dnb/eufemia/src' | ||
|
||
const request = createRequest() | ||
|
||
export const ErrorMessage = () => { | ||
return ( | ||
<ComponentBox scope={{ request }}> | ||
{() => { | ||
// myFormId can be anything, as long as it's a unique instance | ||
const myFormId = () => null | ||
|
||
return ( | ||
<Form.Handler | ||
id={myFormId} | ||
onSubmit={async () => { | ||
await request(1000) // Simulate a request | ||
|
||
Form.Status.setStatus(myFormId, 'error') | ||
}} | ||
> | ||
<Form.Status> | ||
<Form.Card> | ||
<Field.Email /> | ||
<Form.ButtonRow> | ||
<Form.SubmitButton variant="send" /> | ||
<Button | ||
variant="secondary" | ||
onClick={() => { | ||
Form.Status.setStatus(myFormId, 'error') | ||
}} | ||
> | ||
Show error | ||
</Button> | ||
</Form.ButtonRow> | ||
</Form.Card> | ||
</Form.Status> | ||
</Form.Handler> | ||
) | ||
}} | ||
</ComponentBox> | ||
) | ||
} | ||
|
||
export const SuccessMessage = () => { | ||
return ( | ||
<ComponentBox scope={{ request }}> | ||
{() => { | ||
// myFormId can be anything, as long as it's a unique instance | ||
const myFormId = () => null | ||
|
||
return ( | ||
<Form.Handler | ||
id={myFormId} | ||
onSubmit={async () => { | ||
await request(1000) // Simulate a request | ||
|
||
Form.Status.setStatus(myFormId, 'success') | ||
}} | ||
> | ||
<Form.Status> | ||
<Form.Card> | ||
<Field.Email /> | ||
<Form.SubmitButton variant="send" /> | ||
</Form.Card> | ||
</Form.Status> | ||
</Form.Handler> | ||
) | ||
}} | ||
</ComponentBox> | ||
) | ||
} |
16 changes: 16 additions & 0 deletions
16
.../dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Status/demos.mdx
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 @@ | ||
--- | ||
showTabs: true | ||
hideInMenu: true | ||
--- | ||
|
||
import * as Examples from './Examples' | ||
|
||
## Demos | ||
|
||
### Error message | ||
|
||
<Examples.ErrorMessage /> | ||
|
||
### Success message | ||
|
||
<Examples.SuccessMessage /> |
56 changes: 56 additions & 0 deletions
56
...s/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Status/info.mdx
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,56 @@ | ||
--- | ||
showTabs: true | ||
hideInMenu: true | ||
--- | ||
|
||
## Description | ||
|
||
`Form.Status` displays a status message that is fully covering the available space and can be used to show success as a receipt or error messages. | ||
|
||
## Usage | ||
|
||
By default the given children will be shown. | ||
|
||
```tsx | ||
import { Form } from '@dnb/eufemia/extensions/forms' | ||
|
||
render( | ||
<Form.Handler> | ||
<Form.Status>visible content</Form.Status> | ||
</Form.Handler>, | ||
) | ||
``` | ||
|
||
## Showing the success or error message | ||
|
||
You can show the success or error message by using the `Form.Status.setStatus` method: | ||
|
||
```tsx | ||
Form.Status.setStatus(id, 'success') | ||
// or | ||
Form.Status.setStatus(id, 'error') | ||
``` | ||
|
||
You can call it whenever you need to show the success message. Here is an example of how to use it: | ||
|
||
```tsx | ||
import { Form } from '@dnb/eufemia/extensions/forms' | ||
|
||
// myFormId can be anything, as long as it's a unique instance | ||
const myFormId = () => null | ||
|
||
render( | ||
<Form.Handler | ||
id={myFormId} | ||
onSubmit={async () => { | ||
Form.Status.setStatus(myFormId, 'success') | ||
}} | ||
> | ||
<Form.Status>content</Form.Status> | ||
</Form.Handler>, | ||
) | ||
``` | ||
|
||
## Accessibility | ||
|
||
The component will manage focus handling, which is important for screen readers and users using keyboard navigation. |
25 changes: 25 additions & 0 deletions
25
...design-system-portal/src/docs/uilib/extensions/forms/Form/Status/properties.mdx
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,25 @@ | ||
--- | ||
showTabs: true | ||
hideInMenu: true | ||
--- | ||
|
||
import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' | ||
import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' | ||
import { | ||
StatusSuccessProperties, | ||
StatusErrorProperties, | ||
} from '@dnb/eufemia/src/extensions/forms/Form/Status/StatusDocs' | ||
|
||
## Properties | ||
|
||
### Error | ||
|
||
<PropertiesTable props={StatusErrorProperties} /> | ||
|
||
### Success | ||
|
||
<PropertiesTable props={StatusSuccessProperties} /> | ||
|
||
## Translations | ||
|
||
<TranslationsTable localeKey={['StatusError', 'StatusSuccess']} /> |
167 changes: 167 additions & 0 deletions
167
packages/dnb-eufemia/src/extensions/forms/Form/Status/Status.tsx
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,167 @@ | ||
import React, { useCallback, useContext, useEffect, useRef } from 'react' | ||
import classnames from 'classnames' | ||
import Visibility from '../Visibility' | ||
import DataContext from '../../DataContext/Context' | ||
import { useSharedState } from '../../../../shared/helpers/useSharedState' | ||
import setStatus, { Status } from './setStatus' | ||
import { Button, Flex, Section } from '../../../../components' | ||
import { P } from '../../../../elements' | ||
import { useTranslation } from '../../hooks' | ||
import MainHeading from '../MainHeading' | ||
import SubmitButton from '../SubmitButton' | ||
|
||
export type Props = { | ||
success?: { | ||
title?: React.ReactNode | ||
description?: React.ReactNode | ||
buttonText?: React.ReactNode | ||
buttonHref?: string | ||
} | ||
error?: { | ||
title?: React.ReactNode | ||
description?: React.ReactNode | ||
retryButton?: React.ReactNode | ||
cancelButton?: React.ReactNode | ||
} | ||
onCancel?: () => void | ||
children: React.ReactNode | ||
className?: string | ||
} | ||
|
||
function StatusContainer(props: Props) { | ||
const { success, error, onCancel, className, children, ...restProps } = | ||
props | ||
|
||
const translations = useTranslation() | ||
|
||
const { id } = useContext(DataContext) || {} | ||
|
||
const { data } = useSharedState<{ | ||
activeStatus?: Status | ||
}>(id) | ||
const { activeStatus } = data || {} | ||
|
||
// To ensure we not animate on first render. | ||
// When there are several Examples rendered at the same time, | ||
// the first one will animate on the first render. | ||
const animateRef = useRef(undefined) | ||
useEffect(() => { | ||
animateRef.current = true | ||
}, []) | ||
|
||
const innerRef = useRef<HTMLDivElement>(null) | ||
const onVisible = useCallback(() => { | ||
if (animateRef.current) { | ||
innerRef.current.focus?.() | ||
} | ||
}, []) | ||
|
||
// To keep the content visible while hiding it with the HightAnimation | ||
const currentStatusRef = useRef<Status>() | ||
if (activeStatus) { | ||
currentStatusRef.current = activeStatus | ||
} | ||
|
||
const onCancelHandler = useCallback(() => { | ||
if (id) { | ||
setStatus(id, undefined) | ||
} | ||
onCancel?.() | ||
}, [id, onCancel]) | ||
|
||
const childrenAreVisible = | ||
typeof activeStatus !== 'undefined' | ||
? !(activeStatus === activeStatus) | ||
: undefined | ||
const statusContentIsVisible = | ||
typeof activeStatus !== 'undefined' | ||
? activeStatus === activeStatus | ||
: false | ||
|
||
let statusContent = null | ||
|
||
if (currentStatusRef.current === 'success') { | ||
const tr = translations.StatusSuccess | ||
const { | ||
title, | ||
description, | ||
buttonText, | ||
buttonHref = '/', | ||
} = success || {} | ||
|
||
statusContent = ( | ||
<Section | ||
variant="info" | ||
innerSpace={{ top: 'large', bottom: 'xx-large' }} | ||
{...restProps} | ||
> | ||
<Flex.Stack gap="large"> | ||
<MainHeading>{title ?? tr.title}</MainHeading> | ||
<P>{description ?? tr.description}</P> | ||
{buttonHref && ( | ||
<Button href={buttonHref}> | ||
{buttonText ?? tr.buttonText} | ||
</Button> | ||
)} | ||
</Flex.Stack> | ||
</Section> | ||
) | ||
} else if (currentStatusRef.current === 'error') { | ||
const tr = translations.StatusError | ||
const { title, description, cancelButton, retryButton } = error || {} | ||
|
||
statusContent = ( | ||
<Section | ||
variant="transparent" | ||
innerSpace={{ top: 'large', bottom: 'xx-large' }} | ||
{...restProps} | ||
> | ||
<Flex.Stack gap="large"> | ||
<MainHeading>{title ?? tr.title}</MainHeading> | ||
<P>{description ?? tr.description}</P> | ||
<Flex.Horizontal> | ||
<Button variant="secondary" onClick={onCancelHandler}> | ||
{cancelButton ?? tr.cancelButton} | ||
</Button> | ||
<SubmitButton>{retryButton ?? tr.retryButton}</SubmitButton> | ||
</Flex.Horizontal> | ||
</Flex.Stack> | ||
</Section> | ||
) | ||
} | ||
|
||
return ( | ||
<div | ||
className={classnames( | ||
'dnb-forms-status', | ||
activeStatus && `dnb-forms-status--${activeStatus}`, | ||
'dnb-no-focus', | ||
className | ||
)} | ||
tabIndex={-1} | ||
ref={innerRef} | ||
> | ||
<Visibility | ||
visible={statusContentIsVisible} | ||
onVisible={onVisible} | ||
animate={animateRef.current} | ||
> | ||
{statusContent} | ||
</Visibility> | ||
|
||
<Visibility | ||
visible={childrenAreVisible} | ||
onVisible={onVisible} | ||
animate={animateRef.current} | ||
keepInDOM | ||
> | ||
{children} | ||
</Visibility> | ||
</div> | ||
) | ||
} | ||
|
||
StatusContainer.setStatus = setStatus | ||
StatusContainer._supportsSpacingProps = true | ||
|
||
export default StatusContainer |
Oops, something went wrong.