diff --git a/packages/dnb-design-system-portal/src/core/ChangeStyleTheme.tsx b/packages/dnb-design-system-portal/src/core/ChangeStyleTheme.tsx index f745cb1fc13..2eb2faf15fa 100644 --- a/packages/dnb-design-system-portal/src/core/ChangeStyleTheme.tsx +++ b/packages/dnb-design-system-portal/src/core/ChangeStyleTheme.tsx @@ -7,7 +7,7 @@ import { setTheme, } from 'gatsby-plugin-eufemia-theme-handler' -export default function ChangeStyleTheme({ label = null } = {}) { +export default function ChangeStyleTheme({ label = null, ...rest } = {}) { const themes = getThemes() const { name } = getTheme() const { update } = React.useContext(Context) @@ -31,6 +31,7 @@ export default function ChangeStyleTheme({ label = null } = {}) { update({ skeleton: false }) }) }} + {...rest} /> ) } diff --git a/packages/dnb-design-system-portal/src/docs/quickguide-designer/typography/font-families.mdx b/packages/dnb-design-system-portal/src/docs/quickguide-designer/typography/font-families.mdx index 7f23a6795aa..ed6e16e6452 100644 --- a/packages/dnb-design-system-portal/src/docs/quickguide-designer/typography/font-families.mdx +++ b/packages/dnb-design-system-portal/src/docs/quickguide-designer/typography/font-families.mdx @@ -1,14 +1,15 @@ import { H3 } from '@dnb/eufemia/src' +# Font Families + + -## Font Families The default font family for all web applications is `Roboto`, however for headlines and some other items we use `Maison Neue`. ### Maison Neue -

This is a headline in Maison Neue

+

+ This is a paragraph using the headline font Maison Neue +

### Roboto regular -

+

+ Here is a paragraph with some nonsense lipsum text. Contrary to popular + belief, Lorem Ipsum passage, and going through the cites of the word in + classical literature, discovered the undoubtable source. Lorem Ipsum + comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et + Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. +

+
+ +## Roboto Medium + + +

Here is a paragraph with some nonsense lipsum text. Contrary to popular belief, Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum @@ -78,8 +95,10 @@ The default font family for all web applications is `Roboto`, however for headli ### Roboto bold +**NB!** bold is generally not used, use medium, unless there is a specific unique use case. + -

+

Here is a paragraph with some nonsense lipsum text. Contrary to popular belief, Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum diff --git a/packages/dnb-design-system-portal/src/docs/quickguide-designer/typography/font-weights.mdx b/packages/dnb-design-system-portal/src/docs/quickguide-designer/typography/font-weights.mdx index 656e1eca38e..a3ecae9eb72 100644 --- a/packages/dnb-design-system-portal/src/docs/quickguide-designer/typography/font-weights.mdx +++ b/packages/dnb-design-system-portal/src/docs/quickguide-designer/typography/font-weights.mdx @@ -1,6 +1,8 @@ ## Font Weights -Achieved with HTML classes: `.dnb-typo-regular`, `.dnb-typo-bold` +Achieved with HTML classes: `.dnb-t__weight--regular`,`.dnb-t__weight--medium` or `.dnb-t__weight--bold`. + +The old classes, `.dnb-typo-regular`, `.dnb-typo-medium` and `.dnb-typo-bold`, still work, but will also set font-family and font-style. ### Body Regular @@ -8,7 +10,7 @@ Achieved with HTML classes: `.dnb-typo-regular`,

Here is a paragraph with some nonsense lipsum text. Contrary to popular belief, Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum @@ -17,11 +19,10 @@ no need to use a class.

- ### Body Bold +**NB!** bold is generally not used, use medium, unless there is a specific unique use case. + -

+

Here is a paragraph with some nonsense lipsum text. Contrary to popular belief, Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum diff --git a/packages/dnb-design-system-portal/src/docs/quickguide-designer/typography/typographic-elements.mdx b/packages/dnb-design-system-portal/src/docs/quickguide-designer/typography/typographic-elements.mdx index 2c81995945d..294a9e2b1f1 100644 --- a/packages/dnb-design-system-portal/src/docs/quickguide-designer/typography/typographic-elements.mdx +++ b/packages/dnb-design-system-portal/src/docs/quickguide-designer/typography/typographic-elements.mdx @@ -137,16 +137,16 @@ This is an overview of the default, basic typographic elements such as **heading ### Note: -There are two methods to create small text. One, is to use the `.dnb-p--small` modifier class which can be used on paragraphs etc. and allows you to use a bottom margin. The other method is to just use a `` tag which is inline and cannot have a margin. +There are two methods to create small text. One, is to use the `.dnb-t__size--small` modifier class. The other method is to just use a `` tag. ### Example -

- This is a paragraph with a modifier class. This is the small - content. Quem facilisi moderatius id eam, id tamquam albucius per. Vel - quem congue appareat cu, mei te eros convenire. Sea bonorum epicuri ea, - ei exerci tacimates pro, aliquam pertinacia eu vim. +

+ This is a paragraph with a modifier class `.dnb-t__size--small`. + This is the small content. Quem facilisi moderatius id eam, id tamquam + albucius per. Vel quem congue appareat cu, mei te eros convenire. Sea + bonorum epicuri ea, ei exerci tacimates pro, aliquam pertinacia eu vim.

diff --git a/packages/dnb-design-system-portal/src/docs/uilib/about-the-lib/releases/eufemia/v10-info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/about-the-lib/releases/eufemia/v10-info.mdx index 1b91730ef90..2fbd71f4e1b 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/about-the-lib/releases/eufemia/v10-info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/about-the-lib/releases/eufemia/v10-info.mdx @@ -367,7 +367,7 @@ The Anchor was moved from `/elements` to `/components`. 2. Only camelCase props are supported for Drawer, so you will need to update the prop names. 3. `Modal.Inner` or `Modal.Content` converts to `Drawer.Body`. - 4. `Modal.Bar` converts to `Drawer.Navigaton`. + 4. `Modal.Bar` converts to `Drawer.Navigation`. 5. `Modal` was a class component and `Drawer` is a functional component. When you convert from `` or `` to `

` – follow these steps: @@ -378,7 +378,7 @@ The Anchor was moved from `/elements` to `/components`. 2. Only camelCase props are supported for Dialog, so you will need to update the prop names. 3. `Modal.Inner` or `Modal.Content` converts to `Dialog.Body`. - 4. `Modal.Bar` converts to `Dialog.Navigaton`. + 4. `Modal.Bar` converts to `Dialog.Navigation`. 5. `Modal` was a class component and `Dialog` is a functional component. ### [Lists](/uilib/elements/lists) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/about-the-lib/releases/eufemia/v11-info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/about-the-lib/releases/eufemia/v11-info.mdx index f85e7c2f3cc..300565f693e 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/about-the-lib/releases/eufemia/v11-info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/about-the-lib/releases/eufemia/v11-info.mdx @@ -148,6 +148,8 @@ The `InputPassword` component has been moved to `Field.Password`, and is now a p - replace `useError` with `useValidation`. - replace Form.Iterate label variable `{itemNr}` with `{itemNo}`. - replace `Form.FieldProps` with `Field.Provider`. +- replace `...` with `...`. +- replace `...` with `...`. ## NumberFormat @@ -199,4 +201,9 @@ const errorMessages = { - Got removed. Simply provide your error message as a object in the `errorMessages` property with an `useMemo` hook. +## DrawerList + +- replace type `DrawerListDataObjectUnion` with `DrawerListDataArrayItem`. +- replace type `DrawerListDataObject` with `DrawerListDataArrayObject`. + _February, 6. 2024_ diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/autocomplete/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/components/autocomplete/Examples.tsx index f1e5ff9c8df..f1f1102db33 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/autocomplete/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/autocomplete/Examples.tsx @@ -543,6 +543,50 @@ export const AutocompleteDisabledExample = () => ( ) +export const AutocompleteDisabledOptionsExample = () => ( + + + + + The Shawshank Redemption + + ), + year: 1994, + }, + { + disabled: true, + content: ['The Godfather', 'Line with more info'], + year: 1972, + }, + { + disabled: true, + content: [ + 'The Godfather: Part II', + + Anchor 1 + , + + Anchor 2 + , + 'Line with more info', + ], + year: 1974, + }, + { disabled: true, content: 'The Dark Knight', year: 2008 }, + ]} + label="Label" + bottom + /> + + +) + export const AutocompleteContentAsArrayExample = () => ( diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/autocomplete/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/autocomplete/properties.mdx index 41bacf01df2..2732054046d 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/autocomplete/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/autocomplete/properties.mdx @@ -6,6 +6,7 @@ import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/Transla import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { autocompleteProperties } from '@dnb/eufemia/src/components/autocomplete/AutocompleteDocs' import { DrawerListProperties } from '@dnb/eufemia/src/fragments/drawer-list/DrawerListDocs' +import DrawerListDataDoc from '../fragments/drawer-list/_prop-data.mdx' ## Properties @@ -17,59 +18,7 @@ You may check out the [DrawerList Properties](#drawerlist-properties) down below -## Data structure - -```js -// 1. as array -const data = [ - // Every data item can, beside "content" - contain what ever - { - // (optional) can be what ever - selected_key: 'key_0', - - // (optional) is show instead of "content", once selected - selected_value: 'Item 1 Value', - suffix_value: 'Addition 1', - - // Item content as a string, array or React Element - content: 'Item 1 Content', - }, - - // more items ... - { - selected_key: 'key_1', - content: ( - <> - - Searchable content - - ), - }, - { - selected_key: 'key_2', - selected_value: 'Item 3 Value', - suffix_value: 'Addition 3', - content: ( - - - Searchable content - - ), - }, - { - selected_key: 'key_3', - selected_value: 'Item 4 Value', - suffix_value: 'Addition 4', - content: ['Item 4 Content A', <>Custom Component], - }, -] - -// 2. as object -const data = { - a: 'A', - b: 'B', -} -``` + ## Translations diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/autocomplete/visual-tests.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/autocomplete/visual-tests.mdx index 848c1bd6bea..98faa46b3c1 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/autocomplete/visual-tests.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/autocomplete/visual-tests.mdx @@ -5,9 +5,11 @@ draft: true import { AutocompleteOpened, AutocompleteDisabledExample, + AutocompleteDisabledOptionsExample, } from 'Docs/uilib/components/autocomplete/Examples' + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/card/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/components/card/Examples.tsx index f6805a5f3c5..444d749ad75 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/card/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/card/Examples.tsx @@ -5,7 +5,6 @@ import { Flex, Grid, H2, - Hr, P, Section, Table, @@ -17,16 +16,12 @@ import { Field, Form } from '@dnb/eufemia/src/extensions/forms' export const Default = () => { return ( - - + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi cursus pharetra elit in bibendum.

-

- Praesent nunc ipsum, convallis eget convallis gravida, vehicula - vitae metus. -

) @@ -35,11 +30,11 @@ export const Default = () => { export const NestedCards = () => { return ( - +

First Card

- +

Second Card

- +

Third Card (for edge cases only)

@@ -176,10 +171,8 @@ export const Stack = () => { return ( - - -
- +

Stacked content

+

Stacked content

) @@ -188,12 +181,12 @@ export const Stack = () => { export const VerticalFields = () => { return ( - + - + ) } @@ -201,12 +194,12 @@ export const VerticalFields = () => { export const HorizontalFields = () => { return ( - + - + ) } @@ -274,3 +267,20 @@ export const WithNestedSection = () => {
) } + +export const WithOutset = () => { + return ( + + + I'm left aligned + +

Card content

+ +

Nested card

+
+
+ +
+
+ ) +} diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/card/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/card/demos.mdx index 3c88104ef8b..0db53fa4213 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/card/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/card/demos.mdx @@ -12,17 +12,23 @@ import * as Examples from './Examples' ### Vertical fields +When using Eufemia Forms, you may want to use [Form.Card](/uilib/extensions/forms/Form/Card/) instead of the original Card component. + ### Horizontal fields +When using Eufemia Forms, you may want to use [Form.Card](/uilib/extensions/forms/Form/Card/) instead of the original Card component. + ### Stack -The Card components needs to have `stack={true}` or `align="stretch"` in order to stretch its children components. +When `stack` is set to `true`, the Card will add a gap between its children and stretch them to the full. + +For [form components](uilib/extensions/forms/), you should use [Form.Card](/uilib/extensions/forms/Form/Card/) instead of the original Card component. -For [form components](uilib/extensions/forms/), you should use `stack={true}` to get the correct spacing. +When `stack` is set to `true`, the Card will add a gap between its children and stretch them to the full. @@ -32,6 +38,13 @@ Nested cards have `responsive={false}` by default and will not behave responsive +## With `outset` + +When using `outset`, the Card will break out of the layout container. +On small screens (mobile) the outset is removed. + + + ### Without padding diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/Examples.tsx index f42990760e3..ee284002148 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/Examples.tsx @@ -250,11 +250,11 @@ export const DropdownDirections = () => { data={[ ['Vertical', 'alignment'], <> -

Vertical

+

Vertical

alignment

, -

+

Horizontal

alignment

@@ -446,6 +446,26 @@ export const DropdownDisabled = () => (
) +export const DropdownDisabledOptions = () => ( + + + + + +) + export const DropdownDisabledTertiary = () => ( diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/demos.mdx index 564a2d0ff6c..da1f84accdb 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/demos.mdx @@ -12,6 +12,7 @@ import { DropdownTertiaryRight, DropdownMoreMenu, DropdownDisabled, + DropdownDisabledOptions, DropdownCustomEvent, DropdownSizes, DropdownCustomWidth, @@ -87,6 +88,10 @@ With long list to make it scrollable and searchable +Individual options can also be disabled. + + + ### Disabled tertiary dropdown diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/events.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/events.mdx index 8f04391fb14..368183e0d09 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/events.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/events.mdx @@ -4,12 +4,12 @@ showTabs: true ## Events -| Events | Description | -| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `on_change` | _(optional)_ will be called on state changes made by the user. Returns an object with the new selected `data` item `{ data, event, attributes, value }`. | -| `on_select` | _(optional)_ will be called once the user selects an item by a click or keyboard navigation. Returns an object with the new selected `data` item `{ data, event, attributes, value, active_item }`. The **active_item** property is the currently selected item by keyboard navigation | -| `on_show` | _(optional)_ will be called once the user presses the dropdown. Returns the data item `{ data, attributes }`. | -| `on_hide` | _(optional)_ will be called once the user presses the dropdown again, or clicks somewhere else. Returns the data item `{ data, attributes }`. | +| Events | Description | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `on_change` | _(optional)_ will be called on state changes made by the user. Returns an object with the new selected `data` item `{ data, event, attributes, value }`. | +| `on_select` | _(optional)_ will be called once the user focuses or selects an item by a click or keyboard navigation. Returns an object with the new selected `data` item `{ data, event, attributes, value, active_item }`. The **active_item** property is the currently selected item by keyboard navigation | +| `on_show` | _(optional)_ will be called once the user presses the dropdown. Returns the data item `{ data, attributes }`. | +| `on_hide` | _(optional)_ will be called once the user presses the dropdown again, or clicks somewhere else. Returns the data item `{ data, attributes }`. | ### The `on_change` vs `on_select` difference diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/properties.mdx index 46db4976934..7f8fe31a14e 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/dropdown/properties.mdx @@ -5,6 +5,7 @@ showTabs: true import TranslationsTable from 'dnb-design-system-portal/src/shared/parts/TranslationsTable' import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { DrawerListProperties } from '@dnb/eufemia/src/fragments/drawer-list/DrawerListDocs' +import DrawerListDataDoc from '../fragments/drawer-list/_prop-data.mdx' ## Properties @@ -54,49 +55,7 @@ Should either be an index (integer) of the data array or a key – defined by `s If `data` is an object, use the object key as the `value` to define the selected item. Can be a string or integer. -## Data structure - -```js -// 1. as array -const data = [ - // Every data item can, beside "content" - contain what ever - { - // (optional) can be what ever - selectedKey: 'key_0', - - // (optional) is show instead of "content", once selected - selected_value: 'Item 1 Value', - suffix_value: 'Addition 1', - - // Item content as a string or array - content: 'Item 1 Content', - }, - - // more items ... - { - selectedKey: 'key_1', - content: ['Item 2 Value', 'Item 2 Content'], - }, - { - selectedKey: 'key_2', - selected_value: 'Item 3 Value', - suffix_value: 'Addition 3', - content: ['Item 3 Content A', 'Item 3 Content B'], - }, - { - selectedKey: 'key_3', - selected_value: 'Item 4 Value', - suffix_value: 'Addition 4', - content: ['Item 4 Content A', <>Custom Component], - }, -] - -// 2. as object -const data = { - a: 'A', - b: 'B', -} -``` + ## Translations diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments.mdx index 65fddd82274..504d7525830 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments.mdx @@ -10,10 +10,6 @@ import ListFragments from 'dnb-design-system-portal/src/shared/parts/ListFragmen # Fragments -Fragments are small, low-level and reusable parts used inside other components. - -You may use them only to build new components from. - ## Import You import them like so: @@ -26,6 +22,12 @@ import { } from '@dnb/eufemia/fragments' ``` +## Description + +Fragments are small, low-level and reusable parts used inside other components. + +You may use them only to build new components from. + ## Available Fragments diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/Examples.tsx index 4061bfc69d5..f4513acd0b9 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/Examples.tsx @@ -126,6 +126,31 @@ export const DrawerListExampleDefault = () => ( ) +export const DrawerListExampleDisabled = () => ( + + + + + +) + export const DrawerListExampleSingleItem = () => ( diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/_prop-data.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/_prop-data.mdx new file mode 100644 index 00000000000..f3bf6c00d99 --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/_prop-data.mdx @@ -0,0 +1,125 @@ +import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' +import { DrawerListItem } from '@dnb/eufemia/src/fragments/drawer-list/DrawerListDocs' + +## The `data` property + +The `data` can be structured in two main ways: as an array, or as an object. An array is preferred as it gives you the most options. + +### `data` as an array + +```ts +// an array can contain complex items and offers the most control +const data = [ + { + content: "Item 1", + }, + { + content: Item 2 + }, + { + content: ["Item 3", "Line 2", Line 3] + }, + { + content: ['Main account', '1234 12 12345'], + selected_value: 'Main account (605,22 kr)', + suffix_value: '605,22 kr', + }, + { + content: ['Old account', Closed], + disabled: true, + suffix_value: '0,00 kr', + }, +] + +// If you only use the `content` property, you can use it directly in the array. +// This list is identical to the one above: +const data = [ + "Item 1", + Item 2, + ["Item 3", "Line 2", Line 3], + { + content: ['Main account', '1234 12 12345'], + selected_value: 'Main account (605,22 kr)', + suffix_value: '605,22 kr', + }, + { + content: ['Old account', Closed], + disabled: true, + suffix_value: '0,00 kr', + }, +] + +const onChange = ({ data, value }) => { + console.log(data) // returns the item as it appears in the array + console.log(value) // returns the index of the item +} +``` + +Each object in the array have the following properties: + + + +### `data` as an object + +A simpler alternative, but with less options + +```ts +// Each entry can contain the same type of value as the array's `content` property +const data = { + first: "Item 1",, + second: Item 2, + last: ["Item 3", "Line 2", Line 3], +} + +const onChange = ({ data, value }) => { + console.log(data) + // returns a generated object representing the item: + // { + // selectedKey: 'first', + // value: 'first', + // content: 'Item 1', + // type: 'object' + // } + + console.log(value) // returns the key ("first", "second", or "last"), instead of an index + +} + +``` + +### `data` types overview + +The following is an overview of all the types that the `data` prop accepts. (These are not actual names of actual types in the library.) + +```ts +// The visual content that is shown in one DrawerList item. +// An array can be used to define multiple lines. +type CONTENT = string | React.Node | (string | React.Node)[] + +// An array item +type ARRAY_OBJECT = { + content: CONTENT + disabled?: boolean + selectedKey?: string | number + selected_value?: string | React.Node + suffix_value?: string | React.Node +} + +// `data` as an array. A list of "ARRAY_OBJECT" types is preferred, +// but the "CONTENT" type can be useful for simple lists. +type ARRAY = (CONTENT | ARRAY_OBJECT)[] + +// `data` as an object. Can only contain the "CONTENT" type. +// Each `key` behaves like the "ARRAY_OBJECT"'s `selectedKey`. +type RECORD = Record + +// An object or array that represents the entire DrawerList list. +type DATA = ARRAY | RECORD + +// The final type of the `data` prop: +let data: DATA | () => DATA +``` + +#### JSON string + +There is technically support for sending in a JSON string of the data to the `data` prop. But this is an old functionality that we do not really support anymore. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/demos.mdx index f08e26e89d1..8b0f9fe927a 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/demos.mdx @@ -6,6 +6,7 @@ import { DrawerListExampleInteractive, DrawerListExampleOnlyToVisualize, DrawerListExampleDefault, + DrawerListExampleDisabled, DrawerListExampleSingleItem, DrawerListExampleMarkup, } from 'Docs/uilib/components/fragments/drawer-list/Examples' @@ -24,6 +25,10 @@ import { +### Disabled + + + ### Custom event and link on single item diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/properties.mdx index baf76d87302..7cd82e4e733 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/fragments/drawer-list/properties.mdx @@ -4,7 +4,10 @@ showTabs: true import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' import { DrawerListProperties } from '@dnb/eufemia/src/fragments/drawer-list/DrawerListDocs' +import DrawerListDataDoc from './_prop-data.mdx' ## Properties + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/progress-indicator/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/components/progress-indicator/Examples.tsx index baae35ec130..30d98e9ca8b 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/progress-indicator/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/progress-indicator/Examples.tsx @@ -66,7 +66,9 @@ export const ProgressIndicatorCircularLabelInsideExample = () => ( labelDirection="inside" data-visual-test="progress-indicator-label-inside" > - {72}% + + {72}% + ) @@ -360,7 +362,7 @@ const StyledLabel = styled.span` ` const MyCustomLabel = ({ children, ...rest }) => ( {children} diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/section/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/components/section/properties.mdx index f7f18d8e6bc..72f44708195 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/section/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/section/properties.mdx @@ -2,21 +2,12 @@ showTabs: true --- +import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' +import { SectionProperties } from '@dnb/eufemia/src/components/section/SectionDocs' + ## Properties -| Properties | Description | -| --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `variant` | _(optional)_ defines the semantic purpose and subsequently the style of the visual helper. Will take precedence over the style_type property | -| `element` | _(optional)_ define what HTML element should be used. Defaults to `
`. | -| `breakout` | _(optional)_ use `true` to enable a fullscreen breakout look. Supports also media query breakpoints like `{ small: boolean }`. Defaults to `true`. | -| `outline` | _(optional)_ define a custom border color. If `true` is given, `color-black-8` is used. Use a Eufemia [color](/uilib/usage/customisation/colors/). Supports also media query breakpoints like `{ small: 'black-8' }` | -| `roundedCorner` | _(optional)_ use `true` to enable rounded corners (border-radius). Supports also media query breakpoints like `{ small: boolean }`. Defaults to `false`. | -| `backgroundColor` | _(optional)_ define a custom background color, instead of a variant. Use a Eufemia [color](/uilib/usage/customisation/colors/). Supports also media query breakpoints like `{ small: 'white' }`. | -| `dropShadow` | _(optional)_ use `true` to show the default Eufemia DropShadow. Supports also media query breakpoints like `{ small: true }`. | -| `textColor` | _(optional)_ define a custom text color to compliment the backgroundColor. Use a Eufemia [color](/uilib/usage/customisation/colors/). Supports also media query breakpoints like `{ small: 'black-80' }`. | -| `innerSpace` | _(optional)_ will add a padding around the content. Supports also media query breakpoints like `{small: { top: 'medium' }}`. | -| `innerRef` | _(optional)_ by providing a React Ref we can get the internally used element (DOM). E.g. `inner_ref={myRef}` by using `React.createRef()` or `React.useRef()`. | -| [Space](/uilib/layout/space/properties) | _(optional)_ spacing properties like `top` or `bottom` are supported. | + ## Variants diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/textarea/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/components/textarea/Examples.tsx index 0fe519ec67c..1411a3540a3 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/textarea/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/textarea/Examples.tsx @@ -6,7 +6,7 @@ import React from 'react' import ComponentBox from '../../../../shared/tags/ComponentBox' import styled from '@emotion/styled' -import { Textarea, HelpButton, Flex, Card } from '@dnb/eufemia/src' +import { Textarea, HelpButton, Flex } from '@dnb/eufemia/src' import { Field, Form } from '@dnb/eufemia/src/extensions/forms' export const RowsCols = () => ( @@ -154,7 +154,7 @@ export const MaxLength = () => ( - + ( characterCounter={{ max: 3, variant: 'up' }} /> - + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/upload/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/components/upload/Examples.tsx index 349c14ec012..ed3ce133a2d 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/components/upload/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/components/upload/Examples.tsx @@ -12,7 +12,6 @@ import { Section, Upload, } from '@dnb/eufemia/src' -import { UploadValue } from '@dnb/eufemia/src/extensions/forms/Field/Upload' export function createMockFile(name: string, size: number, type: string) { const file = new File([], name, { type }) @@ -35,46 +34,6 @@ const useMockFiles = (setFiles, extend) => { }, []) } -export async function mockAsyncFileUpload( - newFiles: UploadValue, -): Promise { - const promises = newFiles.map(async (file, index) => { - const formData = new FormData() - formData.append('file', file.file, file.file.name) - - await new Promise((resolve) => - setTimeout(resolve, Math.floor(Math.random() * 2000) + 1000), - ) - - const mockResponse = { - ok: (index + 2) % 2 === 0, // Every other request will fail - json: async () => ({ - server_generated_id: `${file.file.name}_${crypto.randomUUID()}`, - }), - } - - return await Promise.resolve(mockResponse) - .then((res) => { - if (res.ok) return res.json() - throw new Error('Unable to upload this file') - }) - .then((data) => { - return { - ...file, - id: data.server_generated_id, - } - }) - .catch((error) => { - return { - ...file, - errorMessage: error.message, - } - }) - }) - - return await Promise.all(promises) -} - export const UploadPrefilledFileList = () => ( +

Default paragraph

+

Regular weight paragraph (same as default)

+

Medium weight paragraph

+
+ ) +} +export function ParagraphSizeModifiers() { + return ( + +

x-small paragraph

+

small paragraph

+

medium paragraph

+

basis paragraph (same as default)

+

large paragraph

+

x-large paragraph

+

xx-large paragraph

+
+ ) +} +export function ParagraphAlignmentModifiers() { + return ( + +

Right aligned paragraph

+

Center aligned paragraph

+

Left aligned paragraph

+
+ ) +} +export function ParagraphFamilyModifiers() { + return ( + +

Basis family paragraph (same as default)

+

+ Heading family paragraph (only different on some themes) +

+

Monospace family paragraph

+
+ ) +} + +export function ParagraphLineHeightModifiers() { + return ( + +

x-small line-height paragraph

+

small line-height paragraph

+

medium line-height paragraph

+

+ basis line-height paragraph (same as default) +

+

large line-height paragraph

+

x-large line-height paragraph

+

xx-large line-height paragraph

+
+ ) +} +export function ParagraphAdditionalModifiers() { + return ( + +
+

Bold weight paragraph

+

Underline paragraph

+

Italic paragraph

+
+
+ ) +} + export function ParagraphDefault() { return ( Strong paragraph (medium weight) - {/* Italic paragraph */} - {/* Underline paragraph */} Numbers 0123456789 Code paragraph @@ -172,22 +240,6 @@ export function ParagraphAdditional() { ) } -export function ParagraphModifiers() { - return ( - -
-

Default paragraph

-

Medium weight paragraph

-

Small paragraph

-

Small paragraph with medium weight

- {/* (Bold is currently not supported by DNB UX) */} - {/*

Bold weight paragraph

*/} - {/*

Small paragraph with bold weight

*/} -
-
- ) -} - export function ParagraphRegressionTests() { const PWrap = ({ customSize = null, ...props }) => { const size = props.size || customSize @@ -196,10 +248,10 @@ export function ParagraphRegressionTests() {

{size}

-

+

{size} - Weight medium

-

+

{size} - Weight bold

diff --git a/packages/dnb-design-system-portal/src/docs/uilib/elements/paragraph/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/elements/paragraph/demos.mdx index 1da55828471..b06fbe36dad 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/elements/paragraph/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/elements/paragraph/demos.mdx @@ -3,28 +3,75 @@ showTabs: true --- import { + ParagraphWeightModifiers, + ParagraphSizeModifiers, + ParagraphAlignmentModifiers, + ParagraphFamilyModifiers, + ParagraphLineHeightModifiers, + ParagraphAdditionalModifiers, ParagraphDefault, ParagraphSmall, ParagraphAdditional, ParagraphRegressionTests, - ParagraphModifiers, } from 'Docs/uilib/elements/paragraph/Examples' ## Demos ### Paragraphs modifiers - +These are the standard available modifiers for paragraph typography: -### Paragraphs `basis` sized +- [Weight](#weight) +- [Size](#size) +- [Alignment](#alignment) +- [Font family](#font-family) +- [Line height](#line-height) + +As well as some [other modifiers](#other-modifiers). + +#### Weight + + + +#### Size + +Also automatically sets the matching line-height (`line` prop). + + + +#### Alignment + + + +#### Font family + + + +#### Line height + +Line-height will be set automatically based on the `size` props, but can also be set separately if needed. + + + +#### Other modifiers + +Although bold, italic and underline are not a standard part of the Eufemia design system for typography (in particular, "medium" should be used instead of "bold"), we still include them as an option for convenience. And there are also cases where an accessibility case can be made for their use. + + + +### Children tag styling + +Paragraph also adds some default styling to child typography HTML elements. Like `` or ``. + +#### Paragraphs `basis` sized -#### Paragraph `small` sized +##### Paragraph `small` sized -#### Additional Paragraph formatting (not defined yet) +##### Additional Paragraph formatting (not defined yet) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/elements/paragraph/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/elements/paragraph/info.mdx index bb55617fbed..36c978ae877 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/elements/paragraph/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/elements/paragraph/info.mdx @@ -10,24 +10,12 @@ import { P } from '@dnb/eufemia/elements' ## Description -Paragraphs are a block-level elements, used to structure and format text contents. +Paragraphs are block-level elements, used to structure and format text contents. -## Paragraph class modifiers +Paragraph has some default typography styling even without any props being set. -Eufemia comes with several styles you can use on paragraphs and other HTML text elements: - -**Weights** - -- `.dnb-p` (Body text) -- `.dnb-p--medium` - -**Sizes** - -- `.dnb-p--small` -- `.dnb-p--x-small` - -**Variants** +Read more [about Fonts in the Designer Guides](/quickguide-designer/fonts/). -- `.dnb-p--lead` +### Typography CSS classes -Read more [about Fonts in the Designer Guides](/quickguide-designer/fonts/). +Both Paragraph and the [Span](uilib/elements/span/) component have the same typography props that uses the [typography helper classes](uilib/typography/helper-classes/). diff --git a/packages/dnb-design-system-portal/src/docs/uilib/elements/span.mdx b/packages/dnb-design-system-portal/src/docs/uilib/elements/span.mdx new file mode 100644 index 00000000000..96150b716e1 --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/elements/span.mdx @@ -0,0 +1,13 @@ +--- +title: 'Span' +theme: 'sbanken' +showTabs: true +hideTabs: + - title: Events +--- + +import SpanInfo from 'Docs/uilib/elements/span/info' +import SpanDemos from 'Docs/uilib/elements/span/demos' + + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/elements/span/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/elements/span/Examples.tsx new file mode 100644 index 00000000000..31d3b1e3f98 --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/elements/span/Examples.tsx @@ -0,0 +1,77 @@ +/** + * UI lib Component Example + * + */ + +import React from 'react' +import ComponentBox from '../../../../shared/tags/ComponentBox' +import Anchor from '@dnb/eufemia/src/components/Anchor' +import { Span, P, H4 } from '@dnb/eufemia/src/elements' + +export function SpanBasic() { + return ( + +

+ Here is a paragraph with a x-small word + and some medium weight text in it. +

+

+ Heading 4 with x-large word +

+ + + Anchor with medium weight words + +
+ ) +} + +export function SpanModifiers() { + return ( + +
+ Default span +
+ Medium weight span +
+ Basis size span +
+ + X-small span with medium weight + +
+
+ ) +} + +export function SpanRegressionTests() { + const SpanWrap = (props) => { + const size = props.size || 'default' + return ( +
+ {size} +
+ + {size} - Weight medium + +
+ + {size} - Weight bold + +
+ ) + } + + return ( + + + + + + + + + + + ) +} diff --git a/packages/dnb-design-system-portal/src/docs/uilib/elements/span/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/elements/span/demos.mdx new file mode 100644 index 00000000000..b7c34c9b398 --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/elements/span/demos.mdx @@ -0,0 +1,25 @@ +--- +showTabs: true +--- + +import { + SpanBasic, + SpanRegressionTests, + SpanModifiers, +} from 'Docs/uilib/elements/span/Examples' + +## Demos + +For more detailed examples of every prop, see the [Paragraph demos](uilib/elements/paragraph/#demos). + +### Basics + + + +### Span modifiers + + + + + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/elements/span/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/elements/span/info.mdx new file mode 100644 index 00000000000..0532948b1dc --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/elements/span/info.mdx @@ -0,0 +1,19 @@ +--- +showTabs: true +--- + +## Import + +```tsx +import { Span } from '@dnb/eufemia/elements' +``` + +## Description + +Spans are inline-elements, used to define parts of text content. + +Span does not define any default styling, if no props are set, it will just be a regular inline `` element. + +### Typography CSS classes + +Both Span and the [Paragraph](uilib/elements/paragraph/) component have the same typography props that uses the [typography helper classes](uilib/typography/helper-classes/). diff --git a/packages/dnb-design-system-portal/src/docs/uilib/elements/span/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/elements/span/properties.mdx new file mode 100644 index 00000000000..fba727d761b --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/elements/span/properties.mdx @@ -0,0 +1,10 @@ +--- +showTabs: true +--- + +import PropertiesTable from 'dnb-design-system-portal/src/shared/parts/PropertiesTable' +import { SpanProperties } from '@dnb/eufemia/src/elements/span/SpanDocs' + +## Properties + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/DataContext/Provider/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/DataContext/Provider/Examples.tsx index dd9eaefe695..20e00c36cbc 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/DataContext/Provider/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/DataContext/Provider/Examples.tsx @@ -6,7 +6,7 @@ import { Value, JSONSchema, } from '@dnb/eufemia/src/extensions/forms' -import { Card, Flex } from '@dnb/eufemia/src' +import { Flex } from '@dnb/eufemia/src' export const TestdataSchema: JSONSchema = { type: 'object', @@ -102,7 +102,7 @@ export const Default = () => { sessionStorageId="provider-example-1" > - + { - +
@@ -192,7 +192,7 @@ export const ValidationWithJsonSchema = () => { onSubmitRequest={() => console.log('onSubmitRequest')} > - + { - + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Examples.tsx index 6e8b3ff8249..7809aa84605 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Examples.tsx @@ -1,6 +1,6 @@ import React from 'react' import ComponentBox from '../../../../shared/tags/ComponentBox' -import { Input, Slider, Card, Flex, NumberFormat } from '@dnb/eufemia/src' +import { Input, Slider, Flex, NumberFormat } from '@dnb/eufemia/src' import { Form, Field, @@ -46,7 +46,7 @@ export const CreateBasicFieldComponent = () => { const preparedProps = { label: 'What is the secret of this field?', fromInput, - validator: (value) => { + onChangeValidator: (value) => { if (value === 'secret') { return new Error('Do not reveal the secret!') } @@ -110,7 +110,7 @@ export const GettingStarted = () => { > Bedrift - + { /> - + @@ -229,7 +229,7 @@ export const BaseFieldComponents = () => { Value, }} > - + { value={true} onChange={(value) => console.log('onChange', value)} /> - + ) } @@ -257,13 +257,13 @@ export const FeatureFields = () => { Value, }} > - + - + ) } @@ -278,20 +278,20 @@ export const LayoutComponents = () => { Profile - + Name - + - + More information - + ) @@ -322,12 +322,12 @@ export const VisibilityBasedOnData = () => { Profile - + Name - + { /> - + More information - + @@ -374,7 +374,7 @@ export const UsingFormHandler = () => { > Profile - + @@ -384,7 +384,7 @@ export const UsingFormHandler = () => { - + ) @@ -413,13 +413,13 @@ export const Validation = () => { > Profile - + - + ) @@ -454,7 +454,7 @@ export const UsingWizard = () => { Profile - + Name { label="Etternavn" required /> - + @@ -475,13 +475,13 @@ export const UsingWizard = () => { Profile - + More information - + @@ -489,7 +489,7 @@ export const UsingWizard = () => { Profile - + @@ -498,7 +498,7 @@ export const UsingWizard = () => { - + @@ -550,13 +550,13 @@ export const UsingFormSection = () => { }, }} > - + Your account - + ) @@ -641,14 +641,14 @@ export const UsingIterate = () => { Accounts - + - + @@ -676,7 +676,7 @@ export const Transformers = () => { return ( - + { /> - + ) } @@ -710,7 +710,7 @@ export const QuickStart = () => { onChange={console.log} onSubmit={console.log} > - + { required /> - + ) } diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/ButtonRow/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/ButtonRow/Examples.tsx index eda154f5c04..7e82019924c 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/ButtonRow/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/ButtonRow/Examples.tsx @@ -1,6 +1,6 @@ import ComponentBox from '../../../../../../shared/tags/ComponentBox' import { Form, Field, Wizard } from '@dnb/eufemia/src/extensions/forms' -import { Button, Card } from '@dnb/eufemia/src' +import { Button } from '@dnb/eufemia/src' import { send as sendIcon } from '@dnb/eufemia/src/icons' export const Default = () => { @@ -18,13 +18,13 @@ export const WithLayout = () => { return ( console.log('onSubmit', data)}> - + - + ) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card.mdx new file mode 100644 index 00000000000..c6b15764c1b --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card.mdx @@ -0,0 +1,26 @@ +--- +title: 'Card' +description: '`Form.Card` is a wrapper for the Card component to make it easier to use inside a form.' +order: 1 +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: Card + href: /uilib/extensions/forms/Form/Card/ +--- + +import Info from './Card/info.mdx' +import Demos from './Card/demos.mdx' + + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card/Examples.tsx new file mode 100644 index 00000000000..cdef9b83088 --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card/Examples.tsx @@ -0,0 +1,21 @@ +import ComponentBox from '../../../../../../shared/tags/ComponentBox' +import { Flex, P } from '@dnb/eufemia/src' +import { Form, Field } from '@dnb/eufemia/src/extensions/forms' + +export const BasicUsage = () => { + return ( + + + Main heading + + + + +

Nested card

+
+
+ +
+
+ ) +} diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card/demos.mdx new file mode 100644 index 00000000000..c02baef433b --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card/demos.mdx @@ -0,0 +1,10 @@ +--- +showTabs: true +hideInMenu: true +--- + +import * as Examples from './Examples' + +## Demos + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card/info.mdx new file mode 100644 index 00000000000..85cd5152f81 --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card/info.mdx @@ -0,0 +1,11 @@ +--- +showTabs: true +hideInMenu: true +--- + +## Description + +`Form.Card` is a wrapper for the [Card](/uilib/components/card/) component to make it easier to use inside a form. + +- It will set `outset` to `true` by default. +- It will set `stack` to `true` by default. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card/properties.mdx new file mode 100644 index 00000000000..0fe97c298d6 --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Card/properties.mdx @@ -0,0 +1,12 @@ +--- +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 { FormCardProperties } from '@dnb/eufemia/src/extensions/forms/Form/Card/CardDocs' + +## Properties + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Handler/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Handler/Examples.tsx index bcec3cdca64..bd12e44ed13 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Handler/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Handler/Examples.tsx @@ -7,7 +7,7 @@ import { Tools, } from '@dnb/eufemia/src/extensions/forms' import { stop as stopIcon } from '@dnb/eufemia/src/icons' -import { Button, Card, Flex, P } from '@dnb/eufemia/src' +import { Button, Flex, P } from '@dnb/eufemia/src' import { debounceAsync } from '@dnb/eufemia/src/shared/helpers/debounce' import { createRequest } from '../SubmitIndicator/Examples' @@ -15,7 +15,7 @@ export const RequiredAndOptionalFields = () => { return ( - + { /> - + ) @@ -37,12 +37,12 @@ export const AsyncSubmit = () => { console.log('onSubmit', data)} > - + - + ) @@ -73,9 +73,9 @@ export const AsyncSubmitComplete = () => { > Heading - + - + @@ -157,7 +157,7 @@ export const AsyncChangeAndValidation = () => { label='Type "valid" to validate the field' path="/myField" required - validator={validator} + onChangeValidator={validator} onChange={onChangeField} autoComplete="off" /> @@ -196,13 +196,13 @@ export const SessionStorage = () => { }} sessionStorageId="session-key" > - + - + ) @@ -218,14 +218,14 @@ export const Autofill = () => { Delivery address - + Your name - + - + Your address @@ -247,14 +247,14 @@ export const Autofill = () => { postalCode={{ required: true, path: '/postalCode' }} city={{ required: true, path: '/city' }} /> - + - +

More information about this form.

-
+
@@ -281,7 +281,7 @@ export const Locale = () => { locale={data?.locale} translations={myTranslations} > - + { Norsk English - + ) } diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Isolation/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Isolation/Examples.tsx index 9f5371c66e2..f4f0547bb9b 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Isolation/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Isolation/Examples.tsx @@ -1,5 +1,5 @@ import ComponentBox from '../../../../../../shared/tags/ComponentBox' -import { Card, Flex, HeightAnimation } from '@dnb/eufemia/src' +import { Flex, HeightAnimation } from '@dnb/eufemia/src' import { Field, Form, Tools } from '@dnb/eufemia/src/extensions/forms' import React from 'react' @@ -53,7 +53,7 @@ export const CommitHandleRef = () => { contactPersons: [{ title: 'Hanne', value: 'hanne' }], }} > - + Ny hovedkontaktperson @@ -89,7 +89,7 @@ export const CommitHandleRef = () => {
- +
- + ) }} @@ -55,12 +55,12 @@ export const AsyncChangeBehavior = () => { return ( - + { - + ) }} diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/Examples.tsx index 480d8b08450..90e05ecb254 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Visibility/Examples.tsx @@ -1,6 +1,6 @@ import React from 'react' import ComponentBox from '../../../../../../shared/tags/ComponentBox' -import { Card, Flex, HeightAnimation, P } from '@dnb/eufemia/src' +import { Flex, HeightAnimation, P } from '@dnb/eufemia/src' import { Field, Form, @@ -239,11 +239,11 @@ export const FilterData = () => { filterData={filterDataPaths} animate > - +

Result:

-
+
@@ -268,7 +268,7 @@ export function InheritVisibility() { return ( - + - + ) @@ -298,7 +298,7 @@ export function VisibilityOnValidation() { return ( - + - + ) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/schema-validation/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/schema-validation/Examples.tsx index b22ae543cf1..e0824a922da 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/schema-validation/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/schema-validation/Examples.tsx @@ -1,5 +1,5 @@ import ComponentBox from '../../../../../../shared/tags/ComponentBox' -import { Card, Flex } from '@dnb/eufemia/src' +import { Flex } from '@dnb/eufemia/src' import { Form, Field, Iterate } from '@dnb/eufemia/src/extensions/forms' import { trash as TrashIcon } from '@dnb/eufemia/src/icons' @@ -26,12 +26,12 @@ export const DataSetSchema = () => { required: ['name', 'address'], }} > - + Company information - + @@ -62,7 +62,7 @@ export const IfRuleSchema = () => { else: { required: ['name'] }, }} > - + Customer information @@ -74,7 +74,7 @@ export const IfRuleSchema = () => { path="/companyName" labelDescription="Company name (required for corporate customers)" /> - + @@ -149,14 +149,14 @@ export const DependantListSchema = () => { > Customer information - + - + Accounts - + Standard accounts @@ -189,7 +189,7 @@ export const DependantListSchema = () => { label="Account number" /> - + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useData/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useData/info.mdx index eba8a84dc16..34c96df04a6 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useData/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useData/info.mdx @@ -265,6 +265,6 @@ function ComponentB() { **tl;dr:** the `useData` hook returns unvalidated data. -When you use an async `onChange` or `validator` event handler on a field, it will delay the "submitted" value, because of its async nature. +When you use an async `onChange`, `onChangeValidator` or `onBlurValidator` event handler on a field, it will delay the "submitted" value, because of its async nature. That means, if you want to access the value of a field immediately, you can use the `useData` hook for that, as it always returns unvalidated data, in sync. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot/Examples.tsx index 7162b4c0f6a..c764ab8db4e 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/useSnapshot/Examples.tsx @@ -1,5 +1,5 @@ import React from 'react' -import { Button, Card } from '@dnb/eufemia/src' +import { Button } from '@dnb/eufemia/src' import ComponentBox from '../../../../../../shared/tags/ComponentBox' import { Field, @@ -81,7 +81,7 @@ export const UndoRedo = () => { return ( <> - + { /> - + - + ) } diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/EditButton/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/EditButton/info.mdx index efafdf5dc4c..fcef0a4dfd7 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/EditButton/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/EditButton/info.mdx @@ -16,11 +16,11 @@ render( - +
-
+
, diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Examples.tsx index 01d092bf1b0..1b78b9c38d5 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Examples.tsx @@ -1,7 +1,7 @@ import React from 'react' import ComponentBox from '../../../../../shared/tags/ComponentBox' import { Form, Wizard } from '@dnb/eufemia/src/extensions/forms' -import { Card, P } from '@dnb/eufemia/src' +import { P } from '@dnb/eufemia/src' export const IntroExample = () => { return ( @@ -20,16 +20,16 @@ export const IntroExample = () => { > Heading - +

Step 1

-
+
Heading - +

Step 2

-
+
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/Examples.tsx index 1ae62f30fc2..0b3521bb535 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/Examples.tsx @@ -1,4 +1,4 @@ -import { Card, P } from '@dnb/eufemia/src' +import { P } from '@dnb/eufemia/src' import ComponentBox from '../../../../../../shared/tags/ComponentBox' import { Field, @@ -78,9 +78,9 @@ export const EditButton = () => { const Step = ({ title }) => { return ( - +

Contents

-
+
@@ -92,13 +92,13 @@ export const EditButton = () => { return ( - + - + ) } diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/info.mdx index 915005f7a1b..5ecd086fd4e 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Step/info.mdx @@ -13,12 +13,12 @@ const Step1 = () => { return ( Heading - +

Contents

-
- + +

Contents

-
+
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/location-hooks/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/location-hooks/Examples.tsx index 3a9a3b24027..f5f50849359 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/location-hooks/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/location-hooks/Examples.tsx @@ -1,6 +1,6 @@ import ComponentBox from '../../../../../../shared/tags/ComponentBox' import { navigate, useLocation } from '@reach/router' -import { Card, P } from '@dnb/eufemia/src' +import { P } from '@dnb/eufemia/src' import { Form, Wizard } from '@dnb/eufemia/src/extensions/forms' export const Default = () => { @@ -24,9 +24,9 @@ export const Default = () => { const MyStep = ({ title }) => { return ( - +

Contents of {title}

-
+
) @@ -62,9 +62,9 @@ export const ReachRouter = () => { const MyStep = ({ title }) => { return ( - +

Contents of {title}

-
+
) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/ArraySelection/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/ArraySelection/Examples.tsx index 31235d5a664..9f9e815f5c1 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/ArraySelection/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/ArraySelection/Examples.tsx @@ -1,4 +1,4 @@ -import { Card, Section } from '@dnb/eufemia/src' +import { Flex, Section } from '@dnb/eufemia/src' import ComponentBox from '../../../../../../shared/tags/ComponentBox' import { Field, Form } from '@dnb/eufemia/src/extensions/forms' @@ -112,6 +112,21 @@ export const CheckboxDisabled = () => ( ) +export const CheckboxDisabledOptions = () => ( + + console.log('onChange', value)} + > + + + + + + +) + export const CheckboxInfo = () => ( ( data-visual-test="array-selection-checkbox-nesting-logic" > - - - - - - { - return Array.isArray(value) - ? value.includes('showInput') - : false - }, - }} - animate - compensateForGap="auto" // makes animation smooth + + + -
- -
-
+ - - { - return Array.isArray(value) - ? value.includes('showAdditionalOption') - : false - }, - }} - animate - compensateForGap="auto" // makes animation smooth - > - + { return Array.isArray(value) - ? value.includes('showMeMore') + ? value.includes('showInput') : false }, }} + animate + compensateForGap="auto" // makes animation smooth >
- +
-
-
-
- + + { + return Array.isArray(value) + ? value.includes('showAdditionalOption') + : false + }, + }} + animate + compensateForGap="auto" // makes animation smooth + > + + { + return Array.isArray(value) + ? value.includes('showMeMore') + : false + }, + }} + > +
+ +
+
+
+
+ + + +
) @@ -431,6 +448,22 @@ export const ButtonDisabled = () => ( ) +export const ButtonDisabledOptions = () => ( + + console.log('onChange', value)} + > + + + + + + +) + export const ButtonInfo = () => ( ( export const ButtonNestingWithLogic = () => ( - + ( - + ) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/ArraySelection/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/ArraySelection/demos.mdx index 77d5c266a57..a2fbfe60259 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/ArraySelection/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/ArraySelection/demos.mdx @@ -50,6 +50,10 @@ import * as Examples from './Examples' +#### Checkbox disabled options + + + #### Checkbox info @@ -108,6 +112,10 @@ You can nest other fields and show them based on your desired logic. +#### Button disabled options + + + #### Button info diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/ArraySelection/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/ArraySelection/info.mdx index acdfe6e543d..85cf5d7e925 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/ArraySelection/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/ArraySelection/info.mdx @@ -8,7 +8,7 @@ showTabs: true There is a corresponding [Value.ArraySelection](/uilib/extensions/forms/Value/ArraySelection) component. -The [Field.Option](/uilib/extensions/forms/base-fields/Option/) is a related component. +Uses the [Field.Option](/uilib/extensions/forms/base-fields/Option/) pseudo-component to define options. ```jsx import { Field } from '@dnb/eufemia/extensions/forms' diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Indeterminate.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Indeterminate.mdx index 3fb815e6d6c..28ffa8dedc7 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Indeterminate.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Indeterminate.mdx @@ -1,7 +1,7 @@ --- title: 'Indeterminate' description: 'The `Field.Indeterminate` component is used to display and handle the indeterminate state of a checkbox.' -componentType: 'primitive' +componentType: 'base-toggle' hideInMenu: true showTabs: true theme: 'sbanken' diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Indeterminate/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Indeterminate/Examples.tsx index 97972da09e8..e279565538c 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Indeterminate/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Indeterminate/Examples.tsx @@ -1,4 +1,4 @@ -import { Card, Flex } from '@dnb/eufemia/src' +import { Flex } from '@dnb/eufemia/src' import ComponentBox from '../../../../../../shared/tags/ComponentBox' import { Field, Form } from '@dnb/eufemia/src/extensions/forms' @@ -6,7 +6,7 @@ export const MixedIndeterminateDependence = () => { return ( - + { valueOn="on" valueOff="off" /> - + @@ -44,7 +44,7 @@ export const PropagateIndeterminateDependence = () => { const { data } = Form.useData() return ( <> - + Checked Unchecked @@ -72,7 +72,7 @@ export const PropagateIndeterminateDependence = () => { valueOn="on" valueOff="off" /> - + ) } @@ -104,7 +104,7 @@ export const NestedIndeterminateDependence = () => { return ( - + { /> - + ) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Number/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Number/Examples.tsx index 116d955d18f..f52023fe382 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Number/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Number/Examples.tsx @@ -1,5 +1,5 @@ import ComponentBox from '../../../../../../shared/tags/ComponentBox' -import { Slider, Grid, Flex, Card } from '@dnb/eufemia/src' +import { Slider, Grid, Flex } from '@dnb/eufemia/src' import { Field, Form } from '@dnb/eufemia/src/extensions/forms' import React from 'react' @@ -30,7 +30,7 @@ export const LabelAndValue = () => { export const LabelAndDescription = () => { return ( - + { labelDescription="\nDescription text with new line using \\n" placeholder="Enter a text..." /> - + ) } @@ -49,7 +49,7 @@ export const LabelAndDescription = () => { export const WithStatus = () => { return ( - + { warning="Aliqua eu aute id qui esse aliqua dolor in aute magna commodo anim enim et. Velit incididunt exercitation est magna ex irure dolore nisi eiusmod ea exercitation." required /> - + ) } @@ -79,7 +79,7 @@ export const WithStatus = () => { export const HorizontalLayout = () => { return ( - + { width="stretch" /> - + ) } diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Option.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Option.mdx index e84d4456511..ee9434ae05f 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Option.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Option.mdx @@ -1,6 +1,6 @@ --- title: 'Option' -description: '`Field.Option` is a wrapper component for selecting an option to be used in a dropdown or similar user experiences.' +description: '`Field.Option` is a pseudo-component for defining an option to be used in a dropdown or similar user experiences.' componentType: 'base-selection' hideInMenu: true showTabs: true diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Option/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Option/info.mdx index c0f5c0b6dd6..c0f94174e84 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Option/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Option/info.mdx @@ -4,7 +4,12 @@ showTabs: true ## Description -`Field.Option` is a part for building selection inputs with Field.Select. +`Field.Option` is a pseudo-component that is used by parent components to configure options. It has no use on it's own. How it renders, and what extra props it accepts, are defined by the parent. + +### Used in: + +- [Field.ArraySelection](/uilib/extensions/forms/base-fields/ArraySelection/) +- [Field.Selection](/uilib/extensions/forms/base-fields/Selection/) ```tsx import { Field } from '@dnb/eufemia/extensions/forms' diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Option/properties.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Option/properties.mdx index f32b1919b91..db9c2bd863a 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Option/properties.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Option/properties.mdx @@ -7,4 +7,6 @@ import { OptionProperties } from '@dnb/eufemia/src/extensions/forms/Field/Option ## Properties +There might be more props available depending on the parent component. For example, `` also allows the `icon` prop, among others, for its options. + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/Examples.tsx index 9f3614c0e20..ea22882c274 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/Examples.tsx @@ -1,6 +1,6 @@ import * as React from 'react' import ComponentBox from '../../../../../../shared/tags/ComponentBox' -import { Button, Card, Flex, Section } from '@dnb/eufemia/src' +import { Button, Flex, Section } from '@dnb/eufemia/src' import { Field, Form } from '@dnb/eufemia/src/extensions/forms' // - Dropdown @@ -149,6 +149,23 @@ export const DropdownDisabled = () => ( ) +export const DropdownDisabledOptions = () => ( + + {() => { + const Example = () => { + return ( + + + + + ) + } + + return + }} + +) + export const DropdownError = () => ( ( ) +export const RadioDisabledOptions = () => ( + + {() => { + const Example = () => { + return ( + + + + + ) + } + + return + }} + +) + export const RadioError = () => ( ( export const RadioNestingWithLogic = () => ( - + ( - + @@ -550,7 +584,7 @@ export const RadioNestingAdvanced = () => ( defaultData={{ mySelection: 'first', firstSelection: 'first' }} onSubmit={console.log} > - + ( animate compensateForGap="auto" // makes animation smooth > - + ( animate compensateForGap="auto" // makes animation smooth > - + - + - + - + @@ -689,6 +723,23 @@ export const ButtonDisabled = () => ( ) +export const ButtonDisabledOptions = () => ( + + {() => { + const Example = () => { + return ( + + + + + ) + } + + return + }} + +) + export const ButtonError = () => ( ( export const ButtonNestingWithLogic = () => ( - + ( - + ) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/demos.mdx index 3bc5bff81ce..8b7dafe76d2 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/demos.mdx @@ -58,6 +58,10 @@ As there are many variants, they are split into separate sections. Here is a sum +#### Dropdown option disabled + + + #### Dropdown error @@ -120,6 +124,10 @@ As there are many variants, they are split into separate sections. Here is a sum +#### Radio option disabled + + + #### Radio error @@ -162,6 +170,10 @@ You can nest other fields and show them based on your desired logic. +#### ToggleButton option disabled + + + #### ToggleButton error diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/info.mdx index 67d12b390b8..a73a716a43c 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/Selection/info.mdx @@ -8,7 +8,7 @@ showTabs: true There is a corresponding [Value.Selection](/uilib/extensions/forms/Value/Selection) component. -The [Field.Option](/uilib/extensions/forms/base-fields/Option/) is a related component. +Uses the [Field.Option](/uilib/extensions/forms/base-fields/Option/) pseudo-component to define options. ```tsx import { Field } from '@dnb/eufemia/extensions/forms' diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/Examples.tsx index 44270be7695..a0859b76e88 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/Examples.tsx @@ -1,5 +1,5 @@ import ComponentBox from '../../../../../../shared/tags/ComponentBox' -import { Card, Flex } from '@dnb/eufemia/src' +import { Flex } from '@dnb/eufemia/src' import { Field, Form, @@ -34,7 +34,7 @@ export const LabelAndValue = () => { export const LabelAndDescription = () => { return ( - + { labelDescription="\nDescription text with new line using \\n" placeholder="Enter a text..." /> - + ) } @@ -53,7 +53,7 @@ export const LabelAndDescription = () => { export const WithStatus = () => { return ( - + { warning={['Warning message A', 'Warning message B']} info={['Info message A', 'Info message B']} /> - + ) } @@ -89,7 +89,7 @@ export const WithStatus = () => { export const HorizontalLayout = () => { return ( - + { width="stretch" /> - + ) } @@ -204,7 +204,7 @@ export const Widths = () => { export const Icons = () => { return ( - + { rightIcon="loupe" onChange={(value) => console.log('onChange', value)} /> - + ) } @@ -316,13 +316,13 @@ export const ValidatePattern = () => { ) } -export const SynchronousExternalValidator = () => { +export const SynchronousExternalChangeValidator = () => { return ( + onChangeValidator={(value) => value.length < 4 ? Error('At least 4 characters') : undefined } onChange={(value) => console.log('onChange', value)} @@ -331,13 +331,13 @@ export const SynchronousExternalValidator = () => { ) } -export const AsynchronousExternalValidator = () => { +export const AsynchronousExternalChangeValidator = () => { return ( + onChangeValidator={(value) => new Promise((resolve) => setTimeout( () => @@ -476,7 +476,7 @@ export function TransformInAndOut() { const MyForm = () => { return ( - + Data Context - + ) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/demos.mdx index 8694985c37e..5497f8c66b1 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/demos.mdx @@ -74,11 +74,11 @@ This example demonstrates how the status message width adjusts according to the ### Synchronous external validator (called on every change) - + ### Asynchronous external validator (called on every change) - + ### Synchronous external validator (called on blur) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/blocks.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/blocks.mdx index 63125958ee7..0b61f2b4dc7 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/blocks.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/blocks.mdx @@ -1,7 +1,6 @@ --- title: 'Blocks' order: 20 -status: 'beta' breadcrumb: - text: Forms href: /uilib/extensions/forms/ diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/blocks/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/blocks/info.mdx index 85576ddfea6..aa2561e40b0 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/blocks/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/blocks/info.mdx @@ -13,8 +13,6 @@ Blocks are a collection of reusable fields and values. They can also be 100% cus Read about how to create a section (block) by using a [Form.Section](/uilib/extensions/forms/Form/Section/). -**Note:** If you are interested in using blocks, please get in touch with us. - ## Usage You import a block like this: diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/changelog.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/changelog.mdx index 8deb3b20aac..7e88677912b 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/changelog.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/changelog.mdx @@ -13,6 +13,19 @@ breadcrumb: Change log for the Eufemia Forms extension. +## v10.57 + +- Added possibility for disabling individual options in [Field.Selection](/uilib/extensions/forms/base-fields/Selection/) and [Field.ArraySelection](/uilib/extensions/forms/base-fields/ArraySelection/). +- Added `labelSrOnly` to Value.\* components, to be able to provide a label that is not visible. +- Added [Form.Card](/uilib/extensions/forms/Form/Card/) component to make it easier to use [Card](/uilib/components/card/) inside a form. +- Added `outset` property to [Form.Card](/uilib/extensions/forms/Form/Card/) and [Card](/uilib/components/card/). +- Deprecated `validator` property in favor of `onChangeValidator` in Field.\* components. +- Renamed `asyncFileHandler` to `fileHandler` in [Field.Upload](/uilib/extensions/forms/feature-fields/more-fields/Upload/), to support both async and sync file handling. +- Fixed displaying indicator with async `onBlurValidator` call when `validateInitially` is used. +- Fixed sharing submit indicator for fields inside [Field.Composition](/uilib/extensions/forms/base-fields/Composition/). +- Fixed so `errorMessages` won't result in infinite loops when not wrapped in `useMemo`. +- Fixed alignment issue in [Value.SummaryList](/uilib/extensions/forms/Value/SummaryList/), when providing a field without label. + ## v10.56 - Added inline help button (`help`) to all `Field.*` components as default (with option to open in Dialog). @@ -57,7 +70,7 @@ Change log for the Eufemia Forms extension. ## v10.53 -- Added validation of Norwegian bankaccount numbers to [Field.BankAccountNumber](/uilib/extensions/forms/feature-fields/BankAccountNumber/). +- Added validation of Norwegian bank account numbers to [Field.BankAccountNumber](/uilib/extensions/forms/feature-fields/BankAccountNumber/). - Added [Form.useTranslation](/uilib/extensions/forms/Form/useTranslation/) that returns the translations for the current locale. - Added `renderMessage` function in [Form.useTranslation](/uilib/extensions/forms/Form/useTranslation/) to render a string with line-breaks. - Added console warning when a field path is declared more than one time. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/Examples.tsx index 8e88a94beef..bdc7c8c9143 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/Examples.tsx @@ -5,7 +5,7 @@ import { TestElement, Form, } from '@dnb/eufemia/src/extensions/forms' -import { Anchor, Card, Flex, Slider } from '@dnb/eufemia/src' +import { Anchor, Flex, Slider } from '@dnb/eufemia/src' export const Default = () => { return ( @@ -325,7 +325,7 @@ export const InlineHelpButtonHTML = () => { export const InlineHelpButtonVerticalLabelDescription = () => { return ( - + { content: 'Dette er hvor mye du har tenkt å låne totalt.', }} /> - + ) } @@ -356,7 +356,7 @@ export const InlineHelpButtonVerticalLabelDescription = () => { export const InlineHelpButtonHorizontalLabel = () => { return ( - + { }} info="Info message" /> - + ) } @@ -400,7 +400,7 @@ export const InlineHelpButtonHorizontalLabel = () => { export const InlineHelpButtonCompositionFields = () => { return ( - + { content: 'Dette er hvor mye du har tenkt å låne totalt.', }} /> - + ) } diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/info.mdx index 3f1bda8fe4e..1841ff70c8d 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/FieldBlock/info.mdx @@ -6,7 +6,7 @@ showTabs: true `FieldBlock` is a reusable wrapper [for building](/uilib/extensions/forms/create-component/) interactive [Field](/uilib/extensions/forms/feature-fields) components. -It shows surrounding elements through properties from `FieldProps` like `label` and `error`, and ensure that spacing between different fields work as required when put into surrounding components like [Flex.Container](/uilib/layout/flex/container/) or [Card](/uilib/components/card/). +It shows surrounding elements through properties from `FieldProps` like `label` and `error`, and ensure that spacing between different fields work as required when put into surrounding components like [Flex.Container](/uilib/layout/flex/container/) or [Form.Card](/uilib/extensions/forms/Form/Card/). It can also be used to group multiple inner FieldBlock components, composing status (error) messages together as one component. Check out the [Field.Composition](/uilib/extensions/forms/base-fields/Composition/) docs for that. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/useFieldProps/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/useFieldProps/info.mdx index c96f6287005..d7991787276 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/useFieldProps/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/useFieldProps/info.mdx @@ -80,8 +80,8 @@ All properties are optional and can be used as needed. These properties can be p - `required` if true, it will call `validateRequired` for validation. - `schema` or `pattern` for JSON schema validation powered by [ajv](https://ajv.js.org/). -- `validator` your custom validation function. It will run on every keystroke. Can be an async function. Use it together with [debounceAsync](/uilib/helpers/functions/#debounce). -- `onBlurValidator` your custom validation function. It will run on a `handleBlur()` call. Use it over `validator` for validations with side-effects. Can be an async function. +- `onChangeValidator` your custom validation function. It will run on every keystroke. Can be an async function. Use it together with [debounceAsync](/uilib/helpers/functions/#debounce). +- `onBlurValidator` your custom validation function. It will run on a `handleBlur()` call. Use it over `onChangeValidator` for validations with side-effects. Can be an async function. - `validateRequired` does allow you to provide a custom logic for how the `required` property should validate. See example down below. - `validateInitially` in order to show an error without a change and blur event. Used for rare cases. - `validateUnchanged` in order to validate without a change and blur event. Used for rare cases. @@ -166,7 +166,8 @@ During validation, the different APIs do have a prioritization order and will st 1. `required` property 1. `schema` property (including `pattern`) -1. `validator` property +1. `onChangeValidator` property +1. `onBlurValidator` property ### Error handling diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/demo-cases/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/demo-cases/Examples.tsx index 37fd1d1e843..3b10e9018db 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/demo-cases/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/demo-cases/Examples.tsx @@ -1,5 +1,5 @@ import * as React from 'react' -import { Card, Flex } from '@dnb/eufemia/src' +import { Flex } from '@dnb/eufemia/src' import { Form, Field, @@ -35,7 +35,7 @@ export const BecomeCorporateCustomer = () => { Bedriftsopplysninger - + { label="Etableringsland" required /> - + - + { path="/website" label="Nettstedsadresse (valgfritt)" /> - + @@ -106,13 +106,13 @@ export const BecomeCorporateCustomer = () => { Profile - + More information - + @@ -140,7 +140,7 @@ export const BecomeCorporateCustomer = () => { Profile - + @@ -149,7 +149,7 @@ export const BecomeCorporateCustomer = () => { - + @@ -195,7 +195,7 @@ export function PizzaDemo() { Which pizza do you want? - + Your Pizza - + - + Allergies - + @@ -239,13 +239,13 @@ export function PizzaDemo() { Delivery address - + Your name - + - + Your address @@ -270,7 +270,7 @@ export function PizzaDemo() { }} city={{ required: true, path: '/city' }} /> - + @@ -278,7 +278,7 @@ export function PizzaDemo() { Summary - + - + - + @@ -306,7 +306,7 @@ export function PizzaDemo() { /> - + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/BankAccountNumber/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/BankAccountNumber/info.mdx index 439e589a957..e285bd6e2e8 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/BankAccountNumber/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/BankAccountNumber/info.mdx @@ -20,5 +20,5 @@ There is a corresponding [Value.BankAccountNumber](/uilib/extensions/forms/Value ### Internal validators exposed -`Field.BankAccountNumber` expose the `bankAccountNumberValidator` validator through its `validator` and `onBlurValidator` property, take a look at [this demo](/uilib/extensions/forms/feature-fields/BankAccountNumber/demos/#extend-validation-with-custom-validation-function). -The `bankAccountNumberValidator` validator, validates if the bankaccount number provided is a [Norwegian bankaccount number](https://no.wikipedia.org/wiki/Kontonummer) or not. +`Field.BankAccountNumber` expose the `bankAccountNumberValidator` validator through its `onChangeValidator` and `onBlurValidator` property, take a look at [this demo](/uilib/extensions/forms/feature-fields/BankAccountNumber/demos/#extend-validation-with-custom-validation-function). +The `bankAccountNumberValidator` validator, validates if the bank account number provided is a [Norwegian bank account number](https://no.wikipedia.org/wiki/Kontonummer) or not. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/Examples.tsx index d69621c62d7..b41277a184c 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/Examples.tsx @@ -90,3 +90,11 @@ export const Range = () => { ) } + +export const AutoClose = () => { + return ( + + + + ) +} diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/demos.mdx index 77f7380dc9a..058224450f0 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/Date/demos.mdx @@ -18,6 +18,14 @@ import * as Examples from './Examples' +### Automatically close picker + +The calendar will be prevented from automatically closing when the submit or cancel buttons are visible, to ensure that the user is actually able to interact with them after date selection. + +To enable the picker to close automatically, you have to set `showCancelButton` to `false`, to override the default behavior. + + + ### With help diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/demos.mdx index 446131cc0b7..844530b2ac5 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/demos.mdx @@ -60,7 +60,7 @@ Below is an example of the error message displayed when there's an invalid D num ### Validation function -You can provide your own validation function, either to `validator` or `onBlurValidator`. +You can provide your own validation function, either to `onChangeValidator` or `onBlurValidator`. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/info.mdx index b3beafc20cf..3af48203034 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/info.mdx @@ -23,7 +23,7 @@ There is a corresponding [Value.NationalIdentityNumber](/uilib/extensions/forms/ ### Internal validators exposed -`Field.NationalIdentityNumber` expose the following validators through its `validator` and `onBlurValidator` property: +`Field.NationalIdentityNumber` expose the following validators through its `onChangeValidator` and `onBlurValidator` property: - `dnrValidator`: validates a D number. - `fnrValidator`: validates a national identity number (fødselsnummer). diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/OrganizationNumber/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/OrganizationNumber/info.mdx index 64cfad6512a..a6eedb8e83e 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/OrganizationNumber/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/OrganizationNumber/info.mdx @@ -21,5 +21,5 @@ There is a corresponding [Value.OrganizationNumber](/uilib/extensions/forms/Valu ### Internal validators exposed -`Field.OrganizationNumber` expose the `organizationNumberValidator` validator through its `validator` and `onBlurValidator` property, take a look at [this demo](/uilib/extensions/forms/feature-fields/OrganizationNumber/demos/#extend-validation-with-custom-validation-function). +`Field.OrganizationNumber` expose the `organizationNumberValidator` validator through its `onChangeValidator` and `onBlurValidator` property, take a look at [this demo](/uilib/extensions/forms/feature-fields/OrganizationNumber/demos/#extend-validation-with-custom-validation-function). The `organizationNumberValidator` validator, validates if the organization number provided is a [Norwegian organization number](https://www.brreg.no/om-oss/registrene-vare/om-enhetsregisteret/organisasjonsnummeret/) or not. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/Examples.tsx index d73ce8dfb45..ec3b420116c 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/Examples.tsx @@ -1,6 +1,5 @@ -import { Card } from '@dnb/eufemia/src' import ComponentBox from '../../../../../../shared/tags/ComponentBox' -import { Field } from '@dnb/eufemia/src/extensions/forms' +import { Field, Form } from '@dnb/eufemia/src/extensions/forms' export const Empty = () => { return ( @@ -168,9 +167,9 @@ export const LongLabel = () => { export const InCard = () => { return ( - + - + ) } diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/info.mdx index 4f84024a570..78ffab6ce97 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/info.mdx @@ -46,7 +46,7 @@ Countries are sorted in alphabetically order, with the following prioritized cou ## Validation -By default it has no validation. But you can enable it by giving a `required`, `pattern`, `schema` or `validator` property with the needed validation. More about validation in the [Getting Started](/uilib/extensions/forms/getting-started/#validation) section. +By default it has no validation. But you can enable it by giving a `required`, `pattern`, `schema`, `onChangeValidator` or `onBlurValidator` property with the needed validation. More about validation in the [Getting Started](/uilib/extensions/forms/getting-started/#validation) section. ### Norwegian mobile numbers diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PostalCodeAndCity/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PostalCodeAndCity/Examples.tsx index 25e8361f887..d98132a2d95 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PostalCodeAndCity/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PostalCodeAndCity/Examples.tsx @@ -1,4 +1,3 @@ -import { Card } from '@dnb/eufemia/src' import ComponentBox from '../../../../../../shared/tags/ComponentBox' import { Field, Form, Iterate } from '@dnb/eufemia/src/extensions/forms' @@ -178,10 +177,10 @@ export const SettingCountryBasedOnPath = () => { return ( - + - + ) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/SelectCountry/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/SelectCountry/Examples.tsx index 350d848539f..90baa8f6e12 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/SelectCountry/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/SelectCountry/Examples.tsx @@ -1,4 +1,3 @@ -import { Card } from '@dnb/eufemia/src' import ComponentBox from '../../../../../../shared/tags/ComponentBox' import { Field, @@ -114,7 +113,7 @@ export function TransformInAndOut() { const MyForm = () => { return ( - + Data Context - + ) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Password/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Password/info.mdx index 6cc6d4d8378..61301f8cc6b 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Password/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Password/info.mdx @@ -13,4 +13,4 @@ render() ## Validation -By default it has no validation. But you can enable it by giving a `required`, `pattern`, `schema` or `validator` property with the needed validation. More about validation in the [Getting Started](/uilib/extensions/forms/getting-started/#validation) section. +By default it has no validation. But you can enable it by giving a `required`, `pattern`, `schema`, `onChangeValidator` or `onBlurValidator` property with the needed validation. More about validation in the [Getting Started](/uilib/extensions/forms/getting-started/#validation) section. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Upload/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Upload/Examples.tsx index 805e4d682a3..aeb1e13a394 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Upload/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Upload/Examples.tsx @@ -1,11 +1,10 @@ import { Flex } from '@dnb/eufemia/src' import ComponentBox from '../../../../../../../shared/tags/ComponentBox' import { Field, Form, Tools } from '@dnb/eufemia/src/extensions/forms' -import { - createMockFile, - mockAsyncFileUpload, -} from '../../../../../../../docs/uilib/components/upload/Examples' +import { createMockFile } from '../../../../../../../docs/uilib/components/upload/Examples' import useUpload from '@dnb/eufemia/src/components/upload/useUpload' +import { UploadValue } from '@dnb/eufemia/src/extensions/forms/Field/Upload' +import { createRequest } from '../../../Form/SubmitIndicator/Examples' export const BasicUsage = () => { return ( @@ -85,7 +84,7 @@ export const WithPath = () => { export const WithAsyncFileHandler = () => { return ( - + {() => { const MyForm = () => { return ( @@ -95,7 +94,7 @@ export const WithAsyncFileHandler = () => { id="async_upload_context_id" path="/attachments" labelDescription="Upload multiple files at once to see the upload error message. This demo has been set up so that every other file in a batch will fail." - asyncFileHandler={mockAsyncFileUpload} + fileHandler={mockAsyncFileUpload} required /> @@ -105,6 +104,47 @@ export const WithAsyncFileHandler = () => { ) } + async function mockAsyncFileUpload( + newFiles: UploadValue, + ): Promise { + const updatedFiles: UploadValue = [] + + for (const [, file] of Object.entries(newFiles)) { + const formData = new FormData() + formData.append('file', file.file, file.file.name) + + const request = createRequest() + await request(Math.floor(Math.random() * 2000) + 1000) // Simulate a request + + try { + const mockResponse = { + ok: false, // Fails virus check + json: async () => ({ + server_generated_id: + file.file.name + '_' + crypto.randomUUID(), + }), + } + + if (!mockResponse.ok) { + throw new Error('Unable to upload this file') + } + + const data = await mockResponse.json() + updatedFiles.push({ + ...file, + id: data.server_generated_id, + }) + } catch (error) { + updatedFiles.push({ + ...file, + errorMessage: error.message, + }) + } + } + + return updatedFiles + } + const Output = () => { const { files } = useUpload('async_upload_context_id') return @@ -115,3 +155,44 @@ export const WithAsyncFileHandler = () => { ) } + +export const WithSyncFileHandler = () => { + return ( + + {() => { + const MyForm = () => { + return ( + console.log(form)}> + + + + + + + ) + } + + function mockSyncFileUpload(newFiles: UploadValue) { + return newFiles.map((file) => { + if (file.file.name.length > 5) { + file.errorMessage = 'File name is too long' + } + return file + }) + } + + const Output = () => { + const { files } = useUpload('sync_upload_context_id') + return + } + + return + }} + + ) +} diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Upload/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Upload/demos.mdx index d8cce0a083d..f7ce6eed8eb 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Upload/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Upload/demos.mdx @@ -28,4 +28,12 @@ import * as Examples from './Examples' ### With asynchronous file handler +The `fileHandler` property supports an asynchronous function, and can be used for handling/validating files asynchronously, like to upload files to a virus checker and display errors based on the outcome: + + +### With synchronous file handler + +The `fileHandler` property supports a synchronous function, and can be used for handling/validating files synchronously, like to check for file names that's too long: + + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Upload/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Upload/info.mdx index 2c5ac985757..f67dc9d6114 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Upload/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Upload/info.mdx @@ -59,9 +59,10 @@ The `value` property represents an array with an object described above: render() ``` -## About the `asyncFileHandler` property +## About the `fileHandler` property -The `asyncFileHandler` is an asynchronous handler function that takes newly added files as a parameter and returns a promise containing the processed files. The component will automatically handle loading states during the upload process. This feature is useful for tasks like uploading files to a virus checker, which returns a new file ID if the file passes the check. To indicate a failed upload, set the `errorMessage` on the specific file object with the desired message to display next to the file in the upload list. +The `fileHandler` is a handler function that supports both an asynchronous and synchronous function. It takes newly added files as a parameter and returns processed files (a promise when asynchronous). +The component will automatically handle asynchronous loading states during the upload process. This feature is useful for tasks like uploading files to a virus checker, which returns a new file ID if the file passes the check. To indicate a failed upload, set the `errorMessage` on the specific file object with the desired message to display next to the file in the upload list. ```js async function virusCheck(newFiles) { diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx index ec4929c6141..8378af5c301 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx @@ -42,7 +42,7 @@ import AsyncChangeExample from './Form/Handler/parts/async-change-example.mdx' - [required](#required) - [pattern](#pattern) - [schema](#schema) - - [onBlurValidator and validator](#onblurvalidator-and-validator) + - [onBlurValidator and onChangeValidator](#onblurvalidator-and-onchangevalidator) - [Connect with another field](#connect-with-another-field) - [Async validation](#async-validation) - [Async validator with debounce](#async-validator-with-debounce) @@ -390,13 +390,13 @@ More info about the async change behavior in the form [Handler](/uilib/extension #### Async field validation -A similar indicator behavior will occur when using async functions for field validation, such as `validator` or `onBlurValidation`, your form will exhibit async behavior. This means that the validation needs to be successfully completed before the form can be submitted. +A similar indicator behavior will occur when using async functions for field validation, such as `onChangeValidator` or `onBlurValidation`, your form will exhibit async behavior. This means that the validation needs to be successfully completed before the form can be submitted. ### Validation and error handling Every field component has a built-in validation that is based on the type of data it handles. This validation is automatically applied to the field when the user interacts with it. The validation is also applied when the user submits the form. -In addition, you can add your own validation to a field component. This is done by adding a `required`, `pattern`, `schema` or `validator` property. +In addition, you can add your own validation to a field component. This is done by adding a `required`, `pattern`, `schema`, `onChangeValidator` or `onBlurValidation` property. Fields which have the `disabled` property or the `readOnly` property, will skip validation. @@ -487,9 +487,9 @@ const schema = { ``` -#### onBlurValidator and validator +#### onBlurValidator and onChangeValidator -The `onBlurValidator` and `validator` properties accepts a function that takes the current value of the field as an argument and returns an error message if the value is invalid: +The `onBlurValidator` and `onChangeValidator` properties accepts a function that takes the current value of the field as an argument and returns an error message if the value is invalid: ```tsx const onChangeValidator = (value) => { @@ -498,14 +498,14 @@ const onChangeValidator = (value) => { return new Error('Invalid value message') } } -render() +render() ``` You can find more info about error messages in the [error messages](/uilib/extensions/forms/Form/error-messages/) docs. ##### Connect with another field -You can also use the `connectWithPath` function to connect the validator to another field. This allows you to rerun the validator function once the value of the connected field changes: +You can also use the `connectWithPath` function to connect the validator (`onChangeValidator` and `onBlurValidator`) to another field. This allows you to rerun the validator function once the value of the connected field changes: ```tsx import { Form, Field } from '@dnb/eufemia/extensions/forms' @@ -523,15 +523,15 @@ render( , ) ``` -By default, the validator function will only run when the "/withValidator" field is changed. When the error message is shown, it will update the message with the new value of the "/myReference" field. +By default, the `onChangeValidator` function will only run when the "/withOnChangeValidator" field is changed. When the error message is shown, it will update the message with the new value of the "/myReference" field. You can also change this behavior for testing purposes by using the following properties: @@ -576,7 +576,7 @@ const onChangeValidator = debounceAsync(async function myValidator(value) { return error } }) -render() +render() ``` ### Localization and translation @@ -699,7 +699,7 @@ When creating [your own field](/uilib/extensions/forms/create-component/#localiz When building your application forms, preferably use the following layout components. They seamlessly places all the fields and components of Eufemia Forms correctly into place. - [Flex.Stack](/uilib/layout/flex/stack/) layout component for easy and consistent application forms. -- [Card](/uilib/components/card/) with the stack property `...` for the default card outline of forms. +- [Form.Card](/uilib/extensions/forms/Form/Card/) with the stack property `...` for the default card outline of forms. - [Form.Appearance](/uilib/extensions/forms/Form/Appearance/) for changing sizes (height) of e.g. input fields. ### Best practices diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/intro-examples.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/intro-examples.mdx index 51c9cb4c170..f5c65b7d164 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/intro-examples.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/intro-examples.mdx @@ -35,7 +35,7 @@ properties for simplified form implementations. ## Layout components -Wrapping inputs in [Flex.Stack](/uilib/layout/flex/stack/) and [Card](/uilib/components/card/) with the `stack` property, provides the standard design without +Wrapping inputs in [Flex.Stack](/uilib/layout/flex/stack/) and [Form.Card](/uilib/extensions/forms/Form/Card/) with the `stack` property, provides the standard design without the need for local styles. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/helpers/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/helpers/Examples.tsx index 0a5cd486913..87e766b226a 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/helpers/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/helpers/Examples.tsx @@ -100,7 +100,7 @@ export function Selection() { return ( -

+

If you select a part of this text, you will see the selection highlight is green.

diff --git a/packages/dnb-design-system-portal/src/docs/uilib/helpers/classes.mdx b/packages/dnb-design-system-portal/src/docs/uilib/helpers/classes.mdx index 67473c9d006..6a355b8aacf 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/helpers/classes.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/helpers/classes.mdx @@ -6,6 +6,8 @@ order: 1 import * as Examples from 'Docs/uilib/helpers/Examples' import SkipLinkExample from 'Docs/uilib/usage/accessibility/examples/skip-link-example.tsx' +# CSS classes + ## CSS helper classes Reusing classes in the markup instead of using SCSS extends or _mixins_ will prevent duplication in the `@dnb/eufemia`. So also your application will have good benefits from reusing these helper classes. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/helpers/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/helpers/info.mdx index 8df645959d5..fe0d4515d1c 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/helpers/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/helpers/info.mdx @@ -2,6 +2,8 @@ showTabs: true --- +# Helpers + ## Description All the [components](/uilib/components) do share a couple of common used helpers. Your application can also use these helpers, but it's totally optional. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/layout/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/layout/Examples.tsx index 6b972e78098..123838b4e28 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/layout/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/layout/Examples.tsx @@ -18,7 +18,6 @@ import { Autocomplete, Dropdown, Space, - Card, Code, Grid, FormSet, @@ -48,20 +47,20 @@ export const LayoutComponents = () => { Profile - + Name - + - + More information - +
) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/Examples.tsx index 7dc43a67f38..7f595809964 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/Examples.tsx @@ -2,7 +2,7 @@ import React from 'react' import styled from '@emotion/styled' import ComponentBox from '../../../../shared/tags/ComponentBox' import MediaQuery from '@dnb/eufemia/src/shared/MediaQuery' -import { Slider, Code, Button, Card, Flex } from '@dnb/eufemia/src' +import { Slider, Code, Button, Flex } from '@dnb/eufemia/src' import { TestElement, Field, @@ -24,20 +24,20 @@ export const LayoutComponents = () => { Profile - + Name - + - + More information - +
) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/info.mdx index 01540f9f36f..215178062fb 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/container/info.mdx @@ -2,14 +2,14 @@ showTabs: true --- -## Description - ## Import ```tsx import { Flex } from '@dnb/eufemia' ``` +## Description + `Flex.Container` is a building block for [CSS flexbox](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_flexible_box_layout) based layout of contents and components. **NB:** For form layouts, use [Flex.Stack](/uilib/layout/flex/stack/) instead. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/info.mdx index b059cc2aaea..6bc9303b5a0 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/info.mdx @@ -2,6 +2,12 @@ showTabs: true --- +## Import + +```tsx +import { Flex } from '@dnb/eufemia' +``` + ## Description To make it easier to build application layout and [form](/uilib/extensions/forms)-views in line with defined design sketches, there are a number of components for layout. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/stack/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/stack/Examples.tsx index fee262eda80..c1cc1721752 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/stack/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/layout/flex/stack/Examples.tsx @@ -47,11 +47,11 @@ export const WithCard = () => { return ( - +

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Aliquam at felis rutrum, luctus dui at, bibendum ipsum.

- +

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Aliquam at felis rutrum, luctus dui at, bibendum ipsum.

@@ -65,7 +65,7 @@ export const WithCardAndHeading = () => { Main heading - +

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Aliquam at felis rutrum, luctus dui at, bibendum ipsum.

@@ -80,7 +80,7 @@ export const WithCardAndHeadings = () => { Main heading Sub heading - +

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

Aliquam at felis rutrum, luctus dui at, bibendum ipsum.

diff --git a/packages/dnb-design-system-portal/src/docs/uilib/typography.mdx b/packages/dnb-design-system-portal/src/docs/uilib/typography.mdx index 6b5decc8266..daef198fe08 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/typography.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/typography.mdx @@ -9,6 +9,15 @@ import { TypographyVariants } from 'Docs/uilib/typography/Examples' # Typography +## Typography components + +The two main components used to set typography are: + +- [Span](uilib/elements/span) +- [P](uilib/elements/paragraph) + +([Lead](uilib/elements/lead) and [Ingress](uilib/elements/ingress) also works in the same way) + ## Typography in general Fonts are handled automatically once the CSS packages **dnb-ui-core** or **dnb-ui-basis** are loaded. @@ -55,7 +64,7 @@ Read more about the [Anchor / Text Link](/uilib/components/anchor) DNB has its own monospace typeface (`font-family`). -Use it either by a CSS class `.dnb-typo-mono-regular` or define your own like so: +Use it either by a CSS class `.dnb-t__family--monospace` or define your own like so: ```css .css-selector { diff --git a/packages/dnb-design-system-portal/src/docs/uilib/typography/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/typography/Examples.tsx index 36027183e8a..88d10c27788 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/typography/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/typography/Examples.tsx @@ -7,6 +7,8 @@ import React from 'react' import styled from '@emotion/styled' import ComponentBox from '../../../shared/tags/ComponentBox' import { Code, H4, Lead, P } from '@dnb/eufemia/src' +import { useTheme } from '@dnb/eufemia/shared' + import { TypographyBox } from '../../../shared/parts/TypographyBox' const Wrapper = styled.div` @@ -29,31 +31,62 @@ const FontUsageExample = ({ typo_class, font_family }) => ( ) -export function FontWeightExample() { +export function FontWeightByThemeExample() { + const theme = useTheme() + + if (theme?.name === 'sbanken') { + return ( + + {/* Regular */} + + + {/* Medium */} + + + {/* Bold */} + + + {/* Mono Regular */} + + + ) + } return ( {/* Regular */} {/* Medium */} {/* Bold */} {/* Mono Regular */} ) @@ -91,7 +124,7 @@ export function TypographyVariants() { fabellas senserit inciderint vim.

Text basis (Medium) -

+

Lorem ipsum dolor sit amet, sint quodsi concludaturque nam ei, appetere oporteat eam te. Vel in deleniti sensibus, officiis menandri efficiantur no cum. Per et habemus gubergren. Mundi @@ -107,7 +140,7 @@ export function TypographyVariants() { fabellas senserit inciderint vim.

Text small (Medium) -

+

Lorem ipsum dolor sit amet, sint quodsi concludaturque nam ei, appetere oporteat eam te. Vel in deleniti sensibus, officiis menandri efficiantur no cum. Per et habemus gubergren. Mundi @@ -119,7 +152,7 @@ export function TypographyVariants() { Lorem ipsum dolor sit amet, sint quodsi concludaturque nam ei.

Text x-small (Medium) -

+

Lorem ipsum dolor sit amet, sint quodsi concludaturque nam ei.

diff --git a/packages/dnb-design-system-portal/src/docs/uilib/typography/_helpers.tsx b/packages/dnb-design-system-portal/src/docs/uilib/typography/_helpers.tsx new file mode 100644 index 00000000000..427c219979a --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/typography/_helpers.tsx @@ -0,0 +1,31 @@ +import { useTheme } from '@dnb/eufemia/shared' + +import propertiesSbanken from '@dnb/eufemia/src/style/themes/theme-sbanken/properties' +import propertiesUi from '@dnb/eufemia/src/style/themes/theme-ui/properties' +import propertiesEiendom from '@dnb/eufemia/src/style/themes/theme-eiendom/properties' + +const properties = { + sbanken: propertiesSbanken, + ui: propertiesUi, + eiendom: propertiesEiendom, +} + +export const GetPropValue = (prop) => { + const theme = useTheme() + const p = properties[theme.name][prop] + if (p && p.startsWith('var(')) { + return GetPropValue(p.substring(4, p.indexOf(')'))) + } + return p +} + +export const GetPropAsPx = (prop) => { + return RemToPx(GetPropValue(prop)) +} + +const RemToPx = (rem = '') => { + if (rem.endsWith('rem')) { + return parseFloat(rem) * 16 + 'px' + } + return rem +} diff --git a/packages/dnb-design-system-portal/src/docs/uilib/typography/font-size.mdx b/packages/dnb-design-system-portal/src/docs/uilib/typography/font-size.mdx index f1c138e0de5..30d2375fe21 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/typography/font-size.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/typography/font-size.mdx @@ -1,23 +1,29 @@ --- title: 'Font Size' -order: 2 +order: 3 --- -# Font Size +import { GetPropValue, GetPropAsPx } from './_helpers.tsx' +import ChangeStyleTheme from '../../../core/ChangeStyleTheme' -For details about what values Typographic elements do use, have a look at the [Fonts & Typography](/quickguide-designer/fonts#typographic-elements) documentation. +# Font Size for + + + +For details about what values Typographic elements do use, have a look at the +[Fonts & Typography](/quickguide-designer/fonts#typographic-elements) documentation. ## Default `font-size` **rem** table -| Pixel | Type | Rem | Custom Property | Info | -| ----- | ---------- | ------------ | ---------------------- | ------------------------------- | -| 14px | `x-small` | **0.875rem** | `--font-size-x-small` | Do not use for texts | -| 16px | `small` | **1rem** | `--font-size-small` | [Fallback](#fallback-font-size) | -| 18px | `basis` | **1.125rem** | `--font-size-basis` | Default size | -| 20px | `medium` | **1.25rem** | `--font-size-medium` | | -| 26px | `large` | **1.625rem** | `--font-size-large` | | -| 34px | `x-large` | **2.125rem** | `--font-size-x-large` | | -| 48px | `xx-large` | **3rem** | `--font-size-xx-large` | | +| Pixel | Type | Rem | CSS variable / property | CSS Classname | Info | +| ------------------------------------- | ---------- | ------------------------------------------ | ----------------------- | ------------------------ | ------------------------------- | +| {GetPropAsPx('--font-size-x-small')} | `x-small` | **{GetPropValue('--font-size-x-small')}** | `--font-size-x-small` | `.dnb-t__size--x-small` | Do not use for texts | +| {GetPropAsPx('--font-size-small')} | `small` | **{GetPropValue('--font-size-small')}** | `--font-size-small` | `.dnb-t__size--small` | [Fallback](#fallback-font-size) | +| {GetPropAsPx('--font-size-basis')} | `basis` | **{GetPropValue('--font-size-basis')}** | `--font-size-basis` | `.dnb-t__size--basis` | Default size | +| {GetPropAsPx('--font-size-medium')} | `medium` | **{GetPropValue('--font-size-medium')}** | `--font-size-medium` | `.dnb-t__size--medium` | | +| {GetPropAsPx('--font-size-large')} | `large` | **{GetPropValue('--font-size-large')}** | `--font-size-large` | `.dnb-t__size--large` | | +| {GetPropAsPx('--font-size-x-large')} | `x-large` | **{GetPropValue('--font-size-x-large')}** | `--font-size-x-large` | `.dnb-t__size--x-large` | | +| {GetPropAsPx('--font-size-xx-large')} | `xx-large` | **{GetPropValue('--font-size-xx-large')}** | `--font-size-xx-large` | `.dnb-t__size--xx-large` | | ### Code Editor Extensions @@ -25,16 +31,16 @@ You may be interested to install an [Eufemia code editor extension](/uilib/helpe ## Additional `font-size` **em** table -| Pixel | Type | Em | Custom Property | Info | -| ----- | ----------- | ------- | ----------------------- | ---- | -| 16px | `basis--em` | **1em** | `--font-size-basis--em` | | +| Pixel | Type | Em | Custom Property | Info | +| ----- | ----------- | ------------------------------------------- | ----------------------- | ---- | +| 16px | `basis--em` | **{GetPropValue('--font-size-basis--em')}** | `--font-size-basis--em` | | ## How to use the sizes (CSS) ```css /* I have a default size */ .dnb-p { - font-size: var(--font-size-basis); /* 1.125 = 18px */ + font-size: var(--font-size-basis); /* 1.125rem = 18px (in Ui theme) */ } ``` diff --git a/packages/dnb-design-system-portal/src/docs/uilib/typography/font-weight.mdx b/packages/dnb-design-system-portal/src/docs/uilib/typography/font-weight.mdx index b06ff72263a..3843404d8eb 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/typography/font-weight.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/typography/font-weight.mdx @@ -1,23 +1,27 @@ --- title: 'Font Weights' -order: 1 +order: 2 redirect_from: - /uilib/typography/font-weights --- -import { FontWeightExample } from 'Docs/uilib/typography/Examples' +import { FontWeightByThemeExample } from 'Docs/uilib/typography/Examples' +import { GetPropValue, GetPropAsPx } from './_helpers.tsx' +import ChangeStyleTheme from '../../../core/ChangeStyleTheme' -# Font Weights +# Font Weights for + + For details about what values Typographic elements do use, have a look at the [Fonts & Typography](/quickguide-designer/fonts#typographic-elements) documentation. ## Eufemia has three (3) font-weights -| Type | Custom Property | CSS Classname | -| ---------------------------------------------------------- | ----------------------- | ------------------ | -| Regular (normal) | `--font-weight-regular` | `dnb-typo-regular` | -| Medium (500) | `--font-weight-medium` | `dnb-typo-medium` | -| Bold (600) | `--font-weight-bold` | `dnb-typo-bold` | +| Type | CSS variable / property | CSS Classname | +| ------------------------------------------------------------------------------------------------- | ----------------------- | ------------------------- | +| Regular ({GetPropValue('--font-weight-regular')}) | `--font-weight-regular` | `.dnb-t__weight--regular` | +| Medium ({GetPropValue('--font-weight-medium')}) | `--font-weight-medium` | `.dnb-t__weight--medium` | +| Bold ({GetPropValue('--font-weight-bold')}) | `--font-weight-bold` | `.dnb-t__weight--bold` | ### How to use the weights (CSS) @@ -34,7 +38,7 @@ p { /* I am Bold */ p { - font-weight: var(--font-weight-bold); /* 600 */ + font-weight: var(--font-weight-bold); /* 600 (in Ui theme) */ } /* This will result in loading the Bold Font */ @@ -47,11 +51,11 @@ p { ```html -

Heading

-

Paragraph

-Third Tag +

Heading

+

Paragraph

+Third Tag ``` -## Examples +## weight examples - + diff --git a/packages/dnb-design-system-portal/src/docs/uilib/typography/helper-classes.mdx b/packages/dnb-design-system-portal/src/docs/uilib/typography/helper-classes.mdx new file mode 100644 index 00000000000..542e182452a --- /dev/null +++ b/packages/dnb-design-system-portal/src/docs/uilib/typography/helper-classes.mdx @@ -0,0 +1,70 @@ +--- +title: 'Helper classes' +description: 'A list of typography CSS classes' +order: 1 +--- + +# Helpers classes + +This is a list of all the typography helper classes available. They are used by the [Span](uilib/elements/span) and [Paragraph](uilib/elements/paragraph) components, but can also be used on their own. + +For visual examples, see the [Paragraph demos](uilib/elements/paragraph/#demos). + +For details on sizes and weights, see the [Font weights](uilib/typography/font-weight/), [Font size](uilib/typography/font-size/) and [Line Height](uilib/typography/line-height/) documentation. + +## CSS classes `.dnb-t` + +### Font weight + +``` + .dnb-t__weight--regular + .dnb-t__weight--medium + .dnb-t__weight--bold +``` + +### Font size + +``` + .dnb-t__size--xx-large + .dnb-t__size--x-large + .dnb-t__size--large + .dnb-t__size--basis + .dnb-t__size--medium + .dnb-t__size--small + .dnb-t__size--x-small +``` + +### Line heights + +``` + .dnb-t__line-height--xx-large + .dnb-t__line-height--x-large + .dnb-t__line-height--large + .dnb-t__line-height--basis + .dnb-t__line-height--medium + .dnb-t__line-height--small + .dnb-t__line-height--x-small +``` + +### Text alignment + +``` + .dnb-t__align--center + .dnb-t__align--left + .dnb-t__align--right +``` + +### Font family + +``` + .dnb-t__family--default + .dnb-t__family--heading + .dnb-t__family--monospace +``` + +### Underline / italic + +``` + .dnb-t__decoration--underline + .dnb-t__slant--italic +``` diff --git a/packages/dnb-design-system-portal/src/docs/uilib/typography/line-height.mdx b/packages/dnb-design-system-portal/src/docs/uilib/typography/line-height.mdx index e6547f21cff..721c439c919 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/typography/line-height.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/typography/line-height.mdx @@ -1,23 +1,29 @@ --- title: 'Line Height' -order: 3 +order: 4 --- -# Line Height +import { GetPropValue, GetPropAsPx } from './_helpers.tsx' +import ChangeStyleTheme from '../../../core/ChangeStyleTheme' + +# Line Height for + + For details about what values Typographic elements do use, have a look at the [Fonts & Typography](/quickguide-designer/fonts#typographic-elements) documentation. ## Default `line-height` **rem** table -| Pixel | Type | Rem | Custom Property | -| ----- | --------- | ------------ | ----------------------- | -| 18px | `x-small` | **1.125rem** | `--line-height-x-small` | -| 20px | `small` | **1.25rem** | `--line-height-small` | -| 24px | `basis` | **1.5rem** | `--line-height-basis` | -| 28px | `lead` | **1.75rem** | `--line-height-lead` | -| 32px | `medium` | **2rem** | `--line-height-medium` | -| 40px | `large` | **2.5rem** | `--line-height-large` | -| 56px | `x-large` | **3.5rem** | `--line-height-x-large` | +| Pixel | Type | Rem | CSS variable / property | CSS Classname | Info | +| --------------------------------------- | ---------- | -------------------------------------------- | ------------------------ | ------------------------------- | -------------------------------- | +| {GetPropAsPx('--line-height-x-small')} | `x-small` | **{GetPropValue('--line-height-x-small')}** | `--line-height-x-small` | `.dnb-t__line-height--x-small` | | +| {GetPropAsPx('--line-height-small')} | `small` | **{GetPropValue('--line-height-small')}** | `--line-height-small` | `.dnb-t__line-height--small` | | +| {GetPropAsPx('--line-height-basis')} | `basis` | **{GetPropValue('--line-height-basis')}** | `--line-height-basis` | `.dnb-t__line-height--basis` | | +| {GetPropAsPx('--line-height-lead')} | `lead` | **{GetPropValue('--line-height-lead')}** | `--line-height-lead` | `.dnb-t__line-height--lead` | Unique line-height for ``. | +| {GetPropAsPx('--line-height-medium')} | `medium` | **{GetPropValue('--line-height-medium')}** | `--line-height-medium` | `.dnb-t__line-height--medium` | | +| {GetPropAsPx('--line-height-large')} | `large` | **{GetPropValue('--line-height-large')}** | `--line-height-large` | `.dnb-t__line-height--large` | | +| {GetPropAsPx('--line-height-x-large')} | `x-large` | **{GetPropValue('--line-height-x-large')}** | `--line-height-x-large` | `.dnb-t__line-height--x-large` | | +| {GetPropAsPx('--line-height-xx-large')} | `xx-large` | **{GetPropValue('--line-height-xx-large')}** | `--line-height-xx-large` | `.dnb-t__line-height--xx-large` | Same as `x-large` | ### Code Editor Extensions @@ -25,18 +31,18 @@ You may be interested to install an [Eufemia code editor extension](/uilib/helpe ## Additional `line-height` **em** table -| Pixel | Type | Em | Custom Property | Info | -| ----- | -------------- | ----------- | ---------------------------- | ------ | -| 16px | `xx-small--em` | **1em** | `--line-height-xx-small--em` | | -| 24px | `basis--em` | **1.333em** | `--line-height-basis--em` | **\*** | +| Pixel | Type | Em | Custom Property | Info | +| ----- | -------------- | ------------------------------------------------ | ---------------------------- | ------ | +| 16px | `xx-small--em` | **{GetPropValue('--line-height-xx-small--em')}** | `--line-height-xx-small--em` | | +| 24px | `basis--em` | **{GetPropValue('--line-height-basis--em')}** | `--line-height-basis--em` | **\*** | -**\*** If we sum 1.33333333333\*18 we get 24. Browsers do round CSS values, so we do not need all the decimal numbers for now. +**\*** For example: if we sum 1.33333333333\*18 we get 24. Browsers do round CSS values, so we do not need all the decimal numbers for now. ### How to use the line heights (CSS) ```css /* I have a default height */ .dnb-p { - line-height: var(--line-height-basis); /* 1.5rem = 24px */ + line-height: var(--line-height-basis); /* 1.5rem = 24px (in Ui theme) */ } ``` diff --git a/packages/dnb-design-system-portal/src/docs/uilib/usage/first-steps.mdx b/packages/dnb-design-system-portal/src/docs/uilib/usage/first-steps.mdx index a308f3e95ea..4dbf7de225e 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/usage/first-steps.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/usage/first-steps.mdx @@ -16,6 +16,6 @@ To give you an overall picture of the Design System, you may read [Getting Start Check out the `@dnb/eufemia` **[Installation documentation](/uilib/usage/#installation)**. -## Be continued +## Continue Continue with [the basics](/uilib/usage/first-steps/the-basics) diff --git a/packages/dnb-design-system-portal/src/e2e/typography.spec.ts b/packages/dnb-design-system-portal/src/e2e/typography.spec.ts index 18e9cad1018..53e3dce4e78 100644 --- a/packages/dnb-design-system-portal/src/e2e/typography.spec.ts +++ b/packages/dnb-design-system-portal/src/e2e/typography.spec.ts @@ -75,11 +75,11 @@ test.describe('Typography for UI', () => { }) test('bold text should have correct font-weight', async ({ page }) => { - await page.waitForSelector('.typography-box > .dnb-typo-bold', { + await page.waitForSelector('.typography-box > .dnb-t__weight--bold', { state: 'attached', }) const element = page - .locator('.typography-box > .dnb-typo-bold') + .locator('.typography-box > .dnb-t__weight--bold') .first() await expect(element).toHaveCSS('font-weight', '600') }) @@ -162,11 +162,14 @@ test.describe('Typography for Sbanken', () => { }) test('bold text should have correct font-weight', async ({ page }) => { - await page.waitForSelector('.typography-box > .dnb-typo-bold', { - state: 'attached', - }) + await page.waitForSelector( + '.typography-box > .dnb-t__weight--medium', + { + state: 'attached', + }, + ) const element = page - .locator('.typography-box > .dnb-typo-bold') + .locator('.typography-box > .dnb-t__weight--medium') .first() await expect(element).toHaveCSS('font-weight', '500') }) diff --git a/packages/dnb-design-system-portal/src/shared/menu/SidebarMenu.module.scss b/packages/dnb-design-system-portal/src/shared/menu/SidebarMenu.module.scss index 75fc678b093..8a9c19a8994 100644 --- a/packages/dnb-design-system-portal/src/shared/menu/SidebarMenu.module.scss +++ b/packages/dnb-design-system-portal/src/shared/menu/SidebarMenu.module.scss @@ -215,7 +215,7 @@ &.dnb-sidebar-menu__theme-badge--sbanken { padding: 0; - font-family: var(--sb-font-family-headings); + font-family: var(--font-family-heading); font-weight: normal; .dnb-sidebar-menu__theme-badge__title { diff --git a/packages/dnb-design-system-portal/src/shared/menu/SidebarMenu.tsx b/packages/dnb-design-system-portal/src/shared/menu/SidebarMenu.tsx index 593b12791e0..4e9b32e1cc5 100644 --- a/packages/dnb-design-system-portal/src/shared/menu/SidebarMenu.tsx +++ b/packages/dnb-design-system-portal/src/shared/menu/SidebarMenu.tsx @@ -636,7 +636,7 @@ function groupNavItems(navItems: NavItem[], location: Location) { // Define item object reference in hashmap hashmap[itemId] = hashItem - // Add all toplevel heading object references to topLevelHeadings array + // Add all top level heading object references to topLevelHeadings array // so that we wont have to loop through the array a second time to sort out top level headings if (item.level === 1) { topLevelHeadings.push(hashmap[itemId]) diff --git a/packages/dnb-design-system-portal/src/shared/parts/PropertiesTable.tsx b/packages/dnb-design-system-portal/src/shared/parts/PropertiesTable.tsx index 52ea1f429b8..a22cbcf03eb 100644 --- a/packages/dnb-design-system-portal/src/shared/parts/PropertiesTable.tsx +++ b/packages/dnb-design-system-portal/src/shared/parts/PropertiesTable.tsx @@ -179,7 +179,7 @@ function convertToCamelCase(doc: string, keys: string[]) { } export function formatName(name: string): React.ReactNode | string { - if (name.includes('/')) { + if (name.includes('[')) { return {name} } diff --git a/packages/dnb-eufemia/scripts/prebuild/tasks/__tests__/__snapshots__/makePropertiesFile.test.ts.snap b/packages/dnb-eufemia/scripts/prebuild/tasks/__tests__/__snapshots__/makePropertiesFile.test.ts.snap index c84adae7547..5418afc2740 100644 --- a/packages/dnb-eufemia/scripts/prebuild/tasks/__tests__/__snapshots__/makePropertiesFile.test.ts.snap +++ b/packages/dnb-eufemia/scripts/prebuild/tasks/__tests__/__snapshots__/makePropertiesFile.test.ts.snap @@ -5,7 +5,7 @@ exports[`Properties for sbanken has to validate 1`] = ` export default { '--sb-font-family-default': '"Roboto", "Helvetica", "Arial", sans-serif', - '--sb-font-family-headings': '"MaisonNeueHeadings", "Roboto", "Helvetica",', + '--sb-font-family-heading': '"MaisonNeueHeadings", "Roboto", "Helvetica",', '--sb-font-weight-default': 'normal', '--sb-font-weight-basis': 'normal', '--sb-font-weight-regular': 'normal', @@ -97,6 +97,7 @@ export default { '--color-emerald-green-25': '#c4d4d6', '--color-emerald-green-10': '#e8eeef', '--font-family-default': 'var(--sb-font-family-default)', + '--font-family-heading': 'var(--sb-font-family-heading)', '--font-family-monospace': '"DNBMono", "Menlo", "Consolas", "Roboto Mono",', '--font-weight-default': 'normal', '--font-weight-basis': 'normal', @@ -187,7 +188,7 @@ exports[`Properties for ui has to validate 1`] = ` export default { '--sb-font-family-default': '"Roboto", "Helvetica", "Arial", sans-serif', - '--sb-font-family-headings': '"MaisonNeueHeadings", "Roboto", "Helvetica",', + '--sb-font-family-heading': '"MaisonNeueHeadings", "Roboto", "Helvetica",', '--sb-font-weight-default': 'normal', '--sb-font-weight-basis': 'normal', '--sb-font-weight-regular': 'normal', @@ -279,6 +280,7 @@ export default { '--color-emerald-green-25': '#c4d4d6', '--color-emerald-green-10': '#e8eeef', '--font-family-default': '"DNB", sans-serif', + '--font-family-heading': 'var(--font-family-default)', '--font-family-monospace': '"DNBMono", "Menlo", "Consolas", "Roboto Mono",', '--font-weight-default': 'normal', '--font-weight-basis': 'normal', diff --git a/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js b/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js index 37f26b72068..22de5e1914e 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js +++ b/packages/dnb-eufemia/src/components/autocomplete/Autocomplete.js @@ -189,6 +189,7 @@ export default class Autocomplete extends React.PureComponent { PropTypes.string, PropTypes.number, ]), + /** @deprecated use `selectedKey` */ selected_key: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, diff --git a/packages/dnb-eufemia/src/components/autocomplete/AutocompleteDocs.ts b/packages/dnb-eufemia/src/components/autocomplete/AutocompleteDocs.ts index f3e63e18783..83d399c7553 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/AutocompleteDocs.ts +++ b/packages/dnb-eufemia/src/components/autocomplete/AutocompleteDocs.ts @@ -270,7 +270,7 @@ export const AutocompleteEvents: PropertiesTableProps = { status: 'optional', }, on_select: { - doc: 'Will be called once the users selects an item by a click or keyboard navigation. Returns an object with the new selected `data` item `{ data, event, attributes, value, active_item }` including [these methods](/uilib/components/autocomplete/events#dynamically-change-data). The "active_item" property is the currently selected item by keyboard navigation', + doc: 'Will be called once the users focuses or selects an item by a click or keyboard navigation. Returns an object with the new selected `data` item `{ data, event, attributes, value, active_item }` including [these methods](/uilib/components/autocomplete/events#dynamically-change-data). The "active_item" property is the currently selected item by keyboard navigation', type: 'function', status: 'optional', }, diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.screenshot.test.ts b/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.screenshot.test.ts index 568038e1d0b..d1d4057d75b 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.screenshot.test.ts +++ b/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.screenshot.test.ts @@ -120,6 +120,21 @@ describe.each(['ui', 'sbanken'])('Autocomplete for %s', (themeName) => { expect(screenshot).toMatchImageSnapshot() }) + it('have to match disabled options', async () => { + const screenshot = await makeScreenshot({ + selector: '[data-visual-test="autocomplete-disabled-options"]', + simulateSelector: + '[data-visual-test="autocomplete-disabled-options"] .dnb-autocomplete .dnb-input', + waitAfterSimulateSelector: + '[data-visual-test="autocomplete-disabled-options"] .dnb-autocomplete--opened', + simulate: 'click', + style: { + height: '25rem', + }, + }) + expect(screenshot).toMatchImageSnapshot() + }) + it('have to match autocomplete opened list', async () => { const screenshot = await makeScreenshot({ selector: '[data-visual-test="autocomplete-opened"]', diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx b/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx index 738f432fbdb..63557889935 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx +++ b/packages/dnb-eufemia/src/components/autocomplete/__tests__/Autocomplete.test.tsx @@ -23,8 +23,8 @@ import { } from '@testing-library/react' import { DrawerListData, - DrawerListDataObject, - DrawerListDataObjectUnion, + DrawerListDataArrayObject, + DrawerListDataArray, } from '../../../fragments/drawer-list' import { Provider } from '../../../shared' @@ -42,7 +42,7 @@ const props: AutocompleteAllProps = { skip_portal: true, } -const mockData: DrawerListDataObjectUnion[] = [ +const mockData: DrawerListDataArray = [ 'AA c', 'BB cc zethx', { content: ['CC', 'cc'] }, @@ -385,7 +385,7 @@ describe('Autocomplete component', () => { const nodes1 = document.querySelectorAll('.dnb-sr-only') expect(nodes1[nodes1.length - 1].textContent).toBe('2 alternativer') - const content = (mockData[2] as DrawerListDataObject).content + const content = (mockData[2] as DrawerListDataArrayObject).content expect( document.querySelectorAll('li.dnb-drawer-list__option')[0] .textContent @@ -763,7 +763,7 @@ describe('Autocomplete component', () => { fireEvent.change(inputElement, { target: { value: 'cc' }, }) - const content = (mockData[2] as DrawerListDataObject).content + const content = (mockData[2] as DrawerListDataArrayObject).content expect(optionElements()[0].textContent).toBe( (content as string[]).join('') ) diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-opened-list.snap.png b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-opened-list.snap.png index 98836c29dc4..8ecc091eecc 100644 Binary files a/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-opened-list.snap.png and b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-opened-list.snap.png differ diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-with-search-result.snap.png b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-with-search-result.snap.png index cec3435d465..818dccf5aa7 100644 Binary files a/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-with-search-result.snap.png and b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-with-search-result.snap.png differ diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-with-suffix-value.snap.png b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-with-suffix-value.snap.png index 60e5dc36841..72004999a82 100644 Binary files a/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-with-suffix-value.snap.png and b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-autocomplete-with-suffix-value.snap.png differ diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-disabled-options.snap.png b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-disabled-options.snap.png new file mode 100644 index 00000000000..aafbeee0cc7 Binary files /dev/null and b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-sbanken-have-to-match-disabled-options.snap.png differ diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-ui-have-to-match-disabled-options.snap.png b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-ui-have-to-match-disabled-options.snap.png new file mode 100644 index 00000000000..ea77bf49488 Binary files /dev/null and b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__image_snapshots__/autocomplete-for-ui-have-to-match-disabled-options.snap.png differ diff --git a/packages/dnb-eufemia/src/components/autocomplete/__tests__/__snapshots__/Autocomplete.test.tsx.snap b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__snapshots__/Autocomplete.test.tsx.snap index 8cb57beb81a..4e8f7ff629b 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/__tests__/__snapshots__/Autocomplete.test.tsx.snap +++ b/packages/dnb-eufemia/src/components/autocomplete/__tests__/__snapshots__/Autocomplete.test.tsx.snap @@ -2936,14 +2936,14 @@ exports[`Autocomplete scss have to match default theme snapshot 1`] = ` .dnb-autocomplete__root .dnb-drawer-list__option__item:nth-of-type(1) { font-weight: var(--font-weight-basis); } -.dnb-autocomplete__root .dnb-drawer-list__option__item:nth-of-type(n + 2) { +.dnb-autocomplete__root .dnb-drawer-list__option:not([disabled]) .dnb-drawer-list__option__item:nth-of-type(n + 2) { color: var(--color-black-55); } .dnb-autocomplete__root .dnb-drawer-list__option__item--highlight { font-weight: var(--font-weight-bold); } -.dnb-autocomplete__root .dnb-drawer-list__option--selected .dnb-drawer-list__option__item:nth-of-type(n + 2) { +.dnb-autocomplete__root .dnb-drawer-list__option--selected:not([disabled]) .dnb-drawer-list__option__item:nth-of-type(n + 2) { color: var(--color-white); } diff --git a/packages/dnb-eufemia/src/components/autocomplete/stories/Autocomplete.stories.tsx b/packages/dnb-eufemia/src/components/autocomplete/stories/Autocomplete.stories.tsx index 3eb1e9b8dc5..1c4e09e0971 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/stories/Autocomplete.stories.tsx +++ b/packages/dnb-eufemia/src/components/autocomplete/stories/Autocomplete.stories.tsx @@ -20,7 +20,7 @@ import { SubmitButton } from '../../input/Input' import { format } from '../../number-format/NumberUtils' import { DrawerListData, - DrawerListDataObjectUnion, + DrawerListDataArray, } from '../../../fragments/DrawerList' export default { @@ -856,7 +856,7 @@ const WideStyle = styled.div` export function DataSuffix() { const { locale } = React.useContext(Context) const ban = format(21001234567, { ban: true, locale }) as string - const numbers: DrawerListDataObjectUnion[] = [ + const numbers: DrawerListDataArray = [ { selected_value: `Brukskonto (${ban})`, suffix_value: ( diff --git a/packages/dnb-eufemia/src/components/autocomplete/style/themes/dnb-autocomplete-theme-ui.scss b/packages/dnb-eufemia/src/components/autocomplete/style/themes/dnb-autocomplete-theme-ui.scss index 034441bc243..3662e9d5646 100644 --- a/packages/dnb-eufemia/src/components/autocomplete/style/themes/dnb-autocomplete-theme-ui.scss +++ b/packages/dnb-eufemia/src/components/autocomplete/style/themes/dnb-autocomplete-theme-ui.scss @@ -53,7 +53,7 @@ font-weight: var(--font-weight-basis); } - &__item:nth-of-type(n + 2) { + &:not([disabled]) .dnb-drawer-list__option__item:nth-of-type(n + 2) { color: var(--color-black-55); } @@ -63,7 +63,7 @@ } .dnb-autocomplete__root - .dnb-drawer-list__option--selected + .dnb-drawer-list__option--selected:not([disabled]) .dnb-drawer-list__option__item:nth-of-type(n + 2) { color: var(--color-white); } diff --git a/packages/dnb-eufemia/src/components/avatar/style/themes/dnb-avatar-theme-sbanken.scss b/packages/dnb-eufemia/src/components/avatar/style/themes/dnb-avatar-theme-sbanken.scss index abbfdb5a453..b1aafe6452f 100644 --- a/packages/dnb-eufemia/src/components/avatar/style/themes/dnb-avatar-theme-sbanken.scss +++ b/packages/dnb-eufemia/src/components/avatar/style/themes/dnb-avatar-theme-sbanken.scss @@ -26,12 +26,12 @@ &--size-large { font-weight: var(--sb-font-weight-basis); - font-family: var(--sb-font-family-headings); + font-family: var(--font-family-heading); } &--size-x-large { font-weight: var(--sb-font-weight-basis); - font-family: var(--sb-font-family-headings); + font-family: var(--font-family-heading); } &__group { diff --git a/packages/dnb-eufemia/src/components/breadcrumb/__tests__/__snapshots__/Breadcrumb.test.tsx.snap b/packages/dnb-eufemia/src/components/breadcrumb/__tests__/__snapshots__/Breadcrumb.test.tsx.snap index e91825894cb..d0fcc560488 100644 --- a/packages/dnb-eufemia/src/components/breadcrumb/__tests__/__snapshots__/Breadcrumb.test.tsx.snap +++ b/packages/dnb-eufemia/src/components/breadcrumb/__tests__/__snapshots__/Breadcrumb.test.tsx.snap @@ -624,9 +624,19 @@ button.dnb-button::-moz-focus-inner { .dnb-section:not([style*="--breakout"]) { --breakout: var(--breakout--on); } +.dnb-section[style*="--outset"].dnb-space[style]:not(.dnb-card) { + padding-left: calc(var(--padding-left) * (1 - var(--outset))); + padding-right: calc(var(--padding-right) * (1 - var(--outset))); +} +.dnb-section[style*="--outset"]::before { + margin-left: calc(var(--outset-left, var(--padding-left)) * -1 * var(--outset)); + margin-right: calc(var(--outset-right, var(--padding-right)) * -1 * var(--outset)); + background-color: inherit; +} @media screen and (max-width: 60em) { .dnb-section { --breakout: var(--breakout--small, var(--breakout--fallback)); + --outset: var(--outset--small, var(--outset--fallback)); --background-color--value: var(--background-color--small); --text-color--value: var(--text-color--small); --outline-color: var(--outline-color--small); @@ -640,6 +650,7 @@ button.dnb-button::-moz-focus-inner { @media screen and (max-width: 60em) and (min-width: 40.00625em) { .dnb-section { --breakout: var(--breakout--medium, var(--breakout--fallback)); + --outset: var(--outset--medium, var(--outset--fallback)); --background-color--value: var(--background-color--medium); --text-color--value: var(--text-color--medium); --outline-color: var(--outline-color--medium); @@ -653,6 +664,7 @@ button.dnb-button::-moz-focus-inner { @media screen and (min-width: 60.00625em) { .dnb-section { --breakout: var(--breakout--large, var(--breakout--fallback)); + --outset: var(--outset--large, var(--outset--fallback)); --background-color--value: var(--background-color--large); --text-color--value: var(--text-color--large); --outline-color: var(--outline-color--large); diff --git a/packages/dnb-eufemia/src/components/button/__tests__/__image_snapshots__/button-for-ui-primary-have-to-match-a-stretched-button.snap.png b/packages/dnb-eufemia/src/components/button/__tests__/__image_snapshots__/button-for-ui-primary-have-to-match-a-stretched-button.snap.png index c487a1d9a7b..0b5a780b97a 100644 Binary files a/packages/dnb-eufemia/src/components/button/__tests__/__image_snapshots__/button-for-ui-primary-have-to-match-a-stretched-button.snap.png and b/packages/dnb-eufemia/src/components/button/__tests__/__image_snapshots__/button-for-ui-primary-have-to-match-a-stretched-button.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/Card.tsx b/packages/dnb-eufemia/src/components/card/Card.tsx index c4698e7cb35..5cbf9e1530a 100644 --- a/packages/dnb-eufemia/src/components/card/Card.tsx +++ b/packages/dnb-eufemia/src/components/card/Card.tsx @@ -28,6 +28,7 @@ export type Props = { */ filled?: boolean } & FlexContainerProps & + Pick & FlexItemProps & { stack?: boolean } & SpaceProps & @@ -49,6 +50,7 @@ function Card(props: Props) { rowGap, responsive = !nestedContext?.isNested, filled, + outset, title, children, ...rest @@ -79,6 +81,11 @@ function Card(props: Props) { filled && 'dnb-card--filled' ), breakout: responsive ? trueWhenSmall : false, + outset: nestedContext?.isNested + ? false + : outset === true + ? falseWhenSmall + : outset, roundedCorner: responsive ? falseWhenSmall : true, outline: '--outline-card-color', innerSpace: diff --git a/packages/dnb-eufemia/src/components/card/CardDocs.ts b/packages/dnb-eufemia/src/components/card/CardDocs.ts index 32e85eba4dd..418be0af9e4 100644 --- a/packages/dnb-eufemia/src/components/card/CardDocs.ts +++ b/packages/dnb-eufemia/src/components/card/CardDocs.ts @@ -1,6 +1,11 @@ import { PropertiesTableProps } from '../../shared/types' export const CardProperties: PropertiesTableProps = { + outset: { + doc: 'True to break out negatively on larger screens. Defaults to `false`.', + type: 'boolean', + status: 'optional', + }, stack: { doc: 'True to stack the sub components with lines between. The `spacing` will default to `medium`.', type: 'boolean', diff --git a/packages/dnb-eufemia/src/components/card/__tests__/Card.screenshot.test.ts b/packages/dnb-eufemia/src/components/card/__tests__/Card.screenshot.test.ts index b0cce62c30c..fc11951fd78 100644 --- a/packages/dnb-eufemia/src/components/card/__tests__/Card.screenshot.test.ts +++ b/packages/dnb-eufemia/src/components/card/__tests__/Card.screenshot.test.ts @@ -64,6 +64,16 @@ describe.each(['ui', 'sbanken'])('Card for %s', (themeName) => { }) expect(screenshot).toMatchImageSnapshot() }) + + it('have to match outset', async () => { + const screenshot = await makeScreenshot({ + selector: '[data-visual-test="layout-card-outset"]', + wrapperStyle: { + padding: '2rem', + }, + }) + expect(screenshot).toMatchImageSnapshot() + }) }) describe.each(['ui', 'sbanken'])( @@ -114,9 +124,18 @@ describe.each(['ui', 'sbanken'])( it('have to match nested cards', async () => { const screenshot = await makeScreenshot({ + ...params, selector: '[data-visual-test="layout-card-nested"]', }) expect(screenshot).toMatchImageSnapshot() }) + + it('have to match outset', async () => { + const screenshot = await makeScreenshot({ + ...params, + selector: '[data-visual-test="layout-card-outset"]', + }) + expect(screenshot).toMatchImageSnapshot() + }) } ) diff --git a/packages/dnb-eufemia/src/components/card/__tests__/Card.test.tsx b/packages/dnb-eufemia/src/components/card/__tests__/Card.test.tsx index b511b182080..b49ac267f67 100644 --- a/packages/dnb-eufemia/src/components/card/__tests__/Card.test.tsx +++ b/packages/dnb-eufemia/src/components/card/__tests__/Card.test.tsx @@ -131,6 +131,7 @@ describe('Card', () => { expect(element).toHaveClass('dnb-flex-item--align-self-stretch') expect(container).toHaveClass('dnb-flex-container--align-stretch') expect(container).toHaveClass('dnb-flex-container--align-self-stretch') + expect(container).toHaveClass('dnb-flex-container--spacing-medium') }) it('should set align="stretch" classes', () => { @@ -331,6 +332,55 @@ describe('Card', () => { expect(element.getAttribute('style')).not.toContain('--space-') }) + it('should support "outset"', () => { + const { rerender } = render() + + const element = document.querySelector('.dnb-card') + + expect(element).toHaveStyle('--outset--small: 0') + expect(element).toHaveStyle('--outset--medium: 1') + expect(element).toHaveStyle('--outset--large: 1') + + rerender( + + ) + + expect(element).toHaveStyle('--outset--small: 1') + expect(element).toHaveStyle('--outset--medium: 0') + expect(element).toHaveStyle('--outset--large: 0') + + rerender() + + expect(element).toHaveStyle('--outset--small: 0') + expect(element).toHaveStyle('--outset--medium: 0') + expect(element).toHaveStyle('--outset--large: 0') + }) + + it('should not allow "outset" on nested cards', () => { + render( + + + + ) + + const firstCard = document.querySelector('.dnb-card') + const secondCard = firstCard.querySelector('.dnb-card') + + expect(firstCard).toHaveStyle('--outset--small: 0') + expect(firstCard).toHaveStyle('--outset--medium: 1') + expect(firstCard).toHaveStyle('--outset--large: 1') + + expect(secondCard).toHaveStyle('--outset--small: 0') + expect(secondCard).toHaveStyle('--outset--medium: 0') + expect(secondCard).toHaveStyle('--outset--large: 0') + }) + it('should support "responsive" of false', () => { const { rerender } = render( diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-border.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-border.snap.png index 1341fae6d4a..ec3b90a7fd9 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-border.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-border.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-flex.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-flex.snap.png index 659c3a692d3..ba3adaffda8 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-flex.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-flex.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-grid.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-grid.snap.png index 49f2bb926b6..436df89e238 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-grid.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-grid.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-nested-cards.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-nested-cards.snap.png index ff04ac4c618..fc01ca68321 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-nested-cards.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-nested-cards.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-outset.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-outset.snap.png new file mode 100644 index 00000000000..6861084414c Binary files /dev/null and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-outset.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-stack.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-stack.snap.png index e996ea8b2d2..3dd8a8093ab 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-stack.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-sbanken-have-to-match-stack.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-border.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-border.snap.png index 7834c550818..52f48e64e6d 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-border.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-border.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-flex.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-flex.snap.png index 6557a5da975..af019fe8ae0 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-flex.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-flex.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-grid.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-grid.snap.png index f98836aaf34..eb7b65197e4 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-grid.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-grid.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-nested-cards.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-nested-cards.snap.png index 9259467f394..ac6854001b2 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-nested-cards.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-nested-cards.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-outset.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-outset.snap.png new file mode 100644 index 00000000000..3ac7fbdb711 Binary files /dev/null and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-outset.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-stack.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-stack.snap.png index 42dc1863332..9db4e1400f5 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-stack.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-for-ui-have-to-match-stack.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-border.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-border.snap.png index d1b615f50d3..bf00eb3413a 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-border.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-border.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-nested-cards.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-nested-cards.snap.png index b790f320a06..5c013d0b3d6 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-nested-cards.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-nested-cards.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-outset.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-outset.snap.png new file mode 100644 index 00000000000..6ad046db986 Binary files /dev/null and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-outset.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-border.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-border.snap.png index 0a9f5c4cf39..2bb50499308 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-border.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-border.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-nested-cards.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-nested-cards.snap.png index cde267ddd31..4b640080bd3 100644 Binary files a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-nested-cards.snap.png and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-nested-cards.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-outset.snap.png b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-outset.snap.png new file mode 100644 index 00000000000..f966fc9cad2 Binary files /dev/null and b/packages/dnb-eufemia/src/components/card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-outset.snap.png differ diff --git a/packages/dnb-eufemia/src/components/card/stories/Card.stories.tsx b/packages/dnb-eufemia/src/components/card/stories/Card.stories.tsx index ef2ae33b361..9bbca63b7ec 100644 --- a/packages/dnb-eufemia/src/components/card/stories/Card.stories.tsx +++ b/packages/dnb-eufemia/src/components/card/stories/Card.stories.tsx @@ -6,7 +6,7 @@ import React from 'react' import { Field, Form } from '../../../extensions/forms' -import { Card, Flex, Grid } from '../../' +import { Card, Flex, Grid, Section, Space } from '../../' import { Wrapper, Box } from 'storybook-utils/helpers' import { H2, P } from '../../../elements' @@ -190,3 +190,31 @@ export const WithGrid = () => { ) } + +export const WithOutset = () => { + return ( + + + Main heading + +

Card content

+ +

Nested card

+
+
+ +
+ +
+

Nested card

+
+
+ ) +} diff --git a/packages/dnb-eufemia/src/components/card/style/dnb-card.scss b/packages/dnb-eufemia/src/components/card/style/dnb-card.scss index 5627d916408..75806e919d6 100644 --- a/packages/dnb-eufemia/src/components/card/style/dnb-card.scss +++ b/packages/dnb-eufemia/src/components/card/style/dnb-card.scss @@ -114,9 +114,19 @@ align-self: flex-start; } - // Nested Cards - & .dnb-card { - --outline-width: 0.125rem; + &[style*='--outset'] { + &.dnb-space[style]:not(.dnb-card) { + padding-left: calc(var(--padding-left) * calc(1 - var(--outset))); + padding-right: calc(var(--padding-right) * calc(1 - var(--outset))); + } + &.dnb-card > .dnb-flex-container { + margin-left: calc( + var(--padding-left, var(--spacing-medium)) * -1 * var(--outset) + ); + margin-right: calc( + var(--padding-right, var(--spacing-medium)) * -1 * var(--outset) + ); + } } } @@ -125,14 +135,22 @@ + * + .dnb-card,/* e.g. one paragraph */ + * + * + .dnb-card,/* e.g. two paragraphs */ + .dnb-help-button__content + .dnb-section + .dnb-card +) { + &:not([class*='space__bottom']) { + margin-bottom: var(--spacing-small); + } +} + +// Deprecated – can be removed in v11 (its handled by the outset prop) +.dnb-card--auto-indent:has( + + .dnb-card:not([style*='--outset']), + + * + .dnb-card:not([style*='--outset']),/* e.g. one paragraph */ + + * + * + .dnb-card:not([style*='--outset']),/* e.g. two paragraphs */ + + .dnb-help-button__content + .dnb-section + .dnb-card:not([style*='--outset']) ) { &:not([class*='space__left']) { @include allAbove(small) { margin-left: var(--spacing-medium); } } - - &:not([class*='space__bottom']) { - margin-bottom: var(--spacing-small); - } } diff --git a/packages/dnb-eufemia/src/components/dialog/__tests__/__image_snapshots__/dialog-for-sbanken-have-to-match-a-top-aligned-dialog.snap.png b/packages/dnb-eufemia/src/components/dialog/__tests__/__image_snapshots__/dialog-for-sbanken-have-to-match-a-top-aligned-dialog.snap.png index 22a5a6e4738..973ad460813 100644 Binary files a/packages/dnb-eufemia/src/components/dialog/__tests__/__image_snapshots__/dialog-for-sbanken-have-to-match-a-top-aligned-dialog.snap.png and b/packages/dnb-eufemia/src/components/dialog/__tests__/__image_snapshots__/dialog-for-sbanken-have-to-match-a-top-aligned-dialog.snap.png differ diff --git a/packages/dnb-eufemia/src/components/dialog/__tests__/__image_snapshots__/dialog-for-ui-have-to-match-a-top-aligned-dialog.snap.png b/packages/dnb-eufemia/src/components/dialog/__tests__/__image_snapshots__/dialog-for-ui-have-to-match-a-top-aligned-dialog.snap.png index 6c7de005b85..ec4fe41e01c 100644 Binary files a/packages/dnb-eufemia/src/components/dialog/__tests__/__image_snapshots__/dialog-for-ui-have-to-match-a-top-aligned-dialog.snap.png and b/packages/dnb-eufemia/src/components/dialog/__tests__/__image_snapshots__/dialog-for-ui-have-to-match-a-top-aligned-dialog.snap.png differ diff --git a/packages/dnb-eufemia/src/components/dialog/__tests__/__snapshots__/Dialog.test.tsx.snap b/packages/dnb-eufemia/src/components/dialog/__tests__/__snapshots__/Dialog.test.tsx.snap index 7bd9ba47689..62b3becfd47 100644 --- a/packages/dnb-eufemia/src/components/dialog/__tests__/__snapshots__/Dialog.test.tsx.snap +++ b/packages/dnb-eufemia/src/components/dialog/__tests__/__snapshots__/Dialog.test.tsx.snap @@ -677,11 +677,12 @@ button.dnb-button::-moz-focus-inner { transform: translate3d(0, -0.5rem, 0); } :not(.dnb-card) .dnb-help-button__content .dnb-section { - margin-left: calc(var(--help-button-indent-width) * -1); - margin-right: calc(var(--help-button-indent-width) * -1); - border: var(--help-button-indent-width) solid var(--help-button-content-background); - border-top: none; - border-bottom: none; + --outset-left: calc( + var(--spacing-medium) + var(--help-button-indent-width) + ); + --outset-right: calc( + var(--spacing-medium) + var(--help-button-indent-width) + ); } .dnb-help-button__content.dnb-height-animation--parallax .dnb-section .dnb-p { transform: translate3d(0, 0, 0); diff --git a/packages/dnb-eufemia/src/components/drawer/__tests__/__snapshots__/Drawer.test.tsx.snap b/packages/dnb-eufemia/src/components/drawer/__tests__/__snapshots__/Drawer.test.tsx.snap index 6f087ae70f5..c74fc3f8533 100644 --- a/packages/dnb-eufemia/src/components/drawer/__tests__/__snapshots__/Drawer.test.tsx.snap +++ b/packages/dnb-eufemia/src/components/drawer/__tests__/__snapshots__/Drawer.test.tsx.snap @@ -678,11 +678,12 @@ button.dnb-button::-moz-focus-inner { transform: translate3d(0, -0.5rem, 0); } :not(.dnb-card) .dnb-help-button__content .dnb-section { - margin-left: calc(var(--help-button-indent-width) * -1); - margin-right: calc(var(--help-button-indent-width) * -1); - border: var(--help-button-indent-width) solid var(--help-button-content-background); - border-top: none; - border-bottom: none; + --outset-left: calc( + var(--spacing-medium) + var(--help-button-indent-width) + ); + --outset-right: calc( + var(--spacing-medium) + var(--help-button-indent-width) + ); } .dnb-help-button__content.dnb-height-animation--parallax .dnb-section .dnb-p { transform: translate3d(0, 0, 0); diff --git a/packages/dnb-eufemia/src/components/dropdown/Dropdown.js b/packages/dnb-eufemia/src/components/dropdown/Dropdown.js index 3a203a3a185..5f2a44109de 100644 --- a/packages/dnb-eufemia/src/components/dropdown/Dropdown.js +++ b/packages/dnb-eufemia/src/components/dropdown/Dropdown.js @@ -127,6 +127,7 @@ export default class Dropdown extends React.PureComponent { PropTypes.string, PropTypes.number, ]), + /** @deprecated use `selectedKey` */ selected_key: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, diff --git a/packages/dnb-eufemia/src/components/dropdown/__tests__/Dropdown.screenshot.test.ts b/packages/dnb-eufemia/src/components/dropdown/__tests__/Dropdown.screenshot.test.ts index fd198cc1749..a7229155f37 100644 --- a/packages/dnb-eufemia/src/components/dropdown/__tests__/Dropdown.screenshot.test.ts +++ b/packages/dnb-eufemia/src/components/dropdown/__tests__/Dropdown.screenshot.test.ts @@ -36,6 +36,20 @@ describe.each(['ui', 'sbanken'])('Dropdown for %s', (themeName) => { expect(screenshot).toMatchImageSnapshot() }) + it('have to match disabled options', async () => { + const screenshot = await makeScreenshot({ + style: { + 'padding-bottom': '14rem', + }, + selector: '[data-visual-test="dropdown-disabled-options"]', + simulate: 'click', + simulateSelector: + '[data-visual-test="dropdown-disabled-options"] .dnb-dropdown__trigger', + simulateAfter: { keypress: 'Escape' }, + }) + expect(screenshot).toMatchImageSnapshot() + }) + it('have to match tertiary variant disabled state', async () => { const screenshot = await makeScreenshot({ selector: '[data-visual-test="dropdown-disabled-tertiary"]', diff --git a/packages/dnb-eufemia/src/components/dropdown/__tests__/Dropdown.test.tsx b/packages/dnb-eufemia/src/components/dropdown/__tests__/Dropdown.test.tsx index f0cb5ae620d..4a6375e97a4 100644 --- a/packages/dnb-eufemia/src/components/dropdown/__tests__/Dropdown.test.tsx +++ b/packages/dnb-eufemia/src/components/dropdown/__tests__/Dropdown.test.tsx @@ -13,8 +13,8 @@ import { testDirectionObserver, } from '../../../fragments/drawer-list/__tests__/DrawerListTestMocks' import { - DrawerListDataObject, - DrawerListDataObjectUnion, + DrawerListDataArrayObject, + DrawerListDataArray, } from '../../../fragments/drawer-list' // use no_animation so we don't need to wait @@ -28,7 +28,7 @@ const props: DropdownAllProps = { no_animation: true, } -const mockData: DrawerListDataObjectUnion[] = [ +const mockData: DrawerListDataArray = [ { selected_value: 'Brukskonto - Kari Nordmann', content: ['1234 56 78901', 'Brukskonto - Kari Nordmann'], @@ -72,7 +72,9 @@ describe('Dropdown component', () => { expect( document.querySelector('.dnb-dropdown__text__inner').textContent - ).toBe((mockData[props.value] as DrawerListDataObject).selected_value) + ).toBe( + (mockData[props.value] as DrawerListDataArrayObject).selected_value + ) keydown(32) // space @@ -96,7 +98,7 @@ describe('Dropdown component', () => { expect( document.querySelector('.dnb-dropdown__text__inner').textContent ).toBe( - (mockData[(props.value as number) + 1] as DrawerListDataObject) + (mockData[(props.value as number) + 1] as DrawerListDataArrayObject) .selected_value ) }) @@ -507,7 +509,7 @@ describe('Dropdown component', () => { rerender() expect(document.querySelector('.dnb-dropdown__text').textContent).toBe( - (mockData[value] as DrawerListDataObject).selected_value + (mockData[value] as DrawerListDataArrayObject).selected_value ) rerender() @@ -520,7 +522,7 @@ describe('Dropdown component', () => { rerender() expect(document.querySelector('.dnb-dropdown__text').textContent).toBe( - (mockData[value] as DrawerListDataObject).selected_value + (mockData[value] as DrawerListDataArrayObject).selected_value ) rerender() @@ -732,6 +734,7 @@ describe('Dropdown component', () => { data: { __id: 0, content: 'English', + selectedKey: 'en-GB', selected_key: 'en-GB', type: 'object', value: 'en-GB', @@ -750,6 +753,7 @@ describe('Dropdown component', () => { isTrusted: false, data: { content: 'Norsk', + selectedKey: 'nb-NO', selected_key: 'nb-NO', type: 'object', value: 'nb-NO', @@ -1071,7 +1075,7 @@ describe('Dropdown component', () => { expect( document.querySelector('.dnb-dropdown__text__inner').textContent ).toBe( - (mockData[(props.value as number) + 1] as DrawerListDataObject) + (mockData[(props.value as number) + 1] as DrawerListDataArrayObject) .selected_value ) }) @@ -1080,7 +1084,9 @@ describe('Dropdown component', () => { render() expect( document.querySelector('.dnb-dropdown__text__inner').textContent - ).toBe((mockData[props.value] as DrawerListDataObject).selected_value) + ).toBe( + (mockData[props.value] as DrawerListDataArrayObject).selected_value + ) }) it('has correct selected value after new selection', () => { @@ -1092,7 +1098,9 @@ describe('Dropdown component', () => { expect( document.querySelector('.dnb-dropdown__text__inner').textContent - ).toBe((mockData[props.value] as DrawerListDataObject).selected_value) + ).toBe( + (mockData[props.value] as DrawerListDataArrayObject).selected_value + ) }) it('has correct value after useEffect value state change', () => { @@ -1111,7 +1119,9 @@ describe('Dropdown component', () => { expect( document.querySelector('.dnb-dropdown__text__inner').textContent - ).toBe((mockData[newValue] as DrawerListDataObject).selected_value) + ).toBe( + (mockData[newValue] as DrawerListDataArrayObject).selected_value + ) open() diff --git a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-different-item-directions.snap.png b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-different-item-directions.snap.png index eaead75948e..cc62ec8745e 100644 Binary files a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-different-item-directions.snap.png and b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-different-item-directions.snap.png differ diff --git a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-disabled-options.snap.png b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-disabled-options.snap.png new file mode 100644 index 00000000000..0e6da3f6953 Binary files /dev/null and b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-disabled-options.snap.png differ diff --git a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-action-menu-in-mobile-view.snap.png b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-action-menu-in-mobile-view.snap.png index 5ba5ce5c70e..331d702686a 100644 Binary files a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-action-menu-in-mobile-view.snap.png and b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-action-menu-in-mobile-view.snap.png differ diff --git a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-action-menu-with-custom-items.snap.png b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-action-menu-with-custom-items.snap.png index a07f65b4717..2487fb300cc 100644 Binary files a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-action-menu-with-custom-items.snap.png and b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-action-menu-with-custom-items.snap.png differ diff --git a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-as-more-menu-opened-on-left-side.snap.png b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-as-more-menu-opened-on-left-side.snap.png index b6c6a11535a..f01a9690d4a 100644 Binary files a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-as-more-menu-opened-on-left-side.snap.png and b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-as-more-menu-opened-on-left-side.snap.png differ diff --git a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-as-more-menu-opened-on-right-side.snap.png b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-as-more-menu-opened-on-right-side.snap.png index 1751fcffa0c..c1aced1d071 100644 Binary files a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-as-more-menu-opened-on-right-side.snap.png and b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-as-more-menu-opened-on-right-side.snap.png differ diff --git a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-items.snap.png b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-items.snap.png index 28831c65b8f..63083bbe130 100644 Binary files a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-items.snap.png and b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-dropdown-items.snap.png differ diff --git a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-tertiary-variant-opened-on-left-side.snap.png b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-tertiary-variant-opened-on-left-side.snap.png index 2fb167961e4..e89b1847501 100644 Binary files a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-tertiary-variant-opened-on-left-side.snap.png and b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-tertiary-variant-opened-on-left-side.snap.png differ diff --git a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-tertiary-variant-opened-on-right-side.snap.png b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-tertiary-variant-opened-on-right-side.snap.png index ee87421f9f3..5530e7253e2 100644 Binary files a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-tertiary-variant-opened-on-right-side.snap.png and b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-sbanken-have-to-match-the-tertiary-variant-opened-on-right-side.snap.png differ diff --git a/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-ui-have-to-match-disabled-options.snap.png b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-ui-have-to-match-disabled-options.snap.png new file mode 100644 index 00000000000..e0d428ebbfb Binary files /dev/null and b/packages/dnb-eufemia/src/components/dropdown/__tests__/__image_snapshots__/dropdown-for-ui-have-to-match-disabled-options.snap.png differ diff --git a/packages/dnb-eufemia/src/components/dropdown/stories/Dropdown.stories.tsx b/packages/dnb-eufemia/src/components/dropdown/stories/Dropdown.stories.tsx index 69e0644343b..e2ff853fd2e 100644 --- a/packages/dnb-eufemia/src/components/dropdown/stories/Dropdown.stories.tsx +++ b/packages/dnb-eufemia/src/components/dropdown/stories/Dropdown.stories.tsx @@ -20,7 +20,7 @@ import { GlobalStatus, } from '../..' import { Flex, Link } from '../../..' -import { DrawerListDataObjectUnion } from '../../../fragments/DrawerList' +import { DrawerListDataArray } from '../../../fragments/DrawerList' import { Provider } from '../../../shared' export default { @@ -628,7 +628,7 @@ export const DropdownSandbox = () => ( ) -let dropdownData: DrawerListDataObjectUnion[] = [ +let dropdownData: DrawerListDataArray = [ { selected_value: 'Brukskonto - Kari Nordmann', content: ( diff --git a/packages/dnb-eufemia/src/components/form-status/__tests__/FormStatus.test.tsx b/packages/dnb-eufemia/src/components/form-status/__tests__/FormStatus.test.tsx index 5c55607b3a2..b308a81cbd2 100644 --- a/packages/dnb-eufemia/src/components/form-status/__tests__/FormStatus.test.tsx +++ b/packages/dnb-eufemia/src/components/form-status/__tests__/FormStatus.test.tsx @@ -41,6 +41,16 @@ describe('FormStatus component', () => { ).toContain('max-width: 30rem;') }) + it('should have correct icon label', () => { + render() + expect( + document.querySelector('[role="presentation"]') + ).toHaveAttribute('data-testid', 'ErrorIcon icon') + expect( + document.querySelector('.dnb-form-status__text') + ).toHaveTextContent('Error message') + }) + it('should re-calculate max-width', () => { const { rerender } = render( diff --git a/packages/dnb-eufemia/src/components/form-status/stories/FormStatus.stories.tsx b/packages/dnb-eufemia/src/components/form-status/stories/FormStatus.stories.tsx index b64100525a0..266c1fa5a5e 100644 --- a/packages/dnb-eufemia/src/components/form-status/stories/FormStatus.stories.tsx +++ b/packages/dnb-eufemia/src/components/form-status/stories/FormStatus.stories.tsx @@ -25,7 +25,7 @@ import { } from '../..' import { Link } from '../../..' import { format } from '../../number-format/NumberUtils' -import { DrawerListDataObject } from '../../../fragments/DrawerList' +import { DrawerListDataArray } from '../../../fragments/DrawerList' export default { title: 'Eufemia/Components/FormStatus', @@ -228,7 +228,7 @@ export const GlobalStatusExample = () => { export const SuffixAndStretchedStatus = () => { const ban = format(21001234567, { ban: true }) as string - const numbers: DrawerListDataObject[] = [ + const numbers: DrawerListDataArray = [ { selected_value: `Brukskonto (${ban})`, suffix_value: ( diff --git a/packages/dnb-eufemia/src/components/global-status/__tests__/__snapshots__/GlobalStatus.test.tsx.snap b/packages/dnb-eufemia/src/components/global-status/__tests__/__snapshots__/GlobalStatus.test.tsx.snap index da959a36d6b..0d289889976 100644 --- a/packages/dnb-eufemia/src/components/global-status/__tests__/__snapshots__/GlobalStatus.test.tsx.snap +++ b/packages/dnb-eufemia/src/components/global-status/__tests__/__snapshots__/GlobalStatus.test.tsx.snap @@ -624,9 +624,19 @@ button.dnb-button::-moz-focus-inner { .dnb-section:not([style*="--breakout"]) { --breakout: var(--breakout--on); } +.dnb-section[style*="--outset"].dnb-space[style]:not(.dnb-card) { + padding-left: calc(var(--padding-left) * (1 - var(--outset))); + padding-right: calc(var(--padding-right) * (1 - var(--outset))); +} +.dnb-section[style*="--outset"]::before { + margin-left: calc(var(--outset-left, var(--padding-left)) * -1 * var(--outset)); + margin-right: calc(var(--outset-right, var(--padding-right)) * -1 * var(--outset)); + background-color: inherit; +} @media screen and (max-width: 60em) { .dnb-section { --breakout: var(--breakout--small, var(--breakout--fallback)); + --outset: var(--outset--small, var(--outset--fallback)); --background-color--value: var(--background-color--small); --text-color--value: var(--text-color--small); --outline-color: var(--outline-color--small); @@ -640,6 +650,7 @@ button.dnb-button::-moz-focus-inner { @media screen and (max-width: 60em) and (min-width: 40.00625em) { .dnb-section { --breakout: var(--breakout--medium, var(--breakout--fallback)); + --outset: var(--outset--medium, var(--outset--fallback)); --background-color--value: var(--background-color--medium); --text-color--value: var(--text-color--medium); --outline-color: var(--outline-color--medium); @@ -653,6 +664,7 @@ button.dnb-button::-moz-focus-inner { @media screen and (min-width: 60.00625em) { .dnb-section { --breakout: var(--breakout--large, var(--breakout--fallback)); + --outset: var(--outset--large, var(--outset--fallback)); --background-color--value: var(--background-color--large); --text-color--value: var(--text-color--large); --outline-color: var(--outline-color--large); diff --git a/packages/dnb-eufemia/src/components/help-button/HelpButtonInline.tsx b/packages/dnb-eufemia/src/components/help-button/HelpButtonInline.tsx index cfdebcdcd73..fa3b5c0d3c7 100644 --- a/packages/dnb-eufemia/src/components/help-button/HelpButtonInline.tsx +++ b/packages/dnb-eufemia/src/components/help-button/HelpButtonInline.tsx @@ -22,6 +22,8 @@ export type HelpProps = { open?: boolean /** Only for the "inline" variant */ breakout?: boolean + /** Only for the "inline" variant */ + outset?: boolean } export type HelpButtonInlineProps = HelpButtonProps & { @@ -97,6 +99,7 @@ export type HelpButtonInlineContentProps = SpacingProps & { children?: React.ReactNode help?: HelpProps breakout?: boolean + outset?: boolean } export function HelpButtonInlineContent( @@ -108,6 +111,7 @@ export function HelpButtonInlineContent( children, help: helpProp, breakout = true, + outset = true, ...rest } = props const { data, update } = @@ -119,12 +123,14 @@ export function HelpButtonInlineContent( content, renderAs, breakout: breakoutProp = true, + outset: outsetProp = true, } = helpProp || {} const innerRef = useRef(null) const cardContext = useContext(CardContext) const breakoutFromLayout = Boolean(cardContext) && breakout && breakoutProp + const outsetFromLayout = outset && outsetProp useEffect(() => { if (isOpen && isUserIntent) { @@ -187,6 +193,7 @@ export function HelpButtonInlineContent( tabIndex={-1} innerRef={innerRef} onKeyDown={onKeyDown} + outset={outsetFromLayout} breakout={breakoutFromLayout} roundedCorner={!breakoutFromLayout} innerSpace={ @@ -203,7 +210,7 @@ export function HelpButtonInlineContent( {...rest} > - {title &&

{title}

} + {title &&

{title}

} {content &&

{content}

}
{children} diff --git a/packages/dnb-eufemia/src/components/help-button/__tests__/__snapshots__/HelpButton.test.tsx.snap b/packages/dnb-eufemia/src/components/help-button/__tests__/__snapshots__/HelpButton.test.tsx.snap index 787af0cfc6e..27a9013f20a 100644 --- a/packages/dnb-eufemia/src/components/help-button/__tests__/__snapshots__/HelpButton.test.tsx.snap +++ b/packages/dnb-eufemia/src/components/help-button/__tests__/__snapshots__/HelpButton.test.tsx.snap @@ -663,11 +663,12 @@ button.dnb-button::-moz-focus-inner { transform: translate3d(0, -0.5rem, 0); } :not(.dnb-card) .dnb-help-button__content .dnb-section { - margin-left: calc(var(--help-button-indent-width) * -1); - margin-right: calc(var(--help-button-indent-width) * -1); - border: var(--help-button-indent-width) solid var(--help-button-content-background); - border-top: none; - border-bottom: none; + --outset-left: calc( + var(--spacing-medium) + var(--help-button-indent-width) + ); + --outset-right: calc( + var(--spacing-medium) + var(--help-button-indent-width) + ); } .dnb-help-button__content.dnb-height-animation--parallax .dnb-section .dnb-p { transform: translate3d(0, 0, 0); diff --git a/packages/dnb-eufemia/src/components/help-button/style/dnb-help-button-inline.scss b/packages/dnb-eufemia/src/components/help-button/style/dnb-help-button-inline.scss index 89353661fc5..b0e8ef6e1de 100644 --- a/packages/dnb-eufemia/src/components/help-button/style/dnb-help-button-inline.scss +++ b/packages/dnb-eufemia/src/components/help-button/style/dnb-help-button-inline.scss @@ -109,17 +109,12 @@ --help-button-indent-width: var(--card-outline-width); :not(.dnb-card) & .dnb-section { - // Because no outline, we need to stretch the content to the left and right, - // so it is aligned on how the Card is displayed. - margin-left: calc(var(--help-button-indent-width) * -1); - margin-right: calc(var(--help-button-indent-width) * -1); - - // Use a border instead of the original outline, - // because is has no animation artifacts. - border: var(--help-button-indent-width) solid - var(--help-button-content-background); - border-top: none; - border-bottom: none; + --outset-left: calc( + var(--spacing-medium) + var(--help-button-indent-width) + ); + --outset-right: calc( + var(--spacing-medium) + var(--help-button-indent-width) + ); } &.dnb-height-animation--parallax .dnb-section .dnb-p { diff --git a/packages/dnb-eufemia/src/components/icon/Icon.tsx b/packages/dnb-eufemia/src/components/icon/Icon.tsx index b8fd1b1ff52..054d5e786ea 100644 --- a/packages/dnb-eufemia/src/components/icon/Icon.tsx +++ b/packages/dnb-eufemia/src/components/icon/Icon.tsx @@ -157,6 +157,9 @@ export default function Icon(localProps: IconAllProps) { } export function getIconNameFromComponent(icon: IconProps['icon']): string { + if (React.isValidElement(icon) && icon?.type) { + icon = icon?.type as IconType + } const name = typeof icon === 'function' ? icon.name : String(icon) if (/^data:image\//.test(name)) { return null diff --git a/packages/dnb-eufemia/src/components/modal/__tests__/__snapshots__/Modal.test.tsx.snap b/packages/dnb-eufemia/src/components/modal/__tests__/__snapshots__/Modal.test.tsx.snap index 3472c02b451..168de6ea3ba 100644 --- a/packages/dnb-eufemia/src/components/modal/__tests__/__snapshots__/Modal.test.tsx.snap +++ b/packages/dnb-eufemia/src/components/modal/__tests__/__snapshots__/Modal.test.tsx.snap @@ -670,11 +670,12 @@ button.dnb-button::-moz-focus-inner { transform: translate3d(0, -0.5rem, 0); } :not(.dnb-card) .dnb-help-button__content .dnb-section { - margin-left: calc(var(--help-button-indent-width) * -1); - margin-right: calc(var(--help-button-indent-width) * -1); - border: var(--help-button-indent-width) solid var(--help-button-content-background); - border-top: none; - border-bottom: none; + --outset-left: calc( + var(--spacing-medium) + var(--help-button-indent-width) + ); + --outset-right: calc( + var(--spacing-medium) + var(--help-button-indent-width) + ); } .dnb-help-button__content.dnb-height-animation--parallax .dnb-section .dnb-p { transform: translate3d(0, 0, 0); diff --git a/packages/dnb-eufemia/src/components/modal/parts/ModalHeader.tsx b/packages/dnb-eufemia/src/components/modal/parts/ModalHeader.tsx index 85515d2eb9b..4c89c573a2b 100644 --- a/packages/dnb-eufemia/src/components/modal/parts/ModalHeader.tsx +++ b/packages/dnb-eufemia/src/components/modal/parts/ModalHeader.tsx @@ -33,7 +33,8 @@ export interface ModalHeaderProps extends Omit { title_class?: string /** - * Font size of the title (maps to `dnb-p--`) + * Font size of the title (maps to `dnb-h--`) + * Default is `large` */ size?: 'medium' | 'large' | 'x-large' | 'xx-large' } diff --git a/packages/dnb-eufemia/src/components/section/Section.tsx b/packages/dnb-eufemia/src/components/section/Section.tsx index f1956f4866a..b4a690148ae 100644 --- a/packages/dnb-eufemia/src/components/section/Section.tsx +++ b/packages/dnb-eufemia/src/components/section/Section.tsx @@ -70,6 +70,12 @@ export type SectionProps = { */ breakout?: boolean | ResponsiveProp + /** + * Define if the Card should break out negatively on larger screens. You can not use `breakout` and `outset` together. + * Defaults to `false` + */ + outset?: boolean | ResponsiveProp + /** * Define if the section should have rounded corners. Defaults to `false`. */ @@ -152,7 +158,8 @@ export function SectionParams( const { variant, - breakout = true, + breakout = !props.outset, + outset, roundedCorner, textColor, backgroundColor, @@ -188,6 +195,7 @@ export function SectionParams( 'breakout', (value) => `var(--breakout--${value ? 'on' : 'off'})` ), + ...computeStyle(outset, 'outset', (value) => (value ? '1' : '0')), ...computeStyle( roundedCorner, 'rounded-corner', diff --git a/packages/dnb-eufemia/src/components/section/SectionDocs.ts b/packages/dnb-eufemia/src/components/section/SectionDocs.ts new file mode 100644 index 00000000000..be479426af2 --- /dev/null +++ b/packages/dnb-eufemia/src/components/section/SectionDocs.ts @@ -0,0 +1,59 @@ +import { PropertiesTableProps } from '../../shared/types' + +export const SectionProperties: PropertiesTableProps = { + variant: { + doc: 'Defines the semantic purpose and subsequently the style of the visual helper. Will take precedence over the style_type property.', + type: 'string', + status: 'optional', + }, + breakout: { + doc: 'Use `true` to enable a fullscreen breakout look. Supports also media query breakpoints like `{ small: boolean }`. Defaults to `true`.', + type: 'boolean', + status: 'optional', + }, + outset: { + doc: 'Define if the Card should break out negatively on larger screens. You can not use `breakout` and `outset` together. Defaults to `false`.', + type: 'boolean', + status: 'optional', + }, + outline: { + doc: "Define a custom border color. If `true` is given, `color-black-8` is used. Use a Eufemia color. Supports also media query breakpoints like `{ small: 'black-8' }`.", + type: 'string', + status: 'optional', + }, + roundedCorner: { + doc: 'Use `true` to enable rounded corners (border-radius). Supports also media query breakpoints like `{ small: boolean }`. Defaults to `false`.', + type: 'boolean', + status: 'optional', + }, + backgroundColor: { + doc: "Define a custom background color, instead of a variant. Use a Eufemia color. Supports also media query breakpoints like `{ small: 'white' }`.", + type: 'string', + status: 'optional', + }, + dropShadow: { + doc: 'Use `true` to show the default Eufemia DropShadow. Supports also media query breakpoints like `{ small: true }`.', + type: 'boolean', + status: 'optional', + }, + textColor: { + doc: "Define a custom text color to compliment the backgroundColor. Use a Eufemia color. Supports also media query breakpoints like `{ small: 'black-80' }`.", + type: 'string', + status: 'optional', + }, + innerSpace: { + doc: "Will add a padding around the content. Supports also media query breakpoints like `{small: { top: 'medium' }}`.", + type: 'string', + status: 'optional', + }, + innerRef: { + doc: 'By providing a React Ref we can get the internally used element (DOM). E.g. `inner_ref={myRef}` by using `React.createRef()` or `React.useRef()`.', + type: 'React.Ref', + status: 'optional', + }, + '[Space](/uilib/layout/space/properties)': { + doc: 'Spacing properties like `top` or `bottom` are supported.', + type: ['string', 'object'], + status: 'optional', + }, +} diff --git a/packages/dnb-eufemia/src/components/section/__tests__/__snapshots__/Section.test.tsx.snap b/packages/dnb-eufemia/src/components/section/__tests__/__snapshots__/Section.test.tsx.snap index 19b393353c0..f1cb2dd754e 100644 --- a/packages/dnb-eufemia/src/components/section/__tests__/__snapshots__/Section.test.tsx.snap +++ b/packages/dnb-eufemia/src/components/section/__tests__/__snapshots__/Section.test.tsx.snap @@ -61,9 +61,19 @@ exports[`Section scss has to match style dependencies css 1`] = ` .dnb-section:not([style*="--breakout"]) { --breakout: var(--breakout--on); } +.dnb-section[style*="--outset"].dnb-space[style]:not(.dnb-card) { + padding-left: calc(var(--padding-left) * (1 - var(--outset))); + padding-right: calc(var(--padding-right) * (1 - var(--outset))); +} +.dnb-section[style*="--outset"]::before { + margin-left: calc(var(--outset-left, var(--padding-left)) * -1 * var(--outset)); + margin-right: calc(var(--outset-right, var(--padding-right)) * -1 * var(--outset)); + background-color: inherit; +} @media screen and (max-width: 60em) { .dnb-section { --breakout: var(--breakout--small, var(--breakout--fallback)); + --outset: var(--outset--small, var(--outset--fallback)); --background-color--value: var(--background-color--small); --text-color--value: var(--text-color--small); --outline-color: var(--outline-color--small); @@ -77,6 +87,7 @@ exports[`Section scss has to match style dependencies css 1`] = ` @media screen and (max-width: 60em) and (min-width: 40.00625em) { .dnb-section { --breakout: var(--breakout--medium, var(--breakout--fallback)); + --outset: var(--outset--medium, var(--outset--fallback)); --background-color--value: var(--background-color--medium); --text-color--value: var(--text-color--medium); --outline-color: var(--outline-color--medium); @@ -90,6 +101,7 @@ exports[`Section scss has to match style dependencies css 1`] = ` @media screen and (min-width: 60.00625em) { .dnb-section { --breakout: var(--breakout--large, var(--breakout--fallback)); + --outset: var(--outset--large, var(--outset--fallback)); --background-color--value: var(--background-color--large); --text-color--value: var(--text-color--large); --outline-color: var(--outline-color--large); diff --git a/packages/dnb-eufemia/src/components/section/style/dnb-section.scss b/packages/dnb-eufemia/src/components/section/style/dnb-section.scss index 13d6e0f7d03..ef75d829e5c 100644 --- a/packages/dnb-eufemia/src/components/section/style/dnb-section.scss +++ b/packages/dnb-eufemia/src/components/section/style/dnb-section.scss @@ -67,8 +67,28 @@ --breakout: var(--breakout--on); } + &[style*='--outset'] { + &.dnb-space[style]:not(.dnb-card) { + padding-left: calc(var(--padding-left) * calc(1 - var(--outset))); + padding-right: calc(var(--padding-right) * calc(1 - var(--outset))); + } + + &::before { + margin-left: calc( + var(--outset-left, var(--padding-left)) * -1 * var(--outset) + ); + margin-right: calc( + var(--outset-right, var(--padding-right)) * -1 * var(--outset) + ); + + // Because of the margin usage, we need to inherit the background color. + background-color: inherit; + } + } + @include allBelow(medium) { --breakout: var(--breakout--small, var(--breakout--fallback)); + --outset: var(--outset--small, var(--outset--fallback)); --background-color--value: var(--background-color--small); --text-color--value: var(--text-color--small); --outline-color: var(--outline-color--small); @@ -80,6 +100,7 @@ } @include allBetween(small, medium) { --breakout: var(--breakout--medium, var(--breakout--fallback)); + --outset: var(--outset--medium, var(--outset--fallback)); --background-color--value: var(--background-color--medium); --text-color--value: var(--text-color--medium); --outline-color: var(--outline-color--medium); @@ -91,6 +112,7 @@ } @include allAbove(medium) { --breakout: var(--breakout--large, var(--breakout--fallback)); + --outset: var(--outset--large, var(--outset--fallback)); --background-color--value: var(--background-color--large); --text-color--value: var(--text-color--large); --outline-color: var(--outline-color--large); diff --git a/packages/dnb-eufemia/src/components/skip-content/__tests__/__snapshots__/SkipContent.test.tsx.snap b/packages/dnb-eufemia/src/components/skip-content/__tests__/__snapshots__/SkipContent.test.tsx.snap index df8d760e814..2b8b3f653b1 100644 --- a/packages/dnb-eufemia/src/components/skip-content/__tests__/__snapshots__/SkipContent.test.tsx.snap +++ b/packages/dnb-eufemia/src/components/skip-content/__tests__/__snapshots__/SkipContent.test.tsx.snap @@ -624,9 +624,19 @@ button.dnb-button::-moz-focus-inner { .dnb-section:not([style*="--breakout"]) { --breakout: var(--breakout--on); } +.dnb-section[style*="--outset"].dnb-space[style]:not(.dnb-card) { + padding-left: calc(var(--padding-left) * (1 - var(--outset))); + padding-right: calc(var(--padding-right) * (1 - var(--outset))); +} +.dnb-section[style*="--outset"]::before { + margin-left: calc(var(--outset-left, var(--padding-left)) * -1 * var(--outset)); + margin-right: calc(var(--outset-right, var(--padding-right)) * -1 * var(--outset)); + background-color: inherit; +} @media screen and (max-width: 60em) { .dnb-section { --breakout: var(--breakout--small, var(--breakout--fallback)); + --outset: var(--outset--small, var(--outset--fallback)); --background-color--value: var(--background-color--small); --text-color--value: var(--text-color--small); --outline-color: var(--outline-color--small); @@ -640,6 +650,7 @@ button.dnb-button::-moz-focus-inner { @media screen and (max-width: 60em) and (min-width: 40.00625em) { .dnb-section { --breakout: var(--breakout--medium, var(--breakout--fallback)); + --outset: var(--outset--medium, var(--outset--fallback)); --background-color--value: var(--background-color--medium); --text-color--value: var(--text-color--medium); --outline-color: var(--outline-color--medium); @@ -653,6 +664,7 @@ button.dnb-button::-moz-focus-inner { @media screen and (min-width: 60.00625em) { .dnb-section { --breakout: var(--breakout--large, var(--breakout--fallback)); + --outset: var(--outset--large, var(--outset--fallback)); --background-color--value: var(--background-color--large); --text-color--value: var(--text-color--large); --outline-color: var(--outline-color--large); diff --git a/packages/dnb-eufemia/src/core/jest/jestSetupScreenshots.css b/packages/dnb-eufemia/src/core/jest/jestSetupScreenshots.css index c2aeee189ba..56a623dcfba 100644 --- a/packages/dnb-eufemia/src/core/jest/jestSetupScreenshots.css +++ b/packages/dnb-eufemia/src/core/jest/jestSetupScreenshots.css @@ -35,9 +35,19 @@ html { background: #fff; } +[data-visual-test]:has( + .dnb-section[style*='--outset--medium: 1'], + .dnb-section[style*='--outset--large: 1'] + ) { + @media (min-width: 40em) { + padding-left: 1.5rem; + padding-right: 1.5rem; + } +} + /** * Hide "Preparing requested page" popup */ gatsby-qod { opacity: 0; -} \ No newline at end of file +} diff --git a/packages/dnb-eufemia/src/elements/span/Span.tsx b/packages/dnb-eufemia/src/elements/span/Span.tsx index 4ad41011aea..a5fec218e1b 100644 --- a/packages/dnb-eufemia/src/elements/span/Span.tsx +++ b/packages/dnb-eufemia/src/elements/span/Span.tsx @@ -4,13 +4,12 @@ */ import React from 'react' -import { SpacingProps } from '../../components/space/types' -import E from '../Element' +import Typography, { TypographyProps } from '../typography/Typography' -type SpanProps = SpacingProps & React.HTMLAttributes +type SpanProps = TypographyProps const Span = React.forwardRef((props: SpanProps, ref) => ( - + )) // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/packages/dnb-eufemia/src/elements/span/SpanDocs.ts b/packages/dnb-eufemia/src/elements/span/SpanDocs.ts new file mode 100644 index 00000000000..428cfb3358a --- /dev/null +++ b/packages/dnb-eufemia/src/elements/span/SpanDocs.ts @@ -0,0 +1,6 @@ +import { PropertiesTableProps } from '../../shared/types' +import { TypographyProperties } from '../typography/TypographyDocs' + +export const SpanProperties: PropertiesTableProps = { + ...TypographyProperties, +} diff --git a/packages/dnb-eufemia/src/elements/span/__tests__/Span.screenshot.test.ts b/packages/dnb-eufemia/src/elements/span/__tests__/Span.screenshot.test.ts new file mode 100644 index 00000000000..8c216ae26a4 --- /dev/null +++ b/packages/dnb-eufemia/src/elements/span/__tests__/Span.screenshot.test.ts @@ -0,0 +1,37 @@ +/** + * Screenshot Test + * This file will not run on "test:staged" because we don't require any related files + */ + +import { + makeScreenshot, + setupPageScreenshot, +} from '../../../core/jest/jestSetupScreenshots' + +describe.each(['ui', 'sbanken'])('Span for %s', (themeName) => { + setupPageScreenshot({ + themeName, + url: '/uilib/elements/span', + }) + + it('basics', async () => { + const screenshot = await makeScreenshot({ + selector: '[data-visual-test="span-basic"]', + }) + expect(screenshot).toMatchImageSnapshot() + }) + + it('with modifiers', async () => { + const screenshot = await makeScreenshot({ + selector: '[data-visual-test="span-modifiers"]', + }) + expect(screenshot).toMatchImageSnapshot() + }) + + it('all sizes and weights', async () => { + const screenshot = await makeScreenshot({ + selector: '[data-visual-test="span-sizes"]', + }) + expect(screenshot).toMatchImageSnapshot() + }) +}) diff --git a/packages/dnb-eufemia/src/elements/span/__tests__/Span.test.tsx b/packages/dnb-eufemia/src/elements/span/__tests__/Span.test.tsx new file mode 100644 index 00000000000..efe4ec62544 --- /dev/null +++ b/packages/dnb-eufemia/src/elements/span/__tests__/Span.test.tsx @@ -0,0 +1,66 @@ +/** + * Element Test + * + */ + +import React from 'react' +import { axeComponent } from '../../../core/jest/jestSetup' +import Span from '../Span' +import { render } from '@testing-library/react' + +describe('Span element', () => { + it('size also sets line-height when not defined', () => { + render() + const element = document.querySelector('.dnb-t__size--large') + + expect(Array.from(element.classList)).toEqual([ + 'dnb-t__line-height--large', + 'dnb-t__size--large', + 'dnb-span', + ]) + }) + it('sets only line-height when size is not defined', () => { + render() + const element = document.querySelector('.dnb-t__line-height--large') + + expect(Array.from(element.classList)).toEqual([ + 'dnb-t__line-height--large', + 'dnb-span', + ]) + }) + it('has correct style when several modifiers are defined', () => { + render( + + ) + const element = document.querySelector('.dnb-t__size--small') + + expect(Array.from(element.classList)).toEqual([ + 'dnb-t__line-height--xx-large', + 'dnb-t__size--small', + 'dnb-t__align--center', + 'dnb-t__family--monospace', + 'dnb-t__weight--medium', + 'dnb-t__decoration--underline', + 'dnb-span', + ]) + }) + it('has correct style when medium is set to true', () => { + render() + const element = document.querySelector('.dnb-t__weight--bold') + expect(Array.from(element.classList)).toEqual([ + 'dnb-t__weight--bold', + 'dnb-span', + ]) + }) + it('should validate with ARIA rules as a span element', async () => { + const Comp = render() + expect(await axeComponent(Comp)).toHaveNoViolations() + }) +}) diff --git a/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-sbanken-all-sizes-and-weights.snap.png b/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-sbanken-all-sizes-and-weights.snap.png new file mode 100644 index 00000000000..6efdd4f0d94 Binary files /dev/null and b/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-sbanken-all-sizes-and-weights.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-sbanken-basics.snap.png b/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-sbanken-basics.snap.png new file mode 100644 index 00000000000..a1e093ad1dc Binary files /dev/null and b/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-sbanken-basics.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-sbanken-with-modifiers.snap.png b/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-sbanken-with-modifiers.snap.png new file mode 100644 index 00000000000..de5d835369a Binary files /dev/null and b/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-sbanken-with-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-ui-all-sizes-and-weights.snap.png b/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-ui-all-sizes-and-weights.snap.png new file mode 100644 index 00000000000..a68410918a1 Binary files /dev/null and b/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-ui-all-sizes-and-weights.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-ui-basics.snap.png b/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-ui-basics.snap.png new file mode 100644 index 00000000000..dbe71beac53 Binary files /dev/null and b/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-ui-basics.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-ui-with-modifiers.snap.png b/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-ui-with-modifiers.snap.png new file mode 100644 index 00000000000..10037147b5e Binary files /dev/null and b/packages/dnb-eufemia/src/elements/span/__tests__/__image_snapshots__/span-for-ui-with-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/Ingress.tsx b/packages/dnb-eufemia/src/elements/typography/Ingress.tsx index 60980b3ef38..35fe9d48c17 100644 --- a/packages/dnb-eufemia/src/elements/typography/Ingress.tsx +++ b/packages/dnb-eufemia/src/elements/typography/Ingress.tsx @@ -5,7 +5,7 @@ import React from 'react' import P, { PProps } from './P' -const Ingress = (props: PProps) =>

+const Ingress = (props: PProps) =>

Ingress._supportsSpacingProps = true diff --git a/packages/dnb-eufemia/src/elements/typography/P.tsx b/packages/dnb-eufemia/src/elements/typography/P.tsx index f530acc891f..f5cb7897ca0 100644 --- a/packages/dnb-eufemia/src/elements/typography/P.tsx +++ b/packages/dnb-eufemia/src/elements/typography/P.tsx @@ -5,97 +5,112 @@ import React, { createContext, useContext } from 'react' import classnames from 'classnames' -import { SpacingProps } from '../../components/space/types' -import type { DynamicElement } from '../../shared/types' -import E from '../Element' +import Typography, { TypographySize, TypographyProps } from './Typography' -export type PSize = - | 'x-small' - | 'small' - | 'basis' - | 'medium' - | 'large' - | 'x-large' - | 'xx-large' +/** @deprecated use TypographySize instead */ +export type PSize = TypographySize -export type PProps = SpacingProps & - React.HTMLAttributes & { - /** - * Defines the Element Type, like "p" - * Default: p - */ - element?: DynamicElement | 'p' - /** - * Tells the component to use the medium font-weight styling dnb-p--medium defined in paragraphStyle - typography-mixins.scss. Find more details here https://eufemia.dnb.no/uilib/typography/font-weight/ - */ - medium?: boolean - /** - * Tells the component to use the bold font-weight styling dnb-p--bold defined in paragraphStyle - typography-mixins.scss. Find more details here https://eufemia.dnb.no/uilib/typography/font-weight/ - */ - bold?: boolean - /** - * Sets the font size based on size classes defined in paragraphStyle - typography-mixins.scss. For more detailed information go here: https://eufemia.dnb.no/uilib/typography/font-size/ - */ - size?: PSize - /** - * A string containing a combination of modifiers, used to set both font-size and weight in one property. e.g. "x-small bold" would make the paragraph extra small and bold. - * Works as a flexible alternative to setting the medium, small, bold and size props. - * List of modifiers can be found at https://eufemia.dnb.no/uilib/typography/font-size/ and https://eufemia.dnb.no/uilib/typography/font-weight/ - */ - modifier?: string - } +export type PProps = TypographyProps & { + /** + * Tells the component to use the medium font-weight styling dnb-t__weight--medium defined in paragraphStyle - typography-mixins.scss. Find more details here https://eufemia.dnb.no/uilib/typography/font-weight/ + * @deprecated use the `weight` prop instead + */ + medium?: boolean + /** + * Tells the component to use the bold font-weight styling dnb-t__weight--bold defined in paragraphStyle - typography-mixins.scss. Find more details here https://eufemia.dnb.no/uilib/typography/font-weight/ + * @deprecated use the `weight` prop instead + */ + bold?: boolean + /** + * A string containing a combination of modifiers, used to set both font-size and weight in one property. e.g. "x-small bold" would make the paragraph extra small and bold. + * Works as a flexible alternative to setting the medium, bold and size props. + * List of modifiers can be found at https://eufemia.dnb.no/uilib/typography/font-size/ and https://eufemia.dnb.no/uilib/typography/font-weight/ + * @deprecated only font weights "bold" and "medium" and sizes "x-small" and "small" are supported. Use the `size` and `weight` props instead. + */ + modifier?: string +} function P(props: PProps) { const { - modifier, + remainingModifiers, element = 'p', className, - medium, - bold, - size, - children, ...rest - } = props + } = handleDeprecatedProps(props) - const allModifiers = [medium && 'medium', bold && 'bold'] const paragraphContext = useContext(ParagraphContext) - if (modifier) { - modifier - .split(/\s/g) - .forEach((modifier) => allModifiers.push(modifier)) - } - - const modifierString = allModifiers - .filter(Boolean) - .reduce((acc, cur) => { - if (['x-small', 'small'].includes(cur)) { - return `${acc} dnb-p__size--${cur}` - } - + const deprecatedModifierString = remainingModifiers.reduce( + (acc, cur) => { + // This entire string could possibly be deprecated. There are no remaining modifiers + // that should be supported, but technically this allows for any class "dnb-p--[modifier]". + // But "dnb-p--lead" is the only class that we have, and it's not supposed to be added here. return `${acc} dnb-p--${cur}` - }, '') + }, + '' + ) return ( - - {children} - + {...rest} + /> ) } +const handleDeprecatedProps = ({ + weight, + size, + modifier, + bold, + medium, + ...rest +}: PProps): TypographyProps & { + remainingModifiers?: string[] +} => { + let oldWeight + let oldSize + + const allModifiers = [bold && 'bold', medium && 'medium'] + if (modifier) { + modifier + .split(/\s/g) + .forEach((modifier) => allModifiers.push(modifier)) + } + + const remainingModifiers = allModifiers.filter(Boolean).filter((cur) => { + if (['x-small'].includes(cur)) { + oldSize = 'x-small' + } else if (['small'].includes(cur)) { + oldSize = oldSize || 'small' + } else if (['medium'].includes(cur)) { + oldWeight = oldWeight || 'medium' + } else if (['bold'].includes(cur)) { + oldWeight = 'bold' + } else { + // There should never be anything here unless it's a custom modifier. + return true + } + return false + }, []) + + return { + weight: weight || oldWeight, + size: oldSize && size !== 'x-small' ? oldSize : size, + remainingModifiers, + ...rest, + } +} + P._supportsSpacingProps = true export default P diff --git a/packages/dnb-eufemia/src/elements/typography/PDocs.ts b/packages/dnb-eufemia/src/elements/typography/PDocs.ts index 8b97070e069..e5d7b74539d 100644 --- a/packages/dnb-eufemia/src/elements/typography/PDocs.ts +++ b/packages/dnb-eufemia/src/elements/typography/PDocs.ts @@ -1,34 +1,21 @@ import { PropertiesTableProps } from '../../shared/types' +import { TypographyProperties } from './TypographyDocs' export const ParagraphProperties: PropertiesTableProps = { - element: { - doc: 'Defines the Element Type, like `p`.', - type: ['HTMLElement', 'string'], - status: 'optional', - }, + ...TypographyProperties, medium: { - doc: 'Tells the component to use the medium font-weight styling `dnb-p--medium`. More details [here](/uilib/typography/font-weight).', + doc: 'Tells the component to use the medium font-weight styling `dnb-t__weight--medium`. More details [here](/uilib/typography/font-weight).', type: 'boolean', - status: 'optional', + status: 'deprecated', }, bold: { - doc: 'Tells the component to use the bold font-weight styling class `dnb-p--bold`. More details [here](/uilib/typography/font-weight).', + doc: 'Tells the component to use the bold font-weight styling class `dnb-t__weight--bold`. More details [here](/uilib/typography/font-weight).', type: 'boolean', - status: 'optional', - }, - size: { - doc: 'Sets the font size based on the following sizes: `x-small`, `small`, `basis`, `medium`, `large`, `x-large` or `xx-large`.', - type: 'string', - status: 'optional', + status: 'deprecated', }, modifier: { - doc: 'String containing a combination of modifiers, used to set both font-size and weight in one property. e.g. `x-small bold` would make the paragraph extra small and bold.', + doc: 'String containing a combination of modifiers, used to set both font-size and weight in one property. e.g. `x-small medium` would make the paragraph extra small and medium.', type: 'string', - status: 'optional', - }, - '[Space](/uilib/layout/space/properties)': { - doc: 'Spacing properties like `top` or `bottom` are supported.', - type: ['string', 'object'], - status: 'optional', + status: 'deprecated', }, } diff --git a/packages/dnb-eufemia/src/elements/typography/Typography.tsx b/packages/dnb-eufemia/src/elements/typography/Typography.tsx new file mode 100644 index 00000000000..6178cbc0a29 --- /dev/null +++ b/packages/dnb-eufemia/src/elements/typography/Typography.tsx @@ -0,0 +1,101 @@ +/** + * HTML Element + * + */ + +import React from 'react' +import classnames from 'classnames' +import { SpacingProps } from '../../components/space/types' +import type { DynamicElement } from '../../shared/types' +import E from '../Element' + +export type TypographySize = + | 'x-small' + | 'small' + | 'basis' + | 'medium' + | 'large' + | 'x-large' + | 'xx-large' + +export type TypographyAlign = 'center' | 'left' | 'right' +export type TypographyFamily = 'basis' | 'heading' | 'monospace' +export type TypographyWeight = 'regular' | 'medium' | 'bold' +export type TypographyDecoration = 'underline' +export type TypographySlant = 'italic' + +export type TypographyProps< + ElementType extends HTMLElement = HTMLElement, +> = SpacingProps & + React.HTMLAttributes & { + /** + * Defines the Element Type, like "p". + */ + element?: DynamicElement + /** + * Sets the font size, also sets the line-height if `line` prop is not set + */ + size?: TypographySize + /** + * Sets the line height, will use same value as `size` if not set. + */ + lineHeight?: TypographySize + /** + * Sets the text alignment + */ + align?: TypographyAlign + /** + * Sets the font family + */ + family?: TypographyFamily + /** + * Sets the font weight + */ + weight?: TypographyWeight + /** + * Sets the font decoration + */ + decoration?: TypographyDecoration + /** + * Sets the font style + */ + slant?: TypographySlant + } + +type TypographyInternalProps = { + innerRef?: React.RefObject | React.ForwardedRef +} + +const Typography = ({ + element = 'p', + className, + size, + lineHeight, + align, + family, + weight, + decoration, + slant, + ...props +}: TypographyProps & TypographyInternalProps) => { + return ( + + ) +} + +Typography._supportsSpacingProps = true + +export default Typography diff --git a/packages/dnb-eufemia/src/elements/typography/TypographyDocs.ts b/packages/dnb-eufemia/src/elements/typography/TypographyDocs.ts new file mode 100644 index 00000000000..b4318a926f7 --- /dev/null +++ b/packages/dnb-eufemia/src/elements/typography/TypographyDocs.ts @@ -0,0 +1,65 @@ +import { PropertiesTableProps } from '../../shared/types' + +export const TypographyProperties: PropertiesTableProps = { + element: { + doc: 'Defines the Element Type, like `p`.', + type: ['HTMLElement', 'string'], + status: 'optional', + }, + size: { + doc: 'Sets the font size, also sets the line-height if `lineHeight` prop is not set.', + type: [ + `'x-small'`, + `'small'`, + `'basis'`, + `'medium'`, + `'large'`, + `'x-large'`, + `'xx-large'`, + ], + status: 'optional', + }, + lineHeight: { + doc: 'Sets the line height, will use same value as `size` if not set.', + type: [ + `'x-small'`, + `'small'`, + `'basis'`, + `'medium'`, + `'large'`, + `'x-large'`, + `'xx-large'`, + ], + status: 'optional', + }, + align: { + doc: 'Sets the text alignment.', + type: [`'center'`, `'left'`, `'right'`], + status: 'optional', + }, + family: { + doc: 'Sets the font family.', + type: [`'basis'`, `'heading'`, `'monospace'`], + status: 'optional', + }, + weight: { + doc: 'Sets the font weight.', + type: [`'regular'`, `'medium'`], + status: 'optional', + }, + decoration: { + doc: 'Sets the font decoration.', + type: `'underline'`, + status: 'optional', + }, + slant: { + doc: 'Sets the font style.', + type: `'italic'`, + status: 'optional', + }, + '[Space](/uilib/layout/space/properties)': { + doc: 'Spacing properties like `top` or `bottom` are supported.', + type: ['string', 'object'], + status: 'optional', + }, +} diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/P.test.tsx b/packages/dnb-eufemia/src/elements/typography/__tests__/P.test.tsx index b5bcb2a4a22..2c7a6ae13c8 100644 --- a/packages/dnb-eufemia/src/elements/typography/__tests__/P.test.tsx +++ b/packages/dnb-eufemia/src/elements/typography/__tests__/P.test.tsx @@ -45,56 +45,128 @@ describe('P element', () => { expect(element.tagName).toBe('STRONG') }) - it('has correct size when size is defined', () => { - render(

) - const element = document.querySelector('.dnb-p__size--large') + it('can set className', () => { + render(

) + const element = document.querySelector('.dnb-p') expect(Array.from(element.classList)).toEqual([ 'dnb-p', - 'dnb-p__size--large', + 'my-class', + 'dnb-t__weight--regular', ]) }) - it('has correct style when size and a modifier is defined', () => { - render(

) - const element = document.querySelector('.dnb-p__size--medium') + it('has correct size and line height when size is defined', () => { + render(

) + const element = document.querySelector('.dnb-t__size--large') expect(Array.from(element.classList)).toEqual([ 'dnb-p', - 'dnb-p--medium', - 'dnb-p__size--medium', + 'dnb-t__line-height--large', + 'dnb-t__size--large', ]) }) - it('has correct style when several modifiers are defined', () => { - render(

) - const element = document.querySelector('.dnb-p__size--small') + it('has correct style when bold is set to true', () => { + render(

) + const element = document.querySelector('.dnb-t__weight--bold') expect(Array.from(element.classList)).toEqual([ 'dnb-p', - 'dnb-p--medium', - 'dnb-p__size--small', + 'dnb-t__weight--bold', ]) }) - it('has correct style when medium is set to true', () => { - render(

) - const element = document.querySelector('.dnb-p--medium') + it('has correct style when several modifiers are defined', () => { + render( +

+ ) + const element = document.querySelector('.dnb-p') + expect(Array.from(element.classList)).toEqual([ 'dnb-p', - 'dnb-p--medium', + 'dnb-t__line-height--xx-large', + 'dnb-t__size--small', + 'dnb-t__align--center', + 'dnb-t__family--monospace', + 'dnb-t__weight--medium', + 'dnb-t__decoration--underline', ]) }) - it('has correct style when bold is set to true', () => { - render(

) - const element = document.querySelector('.dnb-p--bold') - - expect(Array.from(element.classList)).toEqual(['dnb-p', 'dnb-p--bold']) - }) - it('should validate with ARIA rules as a p element', async () => { const Comp = render(

) expect(await axeComponent(Comp)).toHaveNoViolations() }) + + describe('deprecated behaviour', () => { + it('can set className and modifier', () => { + render(

) + const element = document.querySelector('.dnb-p') + + expect(Array.from(element.classList)).toEqual([ + 'dnb-p', + 'dnb-p--my-modifier', + 'my-class', + ]) + }) + it('has correct style when size and a modifier is defined', () => { + render(

) + const element = document.querySelector('.dnb-t__size--medium') + + expect(Array.from(element.classList)).toEqual([ + 'dnb-p', + 'dnb-t__line-height--medium', + 'dnb-t__size--medium', + 'dnb-t__weight--medium', + ]) + }) + it('has correct style when several modifiers are defined', () => { + render(

) + const element = document.querySelector('.dnb-t__size--small') + + expect(Array.from(element.classList)).toEqual([ + 'dnb-p', + 'dnb-t__line-height--small', + 'dnb-t__size--small', + 'dnb-t__weight--medium', + ]) + }) + it('has correct style when several modifiers conflict', () => { + render(

) + const element = document.querySelector('.dnb-t__size--x-small') + + expect(Array.from(element.classList)).toEqual([ + 'dnb-p', + 'dnb-t__line-height--x-small', + 'dnb-t__size--x-small', + 'dnb-t__weight--bold', + ]) + }) + it('has correct style when medium is set to true', () => { + render(

) + const element = document.querySelector('.dnb-t__weight--medium') + expect(Array.from(element.classList)).toEqual([ + 'dnb-p', + 'dnb-t__weight--medium', + ]) + }) + + it('has correct style when bold is set to true', () => { + render(

) + const element = document.querySelector('.dnb-t__weight--bold') + + expect(Array.from(element.classList)).toEqual([ + 'dnb-p', + 'dnb-t__weight--bold', + ]) + }) + }) }) diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/Paragraph.screenshot.test.ts b/packages/dnb-eufemia/src/elements/typography/__tests__/Paragraph.screenshot.test.ts index dde88c9ed42..72d15a759a7 100644 --- a/packages/dnb-eufemia/src/elements/typography/__tests__/Paragraph.screenshot.test.ts +++ b/packages/dnb-eufemia/src/elements/typography/__tests__/Paragraph.screenshot.test.ts @@ -14,23 +14,59 @@ describe.each(['ui', 'sbanken'])('Paragraph for %s', (themeName) => { url: '/uilib/elements/paragraph', }) - it('have to match the paragraph example', async () => { + it('have to match the paragraph with weight modifiers', async () => { const screenshot = await makeScreenshot({ - selector: '[data-visual-test="paragraph-default"]', + selector: '[data-visual-test="paragraph-modifiers-weight"]', }) expect(screenshot).toMatchImageSnapshot() }) - it('have to match the paragraph with small text', async () => { + it('have to match the paragraph with size modifiers', async () => { const screenshot = await makeScreenshot({ - selector: '[data-visual-test="paragraph-small"]', + selector: '[data-visual-test="paragraph-modifiers-size"]', + }) + expect(screenshot).toMatchImageSnapshot() + }) + + it('have to match the paragraph with align modifiers', async () => { + const screenshot = await makeScreenshot({ + style: { width: '30rem' }, + selector: '[data-visual-test="paragraph-modifiers-align"]', }) expect(screenshot).toMatchImageSnapshot() }) - it('have to match the paragraph with modifiers', async () => { + it('have to match the paragraph with family modifiers', async () => { const screenshot = await makeScreenshot({ - selector: '[data-visual-test="paragraph-modifiers"]', + selector: '[data-visual-test="paragraph-modifiers-family"]', + }) + expect(screenshot).toMatchImageSnapshot() + }) + + it('have to match the paragraph with line modifiers', async () => { + const screenshot = await makeScreenshot({ + selector: '[data-visual-test="paragraph-modifiers-line"]', + }) + expect(screenshot).toMatchImageSnapshot() + }) + + it('have to match the paragraph with other modifiers', async () => { + const screenshot = await makeScreenshot({ + selector: '[data-visual-test="paragraph-modifiers-other"]', + }) + expect(screenshot).toMatchImageSnapshot() + }) + + it('have to match the paragraph example', async () => { + const screenshot = await makeScreenshot({ + selector: '[data-visual-test="paragraph-default"]', + }) + expect(screenshot).toMatchImageSnapshot() + }) + + it('have to match the paragraph with small text', async () => { + const screenshot = await makeScreenshot({ + selector: '[data-visual-test="paragraph-small"]', }) expect(screenshot).toMatchImageSnapshot() }) diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-additional-elements.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-additional-elements.snap.png index 9cc37d5f375..93c1f8984d0 100644 Binary files a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-additional-elements.snap.png and b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-additional-elements.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-align-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-align-modifiers.snap.png new file mode 100644 index 00000000000..06f36931f7b Binary files /dev/null and b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-align-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-family-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-family-modifiers.snap.png new file mode 100644 index 00000000000..bb8d7320973 Binary files /dev/null and b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-family-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-line-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-line-modifiers.snap.png new file mode 100644 index 00000000000..2c55f6c9bd0 Binary files /dev/null and b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-line-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-modifiers.snap.png deleted file mode 100644 index 07c58427cc7..00000000000 Binary files a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-modifiers.snap.png and /dev/null differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-other-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-other-modifiers.snap.png new file mode 100644 index 00000000000..6cec9501b17 Binary files /dev/null and b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-other-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-size-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-size-modifiers.snap.png new file mode 100644 index 00000000000..3cc42bd5995 Binary files /dev/null and b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-size-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-weight-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-weight-modifiers.snap.png new file mode 100644 index 00000000000..c3ec48554cb Binary files /dev/null and b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-sbanken-have-to-match-the-paragraph-with-weight-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-align-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-align-modifiers.snap.png new file mode 100644 index 00000000000..5f3022cb912 Binary files /dev/null and b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-align-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-family-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-family-modifiers.snap.png new file mode 100644 index 00000000000..98f546c66b0 Binary files /dev/null and b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-family-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-line-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-line-modifiers.snap.png new file mode 100644 index 00000000000..a359955777f Binary files /dev/null and b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-line-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-modifiers.snap.png deleted file mode 100644 index 6a286fd4bf2..00000000000 Binary files a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-modifiers.snap.png and /dev/null differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-other-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-other-modifiers.snap.png new file mode 100644 index 00000000000..9245dae4eff Binary files /dev/null and b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-other-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-size-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-size-modifiers.snap.png new file mode 100644 index 00000000000..5031d192b5a Binary files /dev/null and b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-size-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-weight-modifiers.snap.png b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-weight-modifiers.snap.png new file mode 100644 index 00000000000..979a5faee26 Binary files /dev/null and b/packages/dnb-eufemia/src/elements/typography/__tests__/__image_snapshots__/paragraph-for-ui-have-to-match-the-paragraph-with-weight-modifiers.snap.png differ diff --git a/packages/dnb-eufemia/src/elements/typography/style/_dnb-t.scss b/packages/dnb-eufemia/src/elements/typography/style/_dnb-t.scss new file mode 100644 index 00000000000..5c63b49aeb8 --- /dev/null +++ b/packages/dnb-eufemia/src/elements/typography/style/_dnb-t.scss @@ -0,0 +1,93 @@ +/* + * Typography + * Universal set of helper classes that do not have a specific element. + * The class ".dnb-t" does nothing, only it's modifiers ".dnb-t__[***]" do. + */ +.dnb-t { + // size + &__size--xx-large { + font-size: var(--font-size-xx-large); + } + &__size--x-large { + font-size: var(--font-size-x-large); + } + &__size--large { + font-size: var(--font-size-large); + } + &__size--medium { + font-size: var(--font-size-medium); + } + &__size--basis { + font-size: var(--font-size-basis); + } + &__size--small { + font-size: var(--font-size-small); + } + &__size--x-small { + font-size: var(--font-size-x-small); + } + + // line height + &__line-height--xx-large { + line-height: var(--line-height-xx-large); + } + &__line-height--x-large { + line-height: var(--line-height-x-large); + } + &__line-height--large { + line-height: var(--line-height-large); + } + &__line-height--medium { + line-height: var(--line-height-medium); + } + &__line-height--basis { + line-height: var(--line-height-basis); + } + &__line-height--small { + line-height: var(--line-height-small); + } + &__line-height--x-small { + line-height: var(--line-height-x-small); + } + + // weight + &__weight--regular { + font-weight: var(--font-weight-regular); + } + &__weight--medium { + font-weight: var(--font-weight-medium); + } + &__weight--bold { + font-weight: var(--font-weight-bold); + } + + // alignement + &__align--center { + text-align: center; + } + &__align--left { + text-align: left; + } + &__align--right { + text-align: right; + } + + // family + &__family--default { + font-family: var(--font-family-default); + } + &__family--heading { + font-family: var(--font-family-heading); + } + &__family--monospace { + font-family: var(--font-family-monospace); + } + + // underline / italic + &__decoration--underline { + text-decoration: underline; + } + &__slant--italic { + font-style: italic; + } +} diff --git a/packages/dnb-eufemia/src/elements/typography/style/dnb-typography.scss b/packages/dnb-eufemia/src/elements/typography/style/dnb-typography.scss index 5fba5f66c3c..63c8ff1b07a 100644 --- a/packages/dnb-eufemia/src/elements/typography/style/dnb-typography.scss +++ b/packages/dnb-eufemia/src/elements/typography/style/dnb-typography.scss @@ -7,7 +7,6 @@ @import './typography-mixins.scss'; @include typographySelectors() { - --typography-h-default-font-family: var(--font-family-default); --typography-h-default-font-weight: var(--font-weight-medium); // Heading xx-large @@ -94,6 +93,8 @@ sub { @include paragraphStyle(); } +@import './dnb-t.scss'; + // Tables .dnb-table { b, diff --git a/packages/dnb-eufemia/src/elements/typography/style/themes/dnb-typography-theme-sbanken.scss b/packages/dnb-eufemia/src/elements/typography/style/themes/dnb-typography-theme-sbanken.scss index 50064e93b27..1d13f27f1d1 100644 --- a/packages/dnb-eufemia/src/elements/typography/style/themes/dnb-typography-theme-sbanken.scss +++ b/packages/dnb-eufemia/src/elements/typography/style/themes/dnb-typography-theme-sbanken.scss @@ -18,7 +18,6 @@ } @include typography.typographySelectors() { - --typography-h-default-font-family: var(--sb-font-family-headings); --typography-h-xx-large-weight: var(--font-weight-regular); --typography-h-x-large-weight: var(--font-weight-regular); --typography-h-large-small-font-size: var(--sb-font-size-medium--plus); diff --git a/packages/dnb-eufemia/src/elements/typography/style/typography-mixins.scss b/packages/dnb-eufemia/src/elements/typography/style/typography-mixins.scss index 34399faf355..04fadd65786 100644 --- a/packages/dnb-eufemia/src/elements/typography/style/typography-mixins.scss +++ b/packages/dnb-eufemia/src/elements/typography/style/typography-mixins.scss @@ -96,7 +96,7 @@ margin: 0; } - font-family: var(--typography-h-default-font-family); + font-family: var(--font-family-heading); // make icons inside heading responsive to the heading size .dnb-icon--default { @@ -240,13 +240,29 @@ &--lead { @include typography_lead(); } + b, + strong { + font-weight: var(--font-weight-medium); + } + // is still needed for backwards compatibility when ".dnp-p" was used for all typography + @include paragraphDeprecated(); - &--medium { + & > small { + font-size: var(--font-size-small); + line-height: var(--line-height-small); + } + + & > cite { font-weight: var(--font-weight-medium); + line-height: var(--line-height-basis); + font-style: italic; } +} - b, - strong { +// should use the .dnb-t classes instead +@mixin paragraphDeprecated() { + // weights + &--medium { font-weight: var(--font-weight-medium); } @@ -254,6 +270,7 @@ font-weight: var(--font-weight-bold); } + // sizes and line-heights &__size--xx-large { font-size: var(--font-size-xx-large); line-height: var(--line-height-xx-large); @@ -279,24 +296,17 @@ line-height: var(--line-height-medium); } - &--small ,// backwards compatibility - &__size--small, - & > small { + &--small, // backwards compatibility + &__size--small { font-size: var(--font-size-small); line-height: var(--line-height-small); } - &--x-small ,// backwards compatibility - &__size--x-small { + &--x-small, // backwards compatibility + &__size--x-small { font-size: var(--font-size-x-small); line-height: var(--line-height-x-small); } - - & > cite { - font-weight: var(--font-weight-medium); - line-height: var(--line-height-basis); - font-style: italic; - } } @mixin headingSpacing_xx-large() { diff --git a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx index ae0e7f08457..8d7660d0a9a 100644 --- a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx @@ -603,7 +603,10 @@ export default function Provider( for (const path in fieldPropsRef.current) { if (mountedFieldsRef.current[path]?.isMounted) { const props = fieldPropsRef.current[path] - if (isAsync(props.validator) || isAsync(props.onBlurValidator)) { + if ( + isAsync(props.onChangeValidator) || + isAsync(props.onBlurValidator) + ) { return true } } diff --git a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx index eebcf937713..dbb5210eb6b 100644 --- a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx @@ -1082,7 +1082,7 @@ describe('DataContext.Provider', () => { expect(onSubmit).toHaveBeenCalledTimes(1) }) - describe('should evaluate long validator and onBlurValidator before continue with async onSubmit', () => { + describe('should evaluate long onChangeValidator and onBlurValidator before continue with async onSubmit', () => { let eventsStart = [] let eventsEnd = [] @@ -1110,12 +1110,12 @@ describe('DataContext.Provider', () => { eventsEnd.push('onChangeField') } - const validator = async () => { - eventsStart.push('validator') + const onChangeValidator = async () => { + eventsStart.push('onChangeValidator') await wait(10) - eventsEnd.push('validator') + eventsEnd.push('onChangeValidator') } const onBlurValidator = async () => { @@ -1135,7 +1135,7 @@ describe('DataContext.Provider', () => { @@ -1152,7 +1152,7 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(eventsStart).toEqual([ - 'validator', + 'onChangeValidator', 'onChangeForm', 'onChangeField', ]) @@ -1168,7 +1168,7 @@ describe('DataContext.Provider', () => { @@ -1186,12 +1186,12 @@ describe('DataContext.Provider', () => { await wait(100) expect(eventsStart).toEqual([ - 'validator', + 'onChangeValidator', 'onBlurValidator', 'onSubmit', ]) expect(eventsEnd).toEqual([ - 'validator', + 'onChangeValidator', 'onBlurValidator', 'onSubmit', ]) @@ -1203,10 +1203,10 @@ describe('DataContext.Provider', () => { await wait(10) return { info: 'Info message' } as const }) - const validator = jest.fn(async (value) => { + const onChangeValidator = jest.fn(async (value) => { await wait(10) - if (value === 'validator-error') { - return new Error('validator-error') + if (value === 'onChangeValidator-error') { + return new Error('onChangeValidator-error') } }) const onBlurValidator = jest.fn(async (value) => { @@ -1220,7 +1220,7 @@ describe('DataContext.Provider', () => { @@ -1250,12 +1250,12 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(0) expect(onBlurValidator).toHaveBeenCalledTimes(0) - expect(validator).toHaveBeenCalledTimes(0) + expect(onChangeValidator).toHaveBeenCalledTimes(0) }) // Use fireEvent over userEvent, because of its sync nature fireEvent.change(input, { - target: { value: 'validator-error' }, + target: { value: 'onChangeValidator-error' }, }) await waitFor(() => { @@ -1266,13 +1266,13 @@ describe('DataContext.Provider', () => { const status = document.querySelector( '.dnb-forms-field-block .dnb-form-status' ) - expect(status).toHaveTextContent('validator-error') + expect(status).toHaveTextContent('onChangeValidator-error') }) await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(0) expect(onBlurValidator).toHaveBeenCalledTimes(0) - expect(validator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledTimes(1) }) // Use fireEvent over userEvent, because of its sync nature @@ -1294,7 +1294,7 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(0) expect(onBlurValidator).toHaveBeenCalledTimes(0) - expect(validator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenCalledTimes(2) }) // Use fireEvent over userEvent, because of its sync nature @@ -1309,7 +1309,7 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(0) expect(onBlurValidator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenCalledTimes(2) }) await userEvent.click(submitButton) @@ -1317,7 +1317,7 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(0) expect(onBlurValidator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenCalledTimes(2) }) await waitFor(() => { @@ -1345,20 +1345,20 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(1) expect(onBlurValidator).toHaveBeenCalledTimes(3) - expect(validator).toHaveBeenCalledTimes(12) + expect(onChangeValidator).toHaveBeenCalledTimes(12) }) }) - it('should set "formState" to "pending" when "validator" is async', async () => { + it('should set "formState" to "pending" when "onChangeValidator" is async', async () => { const result = createRef() - const validator = async () => { + const onChangeValidator = async () => { return new Error('My error') } const { rerender } = render( - + ) @@ -1375,14 +1375,14 @@ describe('DataContext.Provider', () => { expect(result.current.formState).toBeUndefined() }) - const syncValidator = () => { + const syncOnChangeValidator = () => { return new Error('My error') } rerender( - + ) @@ -1496,7 +1496,7 @@ describe('DataContext.Provider', () => { .fn() .mockImplementation(async () => null) - const validator = debounceAsync(async (value) => { + const onChangeValidator = debounceAsync(async (value) => { await wait(400) if (value === 'invalid') { return Error('My error') @@ -1508,7 +1508,7 @@ describe('DataContext.Provider', () => { @@ -1593,7 +1593,7 @@ describe('DataContext.Provider', () => { }) }) - it('should emit onChange only when validator is evaluated successfully', async () => { + it('should emit onChange only when onChangeValidator is evaluated successfully', async () => { const onChangeContext = jest.fn().mockImplementation(async () => { await wait(10) }) @@ -1601,25 +1601,27 @@ describe('DataContext.Provider', () => { await wait(10) }) - const validator = jest.fn().mockImplementation(async (value) => { - /** - * It seems that this test on CI fails during way slower performance. - * The higher timeout is to ensure the typed value will be handle by the async revalidation, even the value was valid when continue typing. - * - * The slower the performance, the higher the timeout needs to be. - */ - await wait(60) - if (value !== 'valid') { - return Error(`value: ${value}`) - } - }) + const onChangeValidator = jest + .fn() + .mockImplementation(async (value) => { + /** + * It seems that this test on CI fails during way slower performance. + * The higher timeout is to ensure the typed value will be handle by the async revalidation, even the value was valid when continue typing. + * + * The slower the performance, the higher the timeout needs to be. + */ + await wait(60) + if (value !== 'valid') { + return Error(`value: ${value}`) + } + }) render( @@ -1986,7 +1988,7 @@ describe('DataContext.Provider', () => { }) }) - it('should fulfill async validator before the form and field event', async () => { + it('should fulfill async onChangeValidator before the form and field event', async () => { const onChangeForm: OnChange = async ({ myField }) => { return { info: 'onChangeForm-info', @@ -2003,7 +2005,7 @@ describe('DataContext.Provider', () => { Error('onChangeField-error'), } } - const validator = debounceAsync(async (value) => { + const onChangeValidator = debounceAsync(async (value) => { if (value === 'invalid') { return Error('My error') } @@ -2015,7 +2017,7 @@ describe('DataContext.Provider', () => { label="My label" path="/myField" onChange={onChangeField} - validator={validator} + onChangeValidator={onChangeValidator} /> @@ -2056,9 +2058,9 @@ describe('DataContext.Provider', () => { it('should show indicator during all async operations', async () => { const events = [] - const validator = debounceAsync(async () => { + const onChangeValidator = debounceAsync(async () => { await wait(101) - events.push('validator') + events.push('onChangeValidator') }) const onChangeForm: OnChange = async () => { await wait(102) @@ -2075,7 +2077,7 @@ describe('DataContext.Provider', () => { label="My label" path="/myField" onChange={onChangeField} - validator={validator} + onChangeValidator={onChangeValidator} /> ) @@ -2088,14 +2090,14 @@ describe('DataContext.Provider', () => { await userEvent.type(input, '123') await waitFor(() => { - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) expect(indicator).toHaveClass( 'dnb-forms-submit-indicator--state-pending' ) }) await waitFor(() => { - expect(events).toEqual(['validator', 'onChangeForm']) + expect(events).toEqual(['onChangeValidator', 'onChangeForm']) expect(indicator).toHaveClass( 'dnb-forms-submit-indicator--state-pending' ) @@ -2103,7 +2105,7 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onChangeForm', 'onChangeField', ]) @@ -3976,7 +3978,7 @@ describe('DataContext.Provider', () => { await wait(10) }) - const validator = jest.fn(async (value) => { + const onChangeValidator = jest.fn(async (value) => { await wait(10) if (value !== 123) { return new Error('Invalid') @@ -3994,7 +3996,7 @@ describe('DataContext.Provider', () => { path="/count" label={data?.count} onChange={onChange} - validator={validator} + onChangeValidator={onChangeValidator} /> {JSON.stringify(data)} @@ -4020,8 +4022,11 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(onChange).toHaveBeenCalledTimes(0) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenLastCalledWith(12, expect.anything()) + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenLastCalledWith( + 12, + expect.anything() + ) }) fireEvent.change(input, { @@ -4032,8 +4037,11 @@ describe('DataContext.Provider', () => { expect(onChange).toHaveBeenCalledTimes(0) expect(onChange).toHaveBeenCalledTimes(0) - expect(validator).toHaveBeenCalledTimes(2) - expect(validator).toHaveBeenLastCalledWith(123, expect.anything()) + expect(onChangeValidator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenLastCalledWith( + 123, + expect.anything() + ) // executed in sync and unvalidated expect(onDataChange).toHaveBeenCalledTimes(4) @@ -4043,8 +4051,11 @@ describe('DataContext.Provider', () => { expect(onChange).toHaveBeenCalledTimes(1) expect(onChange).toHaveBeenLastCalledWith(123) - expect(validator).toHaveBeenCalledTimes(2) - expect(validator).toHaveBeenLastCalledWith(123, expect.anything()) + expect(onChangeValidator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenLastCalledWith( + 123, + expect.anything() + ) }) }) diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/ArraySelection.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/ArraySelection.test.tsx index 4e2161383b1..1b2d652162b 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/ArraySelection.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/ArraySelection.test.tsx @@ -39,6 +39,22 @@ describe('ArraySelection', () => { ).toBe(document.querySelector('.dnb-tooltip__content').id) }) + it('precede option title over children', async () => { + render( + + + child a + + + child b + + + ) + const options = document.querySelectorAll('.dnb-checkbox') + expect(options[0].textContent).toBe('title a') + expect(options[1].textContent).toBe('title b') + }) + it('handles selection correctly', () => { const handleChange = jest.fn() render( @@ -470,6 +486,23 @@ describe('ArraySelection', () => { describe.each(['button', 'checkbox-button'])( '%s', (testVariant: 'button' | 'checkbox-button') => { + it('precede option title over children', async () => { + render( + + + child a + + + child b + + + ) + + const options = document.querySelectorAll('.dnb-button__text') + expect(options[0].textContent).toBe('title a') + expect(options[1].textContent).toBe('title b') + }) + it(`has correct elements when "${testVariant}" is provided provided`, () => { render( diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-sbanken-checkbox-button-have-to-match-checkbox-button-options-horizontal.snap.png b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-sbanken-checkbox-button-have-to-match-checkbox-button-options-horizontal.snap.png index 33772925a1c..de27d822958 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-sbanken-checkbox-button-have-to-match-checkbox-button-options-horizontal.snap.png and b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-sbanken-checkbox-button-have-to-match-checkbox-button-options-horizontal.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-sbanken-checkbox-have-to-match-checkbox-nesting-logic.snap.png b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-sbanken-checkbox-have-to-match-checkbox-nesting-logic.snap.png index c3b150c7f26..6f832134d67 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-sbanken-checkbox-have-to-match-checkbox-nesting-logic.snap.png and b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-sbanken-checkbox-have-to-match-checkbox-nesting-logic.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-ui-checkbox-button-have-to-match-checkbox-button-options-horizontal.snap.png b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-ui-checkbox-button-have-to-match-checkbox-button-options-horizontal.snap.png index ec254db3572..aac49c7f2d6 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-ui-checkbox-button-have-to-match-checkbox-button-options-horizontal.snap.png and b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-ui-checkbox-button-have-to-match-checkbox-button-options-horizontal.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-ui-checkbox-have-to-match-checkbox-nesting-logic.snap.png b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-ui-checkbox-have-to-match-checkbox-nesting-logic.snap.png index 95b8e842f45..acf0cd54d04 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-ui-checkbox-have-to-match-checkbox-nesting-logic.snap.png and b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/__tests__/__image_snapshots__/arrayselection-field-for-ui-checkbox-have-to-match-checkbox-nesting-logic.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/stories/ArraySelection.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/stories/ArraySelection.stories.tsx index f6f8ec241b9..cc8796fc62f 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/stories/ArraySelection.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/ArraySelection/stories/ArraySelection.stories.tsx @@ -1,4 +1,4 @@ -import { Card, Section } from '../../../../../components' +import { Section } from '../../../../../components' import { Field, Form } from '../../..' export default { @@ -8,7 +8,7 @@ export default { export function NestingWithLogic() { return ( - + - + ) } @@ -93,7 +93,7 @@ export function SelectUpToThree() { ] return ( - + @@ -118,6 +118,6 @@ export function SelectUpToThree() { maxItems: 'You can only select up to three', }} /> - + ) } diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/BankAccountNumber.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/BankAccountNumber.tsx index 89abcdae5e5..fafb91f0d2f 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/BankAccountNumber.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/BankAccountNumber.tsx @@ -46,7 +46,9 @@ function BankAccountNumber(props: Props) { const { validate = true, omitMask, + // Deprecated – can be removed in v11 validator, + onChangeValidator = validator, onBlurValidator = bankAccountNumberValidator, label: labelProp, width, @@ -97,7 +99,7 @@ function BankAccountNumber(props: Props) { mask, width: width ?? 'medium', inputMode: 'numeric', - validator: validate ? validator : undefined, + onChangeValidator: validate ? onChangeValidator : undefined, onBlurValidator: validate ? onBlurValidatorToUse : undefined, exportValidators: { bankAccountNumberValidator }, } diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/__tests__/BankAccountNumber.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/__tests__/BankAccountNumber.test.tsx index 9a89d99d702..02d87441d89 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/__tests__/BankAccountNumber.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/__tests__/BankAccountNumber.test.tsx @@ -28,6 +28,57 @@ describe('Field.BankAccountNumber', () => { expect(input).toHaveAttribute('inputmode', 'numeric') }) + // Deprecated – can be removed in v11 + it('should validate given function as validator', async () => { + const text = 'Custom Error message' + const validator = jest.fn((value) => { + return value.length < 4 ? new Error(text) : undefined + }) + + render( + + ) + + await waitFor(() => { + expect(validator).toHaveBeenCalledTimes(1) + }) + + const element = document.querySelector('.dnb-form-status') + + expect(element).toBeInTheDocument() + expect(element.textContent).toBe(text) + }) + + it('should validate given function as onChangeValidator', async () => { + const text = 'Custom Error message' + const onChangeValidator = jest.fn((value) => { + return value.length < 4 ? new Error(text) : undefined + }) + + render( + + ) + + await waitFor(() => { + expect(onChangeValidator).toHaveBeenCalledTimes(1) + }) + + const element = document.querySelector('.dnb-form-status') + + expect(element).toBeInTheDocument() + expect(element.textContent).toBe(text) + }) + describe('should validate Norwegian bank account numbers', () => { const validBankAccountNumbers = [ '52440407897', diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Indeterminate/stories/Indeterminate.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Indeterminate/stories/Indeterminate.stories.tsx index 6b7d24ba52d..dd0cb8a4f8f 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Indeterminate/stories/Indeterminate.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Indeterminate/stories/Indeterminate.stories.tsx @@ -1,6 +1,5 @@ import React from 'react' import Field, { Form } from '../../../Forms' -import { Card } from '../../../../../components' export default { title: 'Eufemia/Extensions/Forms/Indeterminate', @@ -9,7 +8,7 @@ export default { export function WithToggle() { return ( - + - + diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/NationalIdentityNumber.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/NationalIdentityNumber.tsx index 650876cfdd7..96b9b5d8abc 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/NationalIdentityNumber.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/NationalIdentityNumber.tsx @@ -92,8 +92,10 @@ function NationalIdentityNumber(props: Props) { const { validate = true, omitMask, - onBlurValidator = dnrAndFnrValidator, + // Deprecated – can be removed in v11 validator, + onChangeValidator = validator, + onBlurValidator = dnrAndFnrValidator, width, label: labelProp, } = props @@ -129,7 +131,7 @@ function NationalIdentityNumber(props: Props) { mask, width: width ?? 'medium', inputMode: 'numeric', - validator: validate ? validator : undefined, + onChangeValidator: validate ? onChangeValidator : undefined, onBlurValidator: validate ? onBlurValidatorToUse : undefined, exportValidators: { dnrValidator, diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumber.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumber.test.tsx index 72b3e73d4e8..096901fc34e 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumber.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumber.test.tsx @@ -134,7 +134,8 @@ describe('Field.NationalIdentityNumber', () => { expect(dummyValidator).toHaveBeenCalledWith('6', expect.anything()) }) - it('should validate given function', async () => { + // Deprecated – can be removed in v11 + it('should validate given function as validator', async () => { const text = 'Custom Error message' const validator = jest.fn((value) => { return value.length < 4 ? new Error(text) : undefined @@ -159,20 +160,45 @@ describe('Field.NationalIdentityNumber', () => { expect(element.textContent).toBe(text) }) + it('should validate given function as onChangeValidator', async () => { + const text = 'Custom Error message' + const onChangeValidator = jest.fn((value) => { + return value.length < 4 ? new Error(text) : undefined + }) + + render( + + ) + + await waitFor(() => { + expect(onChangeValidator).toHaveBeenCalledTimes(1) + }) + + const element = document.querySelector('.dnb-form-status') + + expect(element).toBeInTheDocument() + expect(element.textContent).toBe(text) + }) + it('should contain errorMessages as second parameter', () => { - const validator = jest.fn() + const onChangeValidator = jest.fn() render( ) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledWith( '123', expect.objectContaining({ errorMessages: expect.objectContaining({ @@ -313,7 +339,7 @@ describe('Field.NationalIdentityNumber', () => { expect(screen.queryByRole('alert')).toBeNull() }) - it('should not validate custom validator when validate false', async () => { + it('should not validate custom onChangeValidator when validate false', async () => { const customValidator: Validator = (value) => { if (value?.length < 4) { return new Error('My error') @@ -324,7 +350,7 @@ describe('Field.NationalIdentityNumber', () => { @@ -333,7 +359,7 @@ describe('Field.NationalIdentityNumber', () => { expect(screen.queryByRole('alert')).toBeNull() }) - it('should not validate extended validator when validate false', async () => { + it('should not validate extended onChangeValidator when validate false', async () => { const invalidFnrBornInApril = '29040112345' const bornInApril = (value: string) => value.substring(2, 4) === '04' @@ -352,7 +378,7 @@ describe('Field.NationalIdentityNumber', () => { value={invalidFnrBornInApril} validateInitially validate={false} - validator={customValidator} + onChangeValidator={customValidator} /> ) @@ -507,7 +533,7 @@ describe('Field.NationalIdentityNumber', () => { ) }) - describe('should extend validation using custom validator', () => { + describe('should extend validation using custom onChangeValidator', () => { const validFnrNumApril = ['14046512368', '10042223293'] const validDNumApril = ['51041678171'] @@ -542,7 +568,7 @@ describe('Field.NationalIdentityNumber', () => { it.each(validIds)('Valid identity number: %s', async (fnrNum) => { render( @@ -554,7 +580,7 @@ describe('Field.NationalIdentityNumber', () => { it.each(invalidIds)('Invalid identity number: %s', async (id) => { render( @@ -571,7 +597,7 @@ describe('Field.NationalIdentityNumber', () => { it.each(invalidDNumApril)('Invalid D number: %s', async (dNum) => { render( @@ -588,7 +614,7 @@ describe('Field.NationalIdentityNumber', () => { it.each(invalidDNumTooShort)('Invalid D number: %s', async (dNum) => { render( @@ -607,7 +633,7 @@ describe('Field.NationalIdentityNumber', () => { async (fnr) => { render( @@ -627,7 +653,7 @@ describe('Field.NationalIdentityNumber', () => { async (fnr) => { render( diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumberMinimumAgeValidator.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumberMinimumAgeValidator.test.tsx index 30e958f7212..d8c0f56b462 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumberMinimumAgeValidator.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumberMinimumAgeValidator.test.tsx @@ -186,13 +186,13 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { const invalidDnums = ['69020112345', '690'] const invalidFnrs = ['29020112345', '290'] - describe('when provided as the only validator validation function', () => { + describe('when provided as the onChangeValidator function', () => { it.each(validIds)( 'Identity number is 18 years or older : %s', async (validId) => { render( { async (invalidId) => { render( { ) }) - describe('when extending the dnrAndFnrValidator as validator', () => { + describe('when extending the dnrAndFnrValidator as onChangeValidator', () => { it.each(validIds)( 'Identity number is 18 years or older : %s', async (validId) => { render( @@ -289,7 +291,9 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( @@ -309,7 +313,9 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( @@ -329,7 +335,9 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( @@ -428,14 +436,14 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { ) }) - describe('when extending the dnrValidator as validator', () => { + describe('when extending the dnrValidator as onChangeValidator', () => { it.each(dnr18YearsOldAndOlder)( 'D number is 18 years or older : %s', async (validDnum) => { render( @@ -453,7 +461,7 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( @@ -473,7 +481,7 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( @@ -547,14 +555,14 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { }) }) - describe('when extending the fnrValidator as validator', () => { + describe('when extending the fnrValidator as onChangeValidator', () => { it.each(fnr18YearsOldAndOlder)( 'Identity number(fnr) is 18 years or older : %s', async (validFnr) => { render( @@ -572,7 +580,7 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( @@ -592,7 +600,7 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/stories/NationalIdentityNumber.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/stories/NationalIdentityNumber.stories.tsx index 02b55500e24..3df8b1737ca 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/stories/NationalIdentityNumber.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/stories/NationalIdentityNumber.stories.tsx @@ -51,34 +51,34 @@ export function ValidatorsUndefinedFalse() { return (

Validate Initially:

@@ -175,34 +175,34 @@ export function NationalIdentityNumberValidator() {

Validate Initially:

@@ -245,34 +245,34 @@ export function DNumberValidator() {

Validate Initially:

@@ -342,67 +342,67 @@ export function AdultValidator() {

Validate Initially:

@@ -464,56 +464,56 @@ export function AdultValidatorAndDefaultValidator() { return (

Validate Initially:

@@ -525,56 +525,56 @@ export function CustomValidatorFunction() { return (

Validate Initially:

@@ -643,56 +643,56 @@ export function CustomValidatorFunctionReturnArray() { return (

Validate Initially:

diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx index 4815ed4a3da..4cbb1b16de4 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx @@ -548,29 +548,32 @@ describe('Field.Number', () => { expect(screen.queryByRole('alert')).toBeInTheDocument() }) - it('should call validator with validateInitially', async () => { - const validator = jest.fn(() => { + it('should call onChangeValidator with validateInitially', async () => { + const onChangeValidator = jest.fn(() => { return new Error('Validator message') }) render( ) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledWith(123, expect.anything()) + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledWith( + 123, + expect.anything() + ) await waitFor(() => { expect(screen.queryByRole('alert')).toBeInTheDocument() }) }) - it('should call validator on form submit', async () => { - const validator = jest.fn(() => { + it('should call onChangeValidator on form submit', async () => { + const onChangeValidator = jest.fn(() => { return new Error('Validator message') }) @@ -578,7 +581,7 @@ describe('Field.Number', () => { @@ -586,8 +589,11 @@ describe('Field.Number', () => { fireEvent.submit(document.querySelector('form')) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledWith(123, expect.anything()) + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledWith( + 123, + expect.anything() + ) await waitFor(() => { expect(screen.queryByRole('alert')).toBeInTheDocument() diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Number/stories/Number.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Number/stories/Number.stories.tsx index 87b383f3810..d74f85e5e5f 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Number/stories/Number.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Number/stories/Number.stories.tsx @@ -45,8 +45,8 @@ export const Number = () => { } export const WithFreshValidator = () => { - const validator: UseFieldProps['validator'] = useCallback( - (num, { connectWithPath }) => { + const validator: UseFieldProps['onChangeValidator'] = + useCallback((num, { connectWithPath }) => { const { getValue } = connectWithPath('/refValue') const amount = getValue() // console.log('amount', amount, amount >= num) @@ -56,9 +56,7 @@ export const WithFreshValidator = () => { if (num === undefined) { return new Error(`No amount was given`) } - }, - [] - ) + }, []) return ( { diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Option/Option.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Option/Option.tsx index 6ed84586d87..f499fb390e1 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Option/Option.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Option/Option.tsx @@ -20,7 +20,7 @@ export default function Option({ // eslint-disable-next-line jsx-a11y/role-has-required-aria-props role="option" > - {children ?? title} + {title ?? children} {text}
) diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Option/OptionDocs.ts b/packages/dnb-eufemia/src/extensions/forms/Field/Option/OptionDocs.ts index 2671ebd960c..c078982357e 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Option/OptionDocs.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Option/OptionDocs.ts @@ -8,18 +8,23 @@ export const OptionProperties: PropertiesTableProps = { status: 'optional', }, title: { - doc: 'Text title for the option.', - type: 'string', + doc: 'Title for the option. Overrides `children`.', + type: ['string', 'React.Node'], status: 'optional', }, text: { doc: 'Secondary text.', - type: 'string', + type: ['string', 'React.Node'], + status: 'optional', + }, + disabled: { + doc: 'Will disable the option.', + type: 'boolean', status: 'optional', }, help: FieldProperties.help, children: { - doc: 'Optional way to provide `title`.', + doc: 'Optional way to provide `title`. Will be ignored if `title` is used.', type: 'React.Node', status: 'optional', }, diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/OrganizationNumber.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/OrganizationNumber.tsx index 79dabac77ee..2b6afda008d 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/OrganizationNumber.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/OrganizationNumber.tsx @@ -42,7 +42,9 @@ function OrganizationNumber(props: Props) { const { validate = true, omitMask, + // Deprecated – can be removed in v11 validator, + onChangeValidator = validator, onBlurValidator = organizationNumberValidator, label: labelProp, width, @@ -67,7 +69,7 @@ function OrganizationNumber(props: Props) { mask, width: width ?? 'medium', inputMode: 'numeric', - validator: validate ? validator : undefined, + onChangeValidator: validate ? onChangeValidator : undefined, onBlurValidator: validate ? onBlurValidatorToUse : undefined, exportValidators: { organizationNumberValidator }, } diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/__tests__/OrganizationNumber.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/__tests__/OrganizationNumber.test.tsx index 30e402dcec3..e4843eb9229 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/__tests__/OrganizationNumber.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/__tests__/OrganizationNumber.test.tsx @@ -95,6 +95,57 @@ describe('Field.OrganizationNumber', () => { }) }) + // Deprecated – can be removed in v11 + it('should validate given function as validator', async () => { + const text = 'Custom Error message' + const validator = jest.fn((value) => { + return value.length < 4 ? new Error(text) : undefined + }) + + render( + + ) + + await waitFor(() => { + expect(validator).toHaveBeenCalledTimes(1) + }) + + const element = document.querySelector('.dnb-form-status') + + expect(element).toBeInTheDocument() + expect(element.textContent).toBe(text) + }) + + it('should validate given function as onChangeValidator', async () => { + const text = 'Custom Error message' + const onChangeValidator = jest.fn((value) => { + return value.length < 4 ? new Error(text) : undefined + }) + + render( + + ) + + await waitFor(() => { + expect(onChangeValidator).toHaveBeenCalledTimes(1) + }) + + const element = document.querySelector('.dnb-form-status') + + expect(element).toBeInTheDocument() + expect(element.textContent).toBe(text) + }) + it('should display error if required and validateInitially', async () => { render() @@ -289,7 +340,7 @@ describe('Field.OrganizationNumber', () => { value={invalidOrgNo} validateInitially validate={false} - validator={customValidator} + onChangeValidator={customValidator} onBlurValidator={false} /> @@ -326,7 +377,7 @@ describe('Field.OrganizationNumber', () => { value={invalidOrgNo} validateInitially validate={false} - validator={customValidator} + onChangeValidator={customValidator} onBlurValidator={false} /> diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/stories/OrganizationNumber.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/stories/OrganizationNumber.stories.tsx index 4af93be8b42..51db385f261 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/stories/OrganizationNumber.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/stories/OrganizationNumber.stories.tsx @@ -98,34 +98,34 @@ export function OrganizationNumberValidator() {

Validate Initially:

@@ -141,34 +141,34 @@ export function CustomValidator() {

Validate Initially:

@@ -223,34 +223,34 @@ export function CustomValidatorReturnArray() {

Validate Initially:

@@ -307,19 +307,22 @@ export function StringValidatorSimple() { return ( - - - + + +

Validate Initially:

- +
diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/PhoneNumber.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/PhoneNumber.tsx index e44d9df3a7f..44c490d08a4 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/PhoneNumber.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/PhoneNumber.tsx @@ -27,7 +27,7 @@ import { getCountryData, } from '../SelectCountry' import useTranslation from '../../hooks/useTranslation' -import { DrawerListDataObject } from '../../../../fragments/DrawerList' +import { DrawerListDataArrayItem } from '../../../../fragments/DrawerList' export type Props = Omit< FieldPropsWithExtraValue< @@ -95,7 +95,7 @@ function PhoneNumber(props: Props) { const countryCodeRef = useRef(props?.emptyValue) const numberRef = useRef(props?.emptyValue) - const dataRef = useRef>(null) + const dataRef = useRef>(null) const langRef = useRef(lang) const wasFilled = useRef(false) @@ -433,7 +433,7 @@ function PhoneNumber(props: Props) { width={ omitCountryCodeField ? 'medium' : props.width ?? 'stretch' } - help={{ ...help, breakout: false }} + help={{ ...help, breakout: false, outset: false }} required={required} errorMessages={errorMessages} validateInitially={validateInitially} diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/__tests__/PhoneNumber.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/__tests__/PhoneNumber.test.tsx index 29efd59316e..4e605f8fd71 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/__tests__/PhoneNumber.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/__tests__/PhoneNumber.test.tsx @@ -669,23 +669,23 @@ describe('Field.PhoneNumber', () => { expect(document.querySelector('[role="alert"]')).toBeInTheDocument() }) - it('should handle "validator" property with country code', async () => { - const validator = jest.fn(() => { + it('should handle "onChangeValidator" property with country code', async () => { + const onChangeValidator = jest.fn(() => { return new Error('some error') }) render( ) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledWith( '+41 9999', expect.objectContaining({ errorMessages: expect.objectContaining({ diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/stories/PhoneNumber.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/stories/PhoneNumber.stories.tsx index 202ab3d8e48..b1bf2fbf5a9 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/stories/PhoneNumber.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/stories/PhoneNumber.stories.tsx @@ -16,7 +16,7 @@ const makeRequest = async (value) => { }) } -const validator = async (value) => { +const onChangeValidator = async (value) => { // Delay the response const isValid = await makeRequest(value) if (!isValid) { @@ -36,7 +36,7 @@ export function PhoneNumber() { ({ + 'Field.errorRequired': translations.PostalCode.errorRequired, + 'Field.errorPattern': translations.PostalCode.errorPattern, + ...postalCodeErrorMessages, + }), + [ + postalCodeErrorMessages, + translations.PostalCode.errorPattern, + translations.PostalCode.errorRequired, + ] + )} width={postalCodeWidth ?? false} inputClassName="dnb-forms-field-postal-code-and-city__postal-code-input" inputMode="numeric" @@ -121,11 +124,18 @@ function PostalCodeAndCity(props: Props) { cityClassName )} label={cityLabel ?? translations.City.label} - errorMessages={{ - 'Field.errorRequired': translations.City.errorRequired, - 'Field.errorPattern': translations.City.errorPattern, - ...cityErrorMessages, - }} + errorMessages={useMemo( + () => ({ + 'Field.errorRequired': translations.City.errorRequired, + 'Field.errorPattern': translations.City.errorPattern, + ...cityErrorMessages, + }), + [ + cityErrorMessages, + translations.City.errorPattern, + translations.City.errorRequired, + ] + )} pattern={cityPattern ?? '^[A-Za-zÆØÅæøå -]+$'} trim width={cityWidth ?? 'stretch'} diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Selection/Selection.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Selection/Selection.tsx index ab6e73776b3..86aa5186f96 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Selection/Selection.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Selection/Selection.tsx @@ -41,6 +41,7 @@ export type Data = Array<{ value: string title: React.ReactNode text?: React.ReactNode + disabled?: boolean }> export type Props = FieldProps & { @@ -364,7 +365,12 @@ function renderRadioItems({ ].filter(Boolean) } -export function mapOptions(children: React.ReactNode, { createOption }) { +export function mapOptions( + children: React.ReactNode, + { + createOption, + }: { createOption: (props: OptionProps, i: number) => React.ReactNode } +) { return React.Children.map( children, (child: React.ReactElement, i) => { @@ -396,11 +402,12 @@ export function makeOptions( if (React.isValidElement(child) && child.type === OptionField) { const props = child.props as OptionFieldProps - const title = props.children ?? props.title ?? Untitled + const title = props.title ?? props.children ?? Untitled const content = props.text ? [title, props.text] : title const selectedKey = String(props.value ?? '') + const disabled = props.disabled - return { selectedKey, content } + return { selectedKey, content, disabled } } // For other children, just show them as content @@ -414,10 +421,11 @@ export function makeOptions( function renderDropdownItems(data: Data) { return ( - data?.map(({ value, title, text }) => { + data?.map(({ value, title, text, disabled }) => { return { selectedKey: value, content: (text ? [title, text] : title) || Untitled, + disabled, } }) || [] ) diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Selection/__tests__/Selection.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Selection/__tests__/Selection.test.tsx index 5da99492a49..19bf5d9f214 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Selection/__tests__/Selection.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Selection/__tests__/Selection.test.tsx @@ -99,26 +99,6 @@ describe('Selection', () => { // getByText instead of getByPlaceholderText since eufemia adds placeholder as tag, not placeholder-attribute expect(screen.getByText('Select something')).toBeInTheDocument() }) - - it('precede option children over title', async () => { - render( - - - child a - - - child b - - - ) - - const selectionButton = document.querySelector('button') - await userEvent.click(selectionButton) - const options = document.querySelectorAll('.dnb-drawer-list__option') - - expect(options[0].textContent).toBe('child a') - expect(options[1].textContent).toBe('child b') - }) }) describe('variants', () => { @@ -136,6 +116,24 @@ describe('variants', () => { expect(radioButtons[1]).toBeChecked() }) + it('precede option title over children', async () => { + render( + + + child a + + + child b + + + ) + + const options = document.querySelectorAll('.dnb-radio') + + expect(options[0].textContent).toBe('title a') + expect(options[1].textContent).toBe('title b') + }) + it('renders help', () => { render( @@ -860,6 +858,25 @@ describe('variants', () => { expect(options[1].getAttribute('aria-selected')).toBe('false') }) + it('precede option title over children', async () => { + render( + + + child a + + + child b + + + ) + + open() + const options = document.querySelectorAll('[role="option"]') + + expect(options[0].textContent).toBe('title a') + expect(options[1].textContent).toBe('title b') + }) + it('renders help', () => { render( + -
+ ) } export function Autocomplete() { return ( - + - + ) } export function HelpButton() { return ( - + - + ) } export function NestingWithLogic() { return ( - + - + ) } diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/String/__tests__/String.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/String/__tests__/String.test.tsx index fd73076f7bf..2e36e267f57 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/String/__tests__/String.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/String/__tests__/String.test.tsx @@ -917,20 +917,20 @@ describe('Field.String', () => { }) }) - describe('validation using a synchronous external validator function', () => { - it('should show error returned by validator', async () => { - const validator = jest.fn(syncValidatorReturningError) + describe('validation using a synchronous external onChangeValidator function', () => { + it('should show error returned by onChangeValidator', async () => { + const onChangeValidator = jest.fn(syncValidatorReturningError) render( ) await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenNthCalledWith( 1, 'abc', expect.anything() @@ -945,18 +945,18 @@ describe('Field.String', () => { fireEvent.blur(input) await waitFor(() => { - expect(validator).toHaveBeenCalledTimes(4) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(4) + expect(onChangeValidator).toHaveBeenNthCalledWith( 2, 'abcd', expect.anything() ) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenNthCalledWith( 3, 'abcde', expect.anything() ) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenNthCalledWith( 4, 'abcdef', expect.anything() @@ -967,12 +967,12 @@ describe('Field.String', () => { }) }) - it('should not show error when validator returns undefined', async () => { - const validator = jest.fn(syncValidatorReturningUndefined) + it('should not show error when onChangeValidator returns undefined', async () => { + const onChangeValidator = jest.fn(syncValidatorReturningUndefined) render( ) @@ -982,20 +982,20 @@ describe('Field.String', () => { }) }) - describe('validation using an asynchronous external validator function', () => { - it('should show error returned by validator', async () => { - const validator = jest.fn(asyncValidatorResolvingWithError) + describe('validation using an asynchronous external onChangeValidator function', () => { + it('should show error returned by onChangeValidator', async () => { + const onChangeValidator = jest.fn(asyncValidatorResolvingWithError) render( ) await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenNthCalledWith( 1, 'abc', expect.anything() @@ -1012,18 +1012,18 @@ describe('Field.String', () => { fireEvent.blur(input) }) - expect(validator).toHaveBeenCalledTimes(4) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(4) + expect(onChangeValidator).toHaveBeenNthCalledWith( 2, 'abcd', expect.anything() ) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenNthCalledWith( 3, 'abcde', expect.anything() ) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenNthCalledWith( 4, 'abcdef', expect.anything() @@ -1033,12 +1033,14 @@ describe('Field.String', () => { ).toBeInTheDocument() }) - it('should not show error when validator returns undefined', async () => { - const validator = jest.fn(asyncValidatorResolvingWithUndefined) + it('should not show error when onChangeValidator returns undefined', async () => { + const onChangeValidator = jest.fn( + asyncValidatorResolvingWithUndefined + ) render( ) @@ -1050,19 +1052,19 @@ describe('Field.String', () => { }) describe('validation using a synchronous external onBlurValidator function', () => { - it('should show error returned by validator', async () => { - const validator = jest.fn(syncValidatorReturningError) + it('should show error returned by onBlurValidator', async () => { + const onBlurValidator = jest.fn(syncValidatorReturningError) render( ) await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator).toHaveBeenCalledTimes(1) + expect(onBlurValidator).toHaveBeenCalledTimes(1) expect(screen.queryByRole('alert')).toBeInTheDocument() }) const input = document.querySelector('input') @@ -1071,13 +1073,13 @@ describe('Field.String', () => { await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator).toHaveBeenCalledTimes(2) - expect(validator).toHaveBeenNthCalledWith( + expect(onBlurValidator).toHaveBeenCalledTimes(2) + expect(onBlurValidator).toHaveBeenNthCalledWith( 1, 'abc', expect.anything() ) - expect(validator).toHaveBeenNthCalledWith( + expect(onBlurValidator).toHaveBeenNthCalledWith( 2, 'abcdef', expect.anything() @@ -1089,12 +1091,12 @@ describe('Field.String', () => { }) }) - it('should not show error when validator returns undefined', async () => { - const validator = jest.fn(syncValidatorReturningUndefined) + it('should not show error when onBlurValidator returns undefined', async () => { + const onBlurValidator = jest.fn(syncValidatorReturningUndefined) render( ) @@ -1108,19 +1110,19 @@ describe('Field.String', () => { }) describe('validation using an asynchronous external onBlurValidator function', () => { - it('should show error returned by validator', async () => { - const validator = jest.fn(asyncValidatorResolvingWithError) + it('should show error returned by onBlurValidator', async () => { + const onBlurValidator = jest.fn(asyncValidatorResolvingWithError) render( ) await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator).toHaveBeenCalledTimes(1) + expect(onBlurValidator).toHaveBeenCalledTimes(1) expect(screen.queryByRole('alert')).toBeInTheDocument() }) const input = document.querySelector('input') @@ -1129,13 +1131,13 @@ describe('Field.String', () => { await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator).toHaveBeenCalledTimes(2) - expect(validator).toHaveBeenNthCalledWith( + expect(onBlurValidator).toHaveBeenCalledTimes(2) + expect(onBlurValidator).toHaveBeenNthCalledWith( 1, 'abc', expect.anything() ) - expect(validator).toHaveBeenNthCalledWith( + expect(onBlurValidator).toHaveBeenNthCalledWith( 2, 'abcdef', expect.anything() @@ -1147,12 +1149,14 @@ describe('Field.String', () => { }) }) - it('should not show error when validator returns undefined', async () => { - const validator = jest.fn(asyncValidatorResolvingWithUndefined) + it('should not show error when onBlurValidator returns undefined', async () => { + const onBlurValidator = jest.fn( + asyncValidatorResolvingWithUndefined + ) render( ) @@ -1234,7 +1238,7 @@ describe('Field.String', () => { ).toBe('At least 4.') }) - it('should provide error message to the validator', async () => { + it('should provide error message to the onBlurValidator', async () => { let collectDeprecatedMessage = null let collectCustomMessage = null const customMessage = 'Your custom error message' diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/String/stories/String.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/String/stories/String.stories.tsx index 97f01bb2125..0e132a3a0da 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/String/stories/String.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/String/stories/String.stories.tsx @@ -106,7 +106,7 @@ export function ErrorMessages() { { + onChangeValidator={() => { return new FormError('OrganizationNumber.errorRequired') }} errorMessages={{ @@ -120,7 +120,7 @@ export function ErrorMessages() { value="abc" minLength={4} // pattern="[0-9]" - // validator={() => { + // onChangeValidator={() => { // return new FormError('OrganizationNumber.errorRequired') // }} // errorMessages={{ diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Upload/Upload.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Upload/Upload.tsx index 8430e563aba..b2ced390916 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Upload/Upload.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Upload/Upload.tsx @@ -40,7 +40,9 @@ export type Props = Omit< | 'onFileDelete' | 'skeleton' > & { - asyncFileHandler?: (newFiles: UploadValue) => Promise + fileHandler?: ( + newFiles: UploadValue + ) => UploadValue | Promise } const validateRequired = ( @@ -62,7 +64,7 @@ const validateRequired = ( const updateFileLoadingState = ( files: UploadValue, - isLoading: boolean + { isLoading } = { isLoading: false } ) => { return files.map((file) => ({ ...file, isLoading })) } @@ -96,7 +98,7 @@ function UploadComponent(props: Props) { handleChange, handleFocus, handleBlur, - asyncFileHandler, + fileHandler, ...rest } = useFieldProps(preparedProps, { executeOnChangeRegardlessOfError: true, @@ -131,20 +133,21 @@ function UploadComponent(props: Props) { // Set loading setFiles([ ...fileContext, - ...updateFileLoadingState(newFiles, true), + ...updateFileLoadingState(newFiles, { isLoading: true }), ]) const uploadedFiles = updateFileLoadingState( - await asyncFileHandler(newFiles), - false + await fileHandler(newFiles), + { isLoading: false } ) + // Set error, if any handleChange([...fileContext, ...uploadedFiles]) } else { handleChange(files) } }, - [fileContext, asyncFileHandler, setFiles, updateFileLoadingState] + [fileContext, setFiles, fileHandler, handleChange] ) const changeHandler = useCallback( @@ -153,13 +156,13 @@ function UploadComponent(props: Props) { handleBlur() handleFocus() - if (asyncFileHandler) { + if (fileHandler) { handleChangeAsync(files) } else { handleChange(files) } }, - [handleBlur, handleChange, handleFocus, asyncFileHandler, fileContext] + [handleBlur, handleFocus, fileHandler, handleChangeAsync, handleChange] ) const width = widthProp as FieldBlockWidth diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Upload/UploadDocs.ts b/packages/dnb-eufemia/src/extensions/forms/Field/Upload/UploadDocs.ts index 0fdfe4b5949..9563077829b 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Upload/UploadDocs.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Upload/UploadDocs.ts @@ -5,6 +5,11 @@ import { import { PropertiesTableProps } from '../../../../shared/types' export const UploadFieldProperties: PropertiesTableProps = { + fileHandler: { + doc: 'File handler function that takes newly added files (`newFiles: UploadValue`) as a parameter and returns the processed files. The function can either be synchronous or asynchronous. It returns a promise (`Promise`) containing the processed files when asynchronous.', + type: 'function', + status: 'optional', + }, ...UploadProperties, title: undefined, text: undefined, diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Upload/__tests__/Upload.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Upload/__tests__/Upload.test.tsx index cc653ce581f..fe53f07ca5b 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Upload/__tests__/Upload.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Upload/__tests__/Upload.test.tsx @@ -905,7 +905,51 @@ describe('Field.Upload', () => { ).toHaveTextContent(nbForms.Upload.errorRequired) }) - it('should handle displaying error from asyncFileHandler', async () => { + it('should handle displaying error from fileHandler with sync function', async () => { + const fileValid = createMockFile('1.png', 100, 'image/png') + const fileInValid = createMockFile('invalid.png', 100, 'image/png') + + const syncFileHandlerFnError = function mockSyncFileUpload( + newFiles: UploadValue + ) { + return newFiles.map((file) => { + if (file.file.name.length > 5) { + file.errorMessage = 'File name is too long' + } + return file + }) + } + + render() + + const element = getRootElement() + + await waitFor(() => + fireEvent.drop(element, { + dataTransfer: { + files: [fileValid], + }, + }) + ) + + expect( + document.querySelector('.dnb-form-status') + ).not.toBeInTheDocument() + + await waitFor(() => + fireEvent.drop(element, { + dataTransfer: { + files: [fileInValid], + }, + }) + ) + + expect(document.querySelector('.dnb-form-status')).toHaveTextContent( + 'File name is too long' + ) + }) + + it('should handle displaying error from fileHandler with async function', async () => { const file = createMockFile('fileName-1.png', 100, 'image/png') const asyncValidatorResolvingWithErrorMessage = () => @@ -928,7 +972,7 @@ describe('Field.Upload', () => { asyncValidatorResolvingWithErrorMessage ) - render() + render() const element = getRootElement() @@ -949,7 +993,7 @@ describe('Field.Upload', () => { }) }) - it('should handle displaying success from asyncFileHandler', async () => { + it('should handle displaying success from fileHandler with async function', async () => { const file = createMockFile('fileName-1.png', 100, 'image/png') const asyncValidatorResolvingWithSuccess = () => @@ -971,7 +1015,7 @@ describe('Field.Upload', () => { asyncValidatorResolvingWithSuccess ) - render() + render() const element = getRootElement() @@ -992,16 +1036,14 @@ describe('Field.Upload', () => { }) }) - it('should display spinner when loading asyncFileHandler', async () => { + it('should display spinner when loading fileHandler with async function', async () => { const file = createMockFile('fileName-1.png', 100, 'image/png') const asyncValidatorResolvingWithSuccess = () => new Promise(() => jest.fn()) render( - + ) const element = getRootElement() @@ -1027,5 +1069,188 @@ describe('Field.Upload', () => { ).toBeInTheDocument() }) }) + + it('should add new files from fileHandler with async function', async () => { + const fileExisting = createMockFile( + 'fileName-existing.png', + 100, + 'image/png' + ) + const newFile1 = createMockFile( + 'fileName-new-1.png', + 100, + 'image/png' + ) + const newFile2 = createMockFile( + 'fileName-new-2.png', + 100, + 'image/png' + ) + + const asyncValidatorResolvingWithSuccess = () => + new Promise((resolve) => + setTimeout( + () => + resolve([ + { + file: newFile1, + id: 'server_generated_id_1', + exists: false, + }, + { + file: newFile2, + id: 'server_generated_id_2', + exists: false, + }, + ]), + 1 + ) + ) + + const asyncFileHandlerFnSuccess = jest.fn( + asyncValidatorResolvingWithSuccess + ) + + render( + + ) + + expect( + document.querySelectorAll('.dnb-upload__file-cell').length + ).toBe(1) + expect( + screen.queryByText('fileName-existing.png') + ).toBeInTheDocument() + expect( + screen.queryByText('fileName-new-1.png') + ).not.toBeInTheDocument() + expect( + screen.queryByText('fileName-new-2.png') + ).not.toBeInTheDocument() + + const element = getRootElement() + + await waitFor(() => { + fireEvent.drop(element, { + dataTransfer: { + files: [newFile1, newFile2], + }, + }) + expect( + document.querySelectorAll('.dnb-upload__file-cell').length + ).toBe(3) + expect( + screen.queryByText('fileName-existing.png') + ).toBeInTheDocument() + expect( + screen.queryByText('fileName-new-1.png') + ).toBeInTheDocument() + expect( + screen.queryByText('fileName-new-2.png') + ).toBeInTheDocument() + }) + }) + + it('should not add existing file using fileHandler with async function', async () => { + const file = createMockFile('fileName.png', 100, 'image/png') + + const asyncValidatorResolvingWithSuccess = () => + new Promise((resolve) => + setTimeout( + () => + resolve([ + { + file, + }, + ]), + 1 + ) + ) + + const asyncFileHandlerFnSuccess = jest.fn( + asyncValidatorResolvingWithSuccess + ) + + render( + + ) + + expect( + document.querySelectorAll('.dnb-upload__file-cell').length + ).toBe(1) + expect(screen.queryByText('fileName.png')).toBeInTheDocument() + + const element = getRootElement() + + await waitFor(() => { + fireEvent.drop(element, { + dataTransfer: { + files: [file], + }, + }) + expect( + document.querySelectorAll('.dnb-upload__file-cell').length + ).toBe(1) + expect(screen.queryByText('fileName.png')).toBeInTheDocument() + }) + }) + }) + + it('should handle a mix of successful and failed files in fileHandler with async function', async () => { + const successFile = createMockFile('successFile.png', 100, 'image/png') + const failFile = createMockFile('failFile.png', 100, 'image/png') + + const asyncValidatorWithMixedResults = () => + new Promise((resolve) => + setTimeout( + () => + resolve([ + { + file: successFile, + id: 'server_generated_id', + exists: false, + }, + { + file: failFile, + id: 'internal_id_fail', + exists: false, + errorMessage: 'Failed to process', + }, + ]), + 1 + ) + ) + + const asyncFileHandlerFn = jest.fn(asyncValidatorWithMixedResults) + + render() + + const element = getRootElement() + + await waitFor(() => + fireEvent.drop(element, { + dataTransfer: { + files: [successFile, failFile], + }, + }) + ) + + await waitFor(() => { + expect(asyncFileHandlerFn).toHaveBeenCalledTimes(1) + expect( + document.querySelectorAll('.dnb-upload__file-cell').length + ).toBe(2) + expect(screen.queryByText('successFile.png')).toBeInTheDocument() + expect(screen.queryByText('failFile.png')).toBeInTheDocument() + expect(document.querySelector('.dnb-form-status')).toHaveTextContent( + 'Failed to process' + ) + }) }) }) diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Upload/stories/Upload.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Upload/stories/Upload.stories.tsx index fc72908ae49..263b6a86106 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Upload/stories/Upload.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Upload/stories/Upload.stories.tsx @@ -1,27 +1,19 @@ -import { Field, Form } from '../../..' +import { Field, Form, Tools } from '../../..' import { Flex } from '../../../../../components' -// import { createMockFile } from '../../../../../components/upload/__tests__/testHelpers' +import useUpload from '../../../../../components/upload/useUpload' +import { createRequest } from '../../../Form/Handler/stories/FormHandler.stories' +import { UploadValue } from '../Upload' export default { title: 'Eufemia/Extensions/Forms/Upload', } export function Upload() { - // const { setFiles } = OriginalUpload.useUpload('unique-id') - - // React.useEffect(() => { - // setFiles([ - // { file: createMockFile('fileName-1.png', 100, 'image/png') }, - // ]) - // }, [setFiles]) - return ( { console.log('global onChange', data) @@ -47,3 +39,65 @@ export function Upload() { ) } + +async function mockAsyncFileUpload__withoutPromises( + newFiles: UploadValue +): Promise { + const updatedFiles: UploadValue = [] + + for (const [index, file] of Object.entries(newFiles)) { + const formData = new FormData() + formData.append('file', file.file, file.file.name) + + const request = createRequest() + await request(Math.floor(Math.random() * 2000) + 1000) // Simulate a request + + try { + const mockResponse = { + ok: (parseFloat(index) + 2) % 2 === 0, // Every other request will fail + json: async () => ({ + server_generated_id: `${file.file.name}_${crypto.randomUUID()}`, + }), + } + + if (!mockResponse.ok) { + throw new Error('Unable to upload this file') + } + + const data = await mockResponse.json() + updatedFiles.push({ + ...file, + id: data.server_generated_id, + }) + } catch (error: any) { + updatedFiles.push({ + ...file, + errorMessage: error.message, + }) + } + } + + return updatedFiles +} + +const Output = () => { + const { files } = useUpload('async_upload_context_id') + return +} +export const WithAsyncFileHandler = () => { + return ( + console.log(form)}> + + + + + + + ) +} diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlock.tsx b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlock.tsx index ac4064c73b5..a712a7a5259 100644 --- a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlock.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlock.tsx @@ -181,6 +181,7 @@ function FieldBlock(props: Props) { const blockId = useId(props.id) const [wasUpdated, forceUpdate] = useReducer(() => ({}), {}) const mountedFieldsRef = useRef({}) + const fieldStateRef = useRef(null) const stateRecordRef = useRef({}) const fieldStateIdsRef = useRef(null) const contentsRef = useRef(null) @@ -254,11 +255,11 @@ function FieldBlock(props: Props) { } }, []) - const setFieldState = useCallback( + const setBlockRecord = useCallback( (props: StateBasis) => { if (nestedFieldBlockContext) { // If this FieldBlock is inside another one, forward the call to the outer one - nestedFieldBlockContext.setFieldState(props) + nestedFieldBlockContext.setBlockRecord(props) return } @@ -269,6 +270,17 @@ function FieldBlock(props: Props) { [nestedFieldBlockContext, setInternalRecord] ) + const setFieldState = useCallback( + (identifier: Identifier, fieldState: SubmitState) => { + if (fieldState !== fieldStateRef.current) { + fieldStateRef.current = fieldState + + forceUpdate() + } + }, + [] + ) + const showFieldError = useCallback( (identifier: Identifier, show: boolean) => { if (nestedFieldBlockContext) { @@ -465,6 +477,11 @@ function FieldBlock(props: Props) { hasCustomContentWidth ? 'custom' : contentWidth }`, labelHeight && `dnb-forms-field-block--label-height-${labelHeight}`, + composition && 'dnb-forms-field-block__composition', + composition && + `dnb-forms-field-block__composition--${ + composition === true ? 'horizontal' : composition + }`, className ) const gridClasses = classnames( @@ -534,6 +551,7 @@ function FieldBlock(props: Props) { return ( )} @@ -611,10 +630,6 @@ function FieldBlock(props: Props) { hasCustomContentWidth ? 'custom' : contentWidth }`, align && `dnb-forms-field-block__contents--align-${align}`, - composition && - `dnb-forms-field-block__contents__composition--${ - composition === true ? 'horizontal' : composition - }`, contentClassName )} ref={contentsRef} @@ -623,7 +638,7 @@ function FieldBlock(props: Props) { diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlockContext.ts b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlockContext.ts index 8f9bcfa688a..b6f9b17416f 100644 --- a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlockContext.ts +++ b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/FieldBlockContext.ts @@ -1,5 +1,5 @@ import React from 'react' -import type { FieldProps, Identifier } from '../types' +import type { FieldProps, Identifier, SubmitState } from '../types' export type FieldErrorIdsRef = Record export type MountedFieldsRef = Record @@ -35,7 +35,7 @@ export type StatusContent = { } export type FieldBlockContextProps = { - setFieldState?: ({ + setBlockRecord?: ({ identifier, type, stateId, @@ -43,6 +43,7 @@ export type FieldBlockContextProps = { showInitially, show, }: StateBasis) => void + setFieldState?: (identifier: Identifier, fieldState: SubmitState) => void showFieldError?: (identifier: Identifier, showError: boolean) => void hasErrorProp?: boolean composition?: true diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/FieldBlock.test.tsx b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/FieldBlock.test.tsx index 86e7c6cd457..c2e9c4e36d1 100644 --- a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/FieldBlock.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/FieldBlock.test.tsx @@ -1,4 +1,5 @@ import React from 'react' +import { wait } from '../../../../core/jest/jestSetup' import { fireEvent, render, waitFor } from '@testing-library/react' import { Input } from '../../../../components' import FieldBlock from '../FieldBlock' @@ -957,6 +958,99 @@ describe('FieldBlock', () => { ).toBe('Nested') }) + describe('fieldState', () => { + it('should show indicator when fieldState is set to pending', async () => { + render( + + + + ) + + const elements = document.querySelectorAll( + '.dnb-forms-submit-indicator' + ) + expect(elements).toHaveLength(1) + expect(elements[0]).toHaveClass( + 'dnb-forms-submit-indicator--state-pending' + ) + }) + + it('should show indicator two (2) times when nested', async () => { + render( + + content + + ) + + const elements = document.querySelectorAll( + '.dnb-forms-submit-indicator' + ) + expect(elements).toHaveLength(2) + expect(elements[0]).toHaveClass( + 'dnb-forms-submit-indicator--state-pending' + ) + expect(elements[1]).toHaveClass( + 'dnb-forms-submit-indicator--state-pending' + ) + }) + + it('should show indicator two (2) times when nested with useFieldProps', async () => { + const onChange = jest.fn(async () => { + await wait(10) + return null + }) + const MockComponent = () => { + const { id, handleChange } = useFieldProps({ + onChange, + }) + + return ( + + + + ) + } + + render( + + + + ) + + const elements = document.querySelectorAll( + '.dnb-forms-submit-indicator' + ) + expect(elements).toHaveLength(2) + + expect(elements[0].className).not.toContain( + 'dnb-forms-submit-indicator--state-' + ) + expect(elements[1].className).not.toContain( + 'dnb-forms-submit-indicator--state-' + ) + + await userEvent.type(document.querySelector('input'), '1') + + expect(elements[0]).toHaveClass( + 'dnb-forms-submit-indicator--state-pending' + ) + expect(elements[1]).toHaveClass( + 'dnb-forms-submit-indicator--state-pending' + ) + + await waitFor(() => { + expect(elements[0]).toHaveClass( + 'dnb-forms-submit-indicator--state-complete' + ) + }) + await waitFor(() => { + expect(elements[1]).toHaveClass( + 'dnb-forms-submit-indicator--state-complete' + ) + }) + }) + }) + describe('help', () => { it('should render content when open is true', async () => { render( diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-sbanken-have-to-match-help-button-in-composition-fields.snap.png b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-sbanken-have-to-match-help-button-in-composition-fields.snap.png index 8cba97a3d09..f4e3064298d 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-sbanken-have-to-match-help-button-in-composition-fields.snap.png and b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-sbanken-have-to-match-help-button-in-composition-fields.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-sbanken-have-to-match-help-button-with-html.snap.png b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-sbanken-have-to-match-help-button-with-html.snap.png index 11b1045f35a..20af8082a89 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-sbanken-have-to-match-help-button-with-html.snap.png and b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-sbanken-have-to-match-help-button-with-html.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-sbanken-have-to-match-label-description-help-button.snap.png b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-sbanken-have-to-match-label-description-help-button.snap.png index 8c432197f82..3a576a94c8f 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-sbanken-have-to-match-label-description-help-button.snap.png and b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-sbanken-have-to-match-label-description-help-button.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-ui-have-to-match-help-button-in-composition-fields.snap.png b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-ui-have-to-match-help-button-in-composition-fields.snap.png index bbf1087b1c4..fe4e6ae4831 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-ui-have-to-match-help-button-in-composition-fields.snap.png and b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-ui-have-to-match-help-button-in-composition-fields.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-ui-have-to-match-help-button-with-html.snap.png b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-ui-have-to-match-help-button-with-html.snap.png index c72ff21cfde..1f5c94c830a 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-ui-have-to-match-help-button-with-html.snap.png and b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-ui-have-to-match-help-button-with-html.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-ui-have-to-match-label-description-help-button.snap.png b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-ui-have-to-match-label-description-help-button.snap.png index bc989a0c5a0..dbb0967d4eb 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-ui-have-to-match-label-description-help-button.snap.png and b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/__tests__/__image_snapshots__/fieldblock-for-ui-have-to-match-label-description-help-button.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/stories/FieldBlock.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/stories/FieldBlock.stories.tsx index 51f2db3c659..aa10159744b 100644 --- a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/stories/FieldBlock.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/stories/FieldBlock.stories.tsx @@ -3,7 +3,7 @@ import FieldBlock from '../FieldBlock' import Input from '../../../../components/Input' import { useFieldProps } from '../../hooks' import { Field, Form } from '../..' -import { Anchor, Card, Flex } from '../../../../components' +import { Anchor, Flex } from '../../../../components' export default { title: 'Eufemia/Extensions/Forms/FieldBlock', @@ -190,7 +190,7 @@ export const WithInlineHelp = () => { Kredittopplysninger - + { content: 'Dette er hvor mye du har tenkt å låne totalt.', }} /> - + { Subheading - + { await new Promise((resolve) => setTimeout(resolve, 1000)) }} /> - +
) } diff --git a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/style/dnb-field-block.scss b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/style/dnb-field-block.scss index 178870b002b..d66077b4019 100644 --- a/packages/dnb-eufemia/src/extensions/forms/FieldBlock/style/dnb-field-block.scss +++ b/packages/dnb-eufemia/src/extensions/forms/FieldBlock/style/dnb-field-block.scss @@ -169,6 +169,7 @@ fieldset.dnb-forms-field-block { &__status { grid-area: status; + .dnb-form-status__shell { margin-top: 0.5rem; // set margin-top on shell, so we can animate the height max-width: 60ch; // to enhance readability @@ -204,31 +205,42 @@ fieldset.dnb-forms-field-block { } } } + } - &__composition { - &--vertical { - display: flex; - flex-flow: column; - row-gap: var(--spacing-small); - } + &__composition--vertical &__contents { + display: flex; + flex-flow: column; + row-gap: var(--spacing-x-small); + } - &--horizontal { - display: flex; - flex-flow: row; - column-gap: var(--spacing-small); + &__composition--horizontal &__contents { + display: flex; + flex-flow: row; + column-gap: var(--spacing-small); - @include allAbove(x-small) { - align-items: flex-end; // To support fields with labels of different size + @include allAbove(x-small) { + align-items: flex-end; // To support fields with labels of different size - &[class*='align-center'] { - align-items: center; // To support fields without labels, but different heights - } - } - @include allBelow(x-small) { - row-gap: var(--spacing-x-small); - flex-flow: column; - } + &[class*='align-center'] { + align-items: center; // To support fields without labels, but different heights } } + @include allBelow(x-small) { + row-gap: var(--spacing-x-small); + flex-flow: column; + } + } + + // Because we want to hide / show the indicator responsively, + // we render both, but hide the one we don't want to show. + @include allBelow(x-small) { + &__composition > &__grid > .dnb-forms-submit-indicator { + display: none; + } + } + @include allAbove(x-small) { + &__composition > &__grid > &__contents .dnb-forms-submit-indicator { + display: none; + } } } diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/ButtonRow/style/dnb-form-button-row.scss b/packages/dnb-eufemia/src/extensions/forms/Form/ButtonRow/style/dnb-form-button-row.scss index d7d3f8848da..7e4597b37d3 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/ButtonRow/style/dnb-form-button-row.scss +++ b/packages/dnb-eufemia/src/extensions/forms/Form/ButtonRow/style/dnb-form-button-row.scss @@ -7,19 +7,27 @@ gap: var(--spacing-small); } -.dnb-card + .dnb-forms-button-row, -.dnb-card + .dnb-button--primary { - &:not([class*='space__top']) { - margin-top: var(--spacing-small); +.dnb-card { + & + .dnb-forms-button-row, + & + .dnb-button--primary { + &:not([class*='space__top']) { + margin-top: var(--spacing-small); - .dnb-button[class*='space__top'] { - margin-top: 0; + .dnb-button[class*='space__top'] { + margin-top: 0; + } } } +} - @include allAbove(small) { - &:not([class*='space__left']) { - margin-left: var(--spacing-medium); +// Deprecated – can be removed in v11 (its handled by the outset prop) +.dnb-card:not([style*='--outset']) { + & + .dnb-forms-button-row, + & + .dnb-button--primary { + @include allAbove(small) { + &:not([class*='space__left']) { + margin-left: var(--spacing-medium); + } } } } diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Card/Card.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Card/Card.tsx new file mode 100644 index 00000000000..277c768ba67 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Card/Card.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import CardInstance, { + Props as CardProps, +} from '../../../../components/card/Card' + +function Card(props: CardProps) { + return +} + +export default Card diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Card/CardDocs.ts b/packages/dnb-eufemia/src/extensions/forms/Form/Card/CardDocs.ts new file mode 100644 index 00000000000..f6c7fcefd65 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Card/CardDocs.ts @@ -0,0 +1,18 @@ +import { CardProperties } from '../../../../components/card/CardDocs' +import { PropertiesTableProps } from '../../../../shared/types' + +export const FormCardProperties: PropertiesTableProps = { + outset: { + ...CardProperties.outset, + doc: 'Same as `outset` in [Card](/uilib/components/card/properties). Defaults to `true`.', + }, + stack: { + ...CardProperties.stack, + doc: 'Same as `stack` in [Card](/uilib/components/card/properties). Defaults to `true`.', + }, + '[Card](/uilib/components/card/properties)': { + doc: 'Card properties.', + type: 'Various', + status: 'optional', + }, +} diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/Card.screenshot.test.ts b/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/Card.screenshot.test.ts new file mode 100644 index 00000000000..56716295014 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/Card.screenshot.test.ts @@ -0,0 +1,42 @@ +/** + * Screenshot Test + * This file will not run on "test:staged" because we don't require any related files + */ + +import { makeScreenshot } from '../../../../../core/jest/jestSetupScreenshots' + +const url = '/uilib/extensions/forms/Form/Card/demos' + +describe('Form.Card', () => { + it('have to match outset', async () => { + const screenshot = await makeScreenshot({ + url, + selector: '[data-visual-test="forms-card"]', + wrapperStyle: { + padding: '2rem', + }, + }) + expect(screenshot).toMatchImageSnapshot() + }) +}) + +describe.each(['ui', 'sbanken'])( + 'Card small screen for %s', + (themeName) => { + const params = { + themeName, + pageViewport: { + width: 400, + }, + url, + } + + it('have to match outset', async () => { + const screenshot = await makeScreenshot({ + ...params, + selector: '[data-visual-test="forms-card"]', + }) + expect(screenshot).toMatchImageSnapshot() + }) + } +) diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/Card.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/Card.test.tsx new file mode 100644 index 00000000000..972f56cb640 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/Card.test.tsx @@ -0,0 +1,69 @@ +import React from 'react' +import { render } from '@testing-library/react' +import { Form } from '../../..' + +describe('Form.Card', () => { + it('should set "outset" by default', () => { + const { rerender } = render() + + const element = document.querySelector('.dnb-card') + + expect(element).toHaveStyle('--outset--small: 0') + expect(element).toHaveStyle('--outset--medium: 1') + expect(element).toHaveStyle('--outset--large: 1') + + rerender( + + ) + + expect(element).toHaveStyle('--outset--small: 1') + expect(element).toHaveStyle('--outset--medium: 0') + expect(element).toHaveStyle('--outset--large: 0') + + rerender() + + expect(element).toHaveStyle('--outset--small: 0') + expect(element).toHaveStyle('--outset--medium: 0') + expect(element).toHaveStyle('--outset--large: 0') + }) + + it('should set "stack" by default', () => { + const { rerender } = render() + + { + const element = document.querySelector('.dnb-card') + const container = element.querySelector('.dnb-flex-container') + + expect(element).toHaveClass('dnb-flex-item--align-self-stretch') + expect(container).toHaveClass('dnb-flex-container--align-stretch') + expect(container).toHaveClass( + 'dnb-flex-container--align-self-stretch' + ) + expect(container).toHaveClass('dnb-flex-container--spacing-medium') + } + + rerender() + + { + const element = document.querySelector('.dnb-card') + const container = element.querySelector('.dnb-flex-container') + + expect(element).toHaveClass('dnb-flex-item--align-self-stretch') + expect(container).not.toHaveClass( + 'dnb-flex-container--align-stretch' + ) + expect(container).toHaveClass( + 'dnb-flex-container--align-self-stretch' + ) + expect(container).not.toHaveClass( + 'dnb-flex-container--spacing-medium' + ) + } + }) +}) diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-outset.snap.png b/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-outset.snap.png new file mode 100644 index 00000000000..2ee6d1a3605 Binary files /dev/null and b/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/__image_snapshots__/card-small-screen-for-sbanken-have-to-match-outset.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-outset.snap.png b/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-outset.snap.png new file mode 100644 index 00000000000..345981d2aa8 Binary files /dev/null and b/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/__image_snapshots__/card-small-screen-for-ui-have-to-match-outset.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/__image_snapshots__/formcard-have-to-match-outset.snap.png b/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/__image_snapshots__/formcard-have-to-match-outset.snap.png new file mode 100644 index 00000000000..8afd9b100c8 Binary files /dev/null and b/packages/dnb-eufemia/src/extensions/forms/Form/Card/__tests__/__image_snapshots__/formcard-have-to-match-outset.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Card/index.ts b/packages/dnb-eufemia/src/extensions/forms/Form/Card/index.ts new file mode 100644 index 00000000000..9bb50edbc10 --- /dev/null +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Card/index.ts @@ -0,0 +1,2 @@ +export { default } from './Card' +export * from './Card' diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Handler/__tests__/Handler.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Handler/__tests__/Handler.test.tsx index 902f8482cc1..e0cce0782fa 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Handler/__tests__/Handler.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Handler/__tests__/Handler.test.tsx @@ -544,7 +544,7 @@ describe('Form.Handler', () => { expect(inputElement).toBeDisabled() }) - it('should not disable form elements during on async validator handling', async () => { + it('should not disable form elements during an async validator handling', async () => { const onSubmit = async () => null const asyncValidator = async () => { return null @@ -568,7 +568,7 @@ describe('Form.Handler', () => { rerender( - + ) @@ -772,7 +772,7 @@ describe('Form.Handler', () => { }) }) - it('should call onSubmit and onSubmitComplete with async validator', async () => { + it('should call onSubmit and onSubmitComplete with async onChangeValidator', async () => { const onSubmit: OnSubmit = jest.fn() const onSubmitComplete = jest.fn() @@ -788,7 +788,7 @@ describe('Form.Handler', () => { @@ -819,7 +819,7 @@ describe('Form.Handler', () => { }) }) - it('should not call async validator when field is not mounted anymore', async () => { + it('should not call async onChangeValidator when field is not mounted anymore', async () => { const onSubmit: OnSubmit = jest.fn() const asyncValidator = jest.fn(async () => { return null @@ -830,7 +830,7 @@ describe('Form.Handler', () => { diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Handler/stories/FormHandler.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Handler/stories/FormHandler.stories.tsx index 03f7c65b3de..0ee999297de 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Handler/stories/FormHandler.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Handler/stories/FormHandler.stories.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect } from 'react' import { Field, Form } from '../../..' -import { Button, Card, GlobalStatus } from '../../../../../components' +import { Button, GlobalStatus } from '../../../../../components' import { debounceAsync } from '../../../../../shared/helpers' export default { @@ -128,14 +128,14 @@ export function AdvancedForm() { <> MainHeading - + SubHeading @@ -166,7 +166,7 @@ export function AdvancedForm() { path="/fieldD" required /> - + @@ -179,9 +179,9 @@ export function AdvancedForm() { - + - + ) } @@ -268,12 +268,12 @@ export function SimpleForm() { onSubmitRequest={onSubmitRequest} onSubmitComplete={onSubmitComplete} > - + @@ -282,7 +282,7 @@ export function SimpleForm() { path="/myField2" onChange={onFieldChange} /> - + @@ -302,12 +302,12 @@ const delay = debounceAsync(async function () { export function SubmitIndicator() { return ( - + - + ) } @@ -319,7 +319,7 @@ export function GlobalStatusStory() { Heading - +
- + ) diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Isolation/stories/Isolation.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Isolation/stories/Isolation.stories.tsx index f303a7b1960..f3b998dda5f 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Isolation/stories/Isolation.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Isolation/stories/Isolation.stories.tsx @@ -1,6 +1,6 @@ import React from 'react' import { Field, Form } from '../../..' -import { Card, Flex, HeightAnimation } from '../../../../../components' +import { Flex, HeightAnimation } from '../../../../../components' export default { title: 'Eufemia/Extensions/Forms/Isolation', @@ -115,7 +115,7 @@ export const TransformOnCommit = () => { mySelection: 'other', }} > - + Legg til ny hovedkontaktperson @@ -165,7 +165,7 @@ export const TransformOnCommit = () => { - + ) } diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/MainHeading/__tests__/__image_snapshots__/formmainheading-have-to-match-above-card.snap.png b/packages/dnb-eufemia/src/extensions/forms/Form/MainHeading/__tests__/__image_snapshots__/formmainheading-have-to-match-above-card.snap.png index b6eadeb84e8..67caab86daf 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/Form/MainHeading/__tests__/__image_snapshots__/formmainheading-have-to-match-above-card.snap.png and b/packages/dnb-eufemia/src/extensions/forms/Form/MainHeading/__tests__/__image_snapshots__/formmainheading-have-to-match-above-card.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/MainHeading/__tests__/__image_snapshots__/formmainheading-have-to-match-help-button.snap.png b/packages/dnb-eufemia/src/extensions/forms/Form/MainHeading/__tests__/__image_snapshots__/formmainheading-have-to-match-help-button.snap.png index 12122c32b65..299c66978b1 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/Form/MainHeading/__tests__/__image_snapshots__/formmainheading-have-to-match-help-button.snap.png and b/packages/dnb-eufemia/src/extensions/forms/Form/MainHeading/__tests__/__image_snapshots__/formmainheading-have-to-match-help-button.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Section/stories/Section.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Section/stories/Section.stories.tsx index 68242f04657..844388e63d6 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Section/stories/Section.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Section/stories/Section.stories.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react' import { Field, Form, JSONSchema, SectionProps, Value } from '../../..' -import { Card, Flex } from '../../../../../components' +import { Flex } from '../../../../../components' import { Props as FieldNameProps } from '../../../Field/Name' export default { @@ -169,10 +169,10 @@ export const NestedSections = () => { const MySection = (props: SectionProps) => { return ( - + - + ) } @@ -228,7 +228,7 @@ export function EditViewContainer() { }, }} > - + Your account - + ) } @@ -276,13 +276,13 @@ export function OpenWhenFieldValidationError() { }, }} > - + Your account - + ) diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/SubHeading/__tests__/__image_snapshots__/formsubheading-have-to-match-above-card.snap.png b/packages/dnb-eufemia/src/extensions/forms/Form/SubHeading/__tests__/__image_snapshots__/formsubheading-have-to-match-above-card.snap.png index 47d7edee661..1b7dc384d78 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/Form/SubHeading/__tests__/__image_snapshots__/formsubheading-have-to-match-above-card.snap.png and b/packages/dnb-eufemia/src/extensions/forms/Form/SubHeading/__tests__/__image_snapshots__/formsubheading-have-to-match-above-card.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/SubHeading/__tests__/__image_snapshots__/formsubheading-have-to-match-help-button.snap.png b/packages/dnb-eufemia/src/extensions/forms/Form/SubHeading/__tests__/__image_snapshots__/formsubheading-have-to-match-help-button.snap.png index 336e6ad8c08..45a59fe0216 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/Form/SubHeading/__tests__/__image_snapshots__/formsubheading-have-to-match-help-button.snap.png and b/packages/dnb-eufemia/src/extensions/forms/Form/SubHeading/__tests__/__image_snapshots__/formsubheading-have-to-match-help-button.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/stories/Visibility.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/stories/Visibility.stories.tsx index 04f230583d8..2d7c159639c 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/stories/Visibility.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Visibility/stories/Visibility.stories.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react' import { Field, Form, Value } from '../../..' -import { Flex, Section, Card } from '../../../../../components' +import { Flex, Section } from '../../../../../components' import { P, Ul, Li } from '../../../../../elements' export default { @@ -185,7 +185,7 @@ export const wrappingVisibilityInFragmentAllVisible = () => { visible: true, }} > - + Test heading <> {

text

-
+ ) } @@ -224,7 +224,7 @@ export const wrappingVisibilityInFragmentAllHidden = () => { visible: false, }} > - + Test heading <> {

text

-
+ ) } @@ -267,7 +267,7 @@ export const wrappingVisibilityInFragments2Hidden = () => { visible5: true, }} > - + Test heading <> {

text

-
+ ) } @@ -315,7 +315,7 @@ export const wrappingVisibilityInFragments3Hidden = () => { visible5: false, }} > - + Test heading <> {

text

-
+ ) } @@ -363,7 +363,7 @@ export const wrappingVisibilityInFragment = () => { visible5: false, }} > - + <> Test heading <> @@ -448,7 +448,7 @@ export const wrappingVisibilityInFragment = () => { />

Text that should appear underneath

-
+ ) } @@ -461,7 +461,7 @@ export const wrappingSingleVisibilityInRootFragment = () => { visible1: false, }} > - + <> {

text

-
+ ) } @@ -477,7 +477,7 @@ export const wrappingSingleVisibilityInRootFragment = () => { export function VisibilityOnValidation() { return ( - + - + ) } diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/index.ts b/packages/dnb-eufemia/src/extensions/forms/Form/index.ts index 1b59f78a1b3..3b2c6c245b6 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/index.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Form/index.ts @@ -11,6 +11,7 @@ export { default as MainHeading } from './MainHeading' export { default as SubHeading } from './SubHeading' export { default as Visibility } from './Visibility' export { default as Section } from './Section' +export { default as Card } from './Card' export { default as Isolation } from './Isolation' export { default as Snapshot } from './Snapshot' export { default as useData } from './data-context/useData' diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/ArrayDocs.ts b/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/ArrayDocs.ts index e5912f3d3f7..90a8abb1d12 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/ArrayDocs.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/ArrayDocs.ts @@ -52,7 +52,7 @@ export const ArrayProperties: PropertiesTableProps = { type: 'unknown', status: 'optional', }, - validator: DataValueWritePropsProperties.validator, + onChangeValidator: DataValueWritePropsProperties.onChangeValidator, validateInitially: DataValueWritePropsProperties.validateInitially, continuousValidation: DataValueWritePropsProperties.continuousValidation, containerMode: { diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/__tests__/Array.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/__tests__/Array.test.tsx index 58b73db770c..c9b34f5e142 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/__tests__/Array.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/__tests__/Array.test.tsx @@ -504,9 +504,9 @@ describe('Iterate.Array', () => { }) }) - describe('validator', () => { - it('should validate validator initially (validateInitially)', async () => { - const validator = jest.fn((arrayValue) => { + describe('onChangeValidator', () => { + it('should validate onChangeValidator initially (validateInitially)', async () => { + const onChangeValidator = jest.fn((arrayValue) => { if (arrayValue.length === 2) { return new Error('Error message') } @@ -520,7 +520,7 @@ describe('Iterate.Array', () => { > @@ -529,8 +529,8 @@ describe('Iterate.Array', () => { ) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledWith( ['foo', 'bar'], expect.anything() ) @@ -546,8 +546,8 @@ describe('Iterate.Array', () => { fireEvent.click(document.querySelector('button')) - expect(validator).toHaveBeenCalledTimes(2) - expect(validator).toHaveBeenCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenCalledWith( ['foo', 'bar', 'baz'], expect.anything() ) @@ -559,8 +559,8 @@ describe('Iterate.Array', () => { }) }) - it('should validate validator on form submit', async () => { - const validator = jest.fn((arrayValue) => { + it('should validate onChangeValidator on form submit', async () => { + const onChangeValidator = jest.fn((arrayValue) => { if (arrayValue.length === 2) { return new Error('Error message') } @@ -572,20 +572,23 @@ describe('Iterate.Array', () => { items: ['foo', 'bar'], }} > - + ) - expect(validator).toHaveBeenCalledTimes(0) + expect(onChangeValidator).toHaveBeenCalledTimes(0) const form = document.querySelector('form') fireEvent.submit(form) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledWith( ['foo', 'bar'], expect.anything() ) @@ -601,8 +604,8 @@ describe('Iterate.Array', () => { fireEvent.click(document.querySelector('button')) - expect(validator).toHaveBeenCalledTimes(2) - expect(validator).toHaveBeenCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenCalledWith( ['foo', 'bar', 'baz'], expect.anything() ) @@ -618,7 +621,7 @@ describe('Iterate.Array', () => { const findFirstDuplication = (arr) => arr.findIndex((e, i) => arr.indexOf(e) !== i) - const validator = jest.fn((arrayValue) => { + const onChangeValidator = jest.fn((arrayValue) => { const index = findFirstDuplication(arrayValue) if (index > -1) { const value = arrayValue[index] @@ -628,7 +631,10 @@ describe('Iterate.Array', () => { render( - + diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/types.ts b/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/types.ts index ad8aef4cced..6e8eeaa9e68 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/types.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/types.ts @@ -24,7 +24,7 @@ export type Props = Omit< limit?: number countPath?: Path countPathLimit?: number - validator?: Validator + onChangeValidator?: Validator withoutFlex?: boolean animate?: boolean placeholder?: React.ReactNode diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/EditContainer/__tests__/EditAndViewContainer.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Iterate/EditContainer/__tests__/EditAndViewContainer.test.tsx index db4e2e32dc1..78d1a364a09 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/EditContainer/__tests__/EditAndViewContainer.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/EditContainer/__tests__/EditAndViewContainer.test.tsx @@ -86,7 +86,7 @@ describe('EditContainer and ViewContainer', () => { { + onChangeValidator={(value) => { if (value === '01') { return new Error('error') } diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/stories/Iterate.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Iterate/stories/Iterate.stories.tsx index 3e17807f36e..191e70358fc 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/stories/Iterate.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/stories/Iterate.stories.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react' import { Field, Form, Iterate, Tools, Value, Wizard } from '../..' -import { Card, Flex } from '../../../../components' +import { Flex } from '../../../../components' export default { title: 'Eufemia/Extensions/Forms/Iterate', @@ -17,7 +17,7 @@ export const AnimatedContainer = () => { id="myForm" > - + Empty list}> @@ -34,7 +34,7 @@ export const AnimatedContainer = () => { text="Add new item" top /> - + { Accounts - + - + @@ -178,13 +178,13 @@ export const InitialOpen = () => { Statsborgerskap - + { + onChangeValidator={(arrayValue) => { const findFirstDuplication = (arr) => arr.findIndex((e, i) => arr.indexOf(e) !== i) @@ -207,7 +207,7 @@ export const InitialOpen = () => { pushValue={null} text="Legg til flere statsborgerskap" /> - + @@ -228,7 +228,7 @@ export const WithArrayValidator = () => { { + onChangeValidator={(arrayValue) => { if (!(arrayValue?.length > 0)) { return new Error('You need at least one item') } @@ -272,7 +272,7 @@ export function InWizard() { - + @@ -292,7 +292,7 @@ export function InWizard() { variant="tertiary" pushValue={{}} /> - + diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/stories/PushContainer.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Iterate/stories/PushContainer.stories.tsx index 3b81ebd8f10..8873adc93da 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/stories/PushContainer.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/stories/PushContainer.stories.tsx @@ -1,6 +1,6 @@ import React, { useLayoutEffect } from 'react' import { Field, Form, Iterate, Tools, Value } from '../..' -import { Card, Flex } from '../../../../components' +import { Flex } from '../../../../components' export default { title: 'Eufemia/Extensions/Forms/Iterate/PushContainer', @@ -27,13 +27,13 @@ export const ComplexPushContainer = () => { return ( Representatives - + - + ) diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/Provider/stories/ValueProvider.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/stories/ValueProvider.stories.tsx index b4ad11af359..6a57bb8428f 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Value/Provider/stories/ValueProvider.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Value/Provider/stories/ValueProvider.stories.tsx @@ -1,5 +1,5 @@ import { Field, Form, Value } from '../../..' -import { Card, HeightAnimation } from '../../../../../components' +import { HeightAnimation } from '../../../../../components' export default { title: 'Eufemia/Extensions/Forms/ValueProvider', @@ -8,7 +8,7 @@ export default { export function ValueProvider() { return ( - + - + ) } diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/SummaryList.tsx b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/SummaryList.tsx index 61564d940f6..9b0b91c1440 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/SummaryList.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/SummaryList.tsx @@ -7,7 +7,10 @@ import ValueProvider from '../Provider/ValueProvider' import { ValueProps } from '../../types' import { useVerifyChildren } from './useVerifyChildren' -export type Props = Omit & { +export type Props = Omit< + DlAllProps, + 'label' | 'labelSrOnly' | 'children' +> & { children: React.ReactNode transformLabel?: ValueProps['transformLabel'] inheritVisibility?: ValueProps['inheritVisibility'] diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/__tests__/SummaryList.screenshot.test.ts b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/__tests__/SummaryList.screenshot.test.ts index 6a2d12fec10..035f46ed271 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/__tests__/SummaryList.screenshot.test.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/__tests__/SummaryList.screenshot.test.ts @@ -35,4 +35,12 @@ describe('Field.SummaryList', () => { }) expect(screenshot).toMatchImageSnapshot() }) + + it('have to match without a label', async () => { + const screenshot = await makeScreenshot({ + style: { width: '6rem' }, + selector: '[data-visual-test="forms-value-summary-empty-label"]', + }) + expect(screenshot).toMatchImageSnapshot() + }) }) diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/__tests__/SummaryList.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/__tests__/SummaryList.test.tsx index 02e96825441..9353266d61a 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/__tests__/SummaryList.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/__tests__/SummaryList.test.tsx @@ -17,6 +17,27 @@ describe('Field.SummaryList', () => { expect(element.getAttribute('aria-label')).toBe('Aria Label') }) + it('should have dnb-sr-only class when no label is given', () => { + render( + + + + ) + expect(document.querySelector('dt')).toHaveClass('dnb-sr-only') + }) + + it('should set dnb-sr-only class when labelSrOnly is true', () => { + render( + + + + ) + + const element = document.querySelector('dt') + expect(element).toHaveClass('dnb-sr-only') + expect(element).toHaveTextContent('Label') + }) + it('should warn when child is not a Value.* component', () => { const log = jest.spyOn(console, 'log').mockImplementation() diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/__tests__/__image_snapshots__/fieldsummarylist-have-to-match-without-a-label.snap.png b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/__tests__/__image_snapshots__/fieldsummarylist-have-to-match-without-a-label.snap.png new file mode 100644 index 00000000000..06fcd7b3253 Binary files /dev/null and b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/__tests__/__image_snapshots__/fieldsummarylist-have-to-match-without-a-label.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/stories/SummaryList.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/stories/SummaryList.stories.tsx index 69f51e99265..fa7ab94ef6c 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/stories/SummaryList.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Value/SummaryList/stories/SummaryList.stories.tsx @@ -1,5 +1,5 @@ import { Field, Form, Value } from '../../..' -import { Card, Flex } from '../../../../../components' +import { Flex } from '../../../../../components' import { P } from '../../../../../elements' export default { @@ -33,7 +33,7 @@ export function SummaryList() { function ContactInformationView() { return ( <> - + Subheading @@ -45,7 +45,7 @@ function ContactInformationView() { - + diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/Upload/stories/Upload.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Value/Upload/stories/Upload.stories.tsx index 21b18eb069c..51ba2b143b9 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Value/Upload/stories/Upload.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Value/Upload/stories/Upload.stories.tsx @@ -1,5 +1,4 @@ import { Form, Value } from '../../..' -import { Card } from '../../../../../components' import { P } from '../../../../../elements' export default { @@ -42,7 +41,7 @@ export function Upload() { what: ['Foo', 'Bar', 'Baz'], }} > - +

layout="grid"

-
- + +

layout="horizontal"

-
- + +

layout="vertical"

-
- + +

empty values

label.toUpperCase()} @@ -88,7 +87,7 @@ export function Upload() { -
+
) } diff --git a/packages/dnb-eufemia/src/extensions/forms/Value/ValueDocs.ts b/packages/dnb-eufemia/src/extensions/forms/Value/ValueDocs.ts index 2b5492fa61a..1e594e1e7b8 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Value/ValueDocs.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Value/ValueDocs.ts @@ -16,6 +16,11 @@ export const ValueProperties: PropertiesTableProps = { type: 'string', status: 'optional', }, + labelSrOnly: { + doc: 'Use `true` to make the label only readable by screen readers.', + type: 'boolean', + status: 'optional', + }, transformLabel: { doc: 'Transforms the label before it gets displayed. Receives the label as the first parameter. The second parameter is a object containing the `convertJsxToString` function.', type: 'function', diff --git a/packages/dnb-eufemia/src/extensions/forms/ValueBlock/ValueBlock.tsx b/packages/dnb-eufemia/src/extensions/forms/ValueBlock/ValueBlock.tsx index b057b0f58aa..37a82ae78c0 100644 --- a/packages/dnb-eufemia/src/extensions/forms/ValueBlock/ValueBlock.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/ValueBlock/ValueBlock.tsx @@ -43,6 +43,7 @@ function ValueBlock(props: Props) { const { className, label: labelProp, + labelSrOnly, transformLabel = (label: Props['label']) => label, inline, maxWidth = props.composition ? props.maxWidth : 'large', @@ -116,7 +117,12 @@ function ValueBlock(props: Props) { value={{ ...summaryListContext, isNested: true }} > -
+
{label && {label}}
{label} diff --git a/packages/dnb-eufemia/src/extensions/forms/ValueBlock/__tests__/ValueBlock.test.tsx b/packages/dnb-eufemia/src/extensions/forms/ValueBlock/__tests__/ValueBlock.test.tsx index ccb04c3bb22..ebc27f09f84 100644 --- a/packages/dnb-eufemia/src/extensions/forms/ValueBlock/__tests__/ValueBlock.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/ValueBlock/__tests__/ValueBlock.test.tsx @@ -69,6 +69,18 @@ describe('ValueBlock', () => { expect(element).toHaveClass('dnb-forms-value-block--max-width-medium') }) + it('should set dnb-sr-only class when labelSrOnly is true', () => { + render( + + content + + ) + + const element = document.querySelector('.dnb-form-label') + expect(element).toHaveClass('dnb-sr-only') + expect(element).toHaveTextContent('Label') + }) + it('should put children in a wrapper element "__content"', () => { render(content) diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.screenshot.test.ts b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.screenshot.test.ts index 830db5d5b8e..f1c5dfede77 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.screenshot.test.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.screenshot.test.ts @@ -8,6 +8,10 @@ describe('Wizard.Container', () => { pageViewport: { width: 700, }, + wrapperStyle: { + 'padding-left': '2.5rem', + 'padding-right': '2.5rem', + }, selector: '[data-visual-test="wizard-layout-card-border"] .dnb-forms-wizard-layout__contents', }) diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx index 3f23b7426c0..74f6732aeac 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx @@ -281,9 +281,9 @@ describe('Wizard.Container', () => { }) it('should support navigating back and forth with async validators', async () => { - const validator = async (value: string) => { + const onChangeValidator = async (value: string) => { if (value !== 'valid') { - return new Error('validator-error') + return new Error('onChangeValidator-error') } } @@ -300,7 +300,7 @@ describe('Wizard.Container', () => { @@ -337,7 +337,7 @@ describe('Wizard.Container', () => { expect(output()).toHaveTextContent('Step 1') expect(screen.queryByRole('alert')).toHaveTextContent( - 'validator-error' + 'onChangeValidator-error' ) fireEvent.blur(input()) @@ -372,7 +372,7 @@ describe('Wizard.Container', () => { expect(output()).toHaveTextContent('Step 1') expect(screen.queryByRole('alert')).toHaveTextContent( - 'validator-error' + 'onChangeValidator-error' ) fireEvent.blur(input()) @@ -1336,14 +1336,17 @@ describe('Wizard.Container', () => { }) }) - it('should handle async validator', async () => { + it('should handle async onChangeValidator', async () => { const asyncValidator = async () => null render( Step 1 - + @@ -1389,7 +1392,7 @@ describe('Wizard.Container', () => { }) }) - it('should handle async validator with error', async () => { + it('should handle async onChangeValidator with error', async () => { const asyncValidator = async (value: string) => { if (value !== 'valid') { return new Error('Error message') @@ -1400,7 +1403,7 @@ describe('Wizard.Container', () => { Step 1 - + diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-border.snap.png b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-border.snap.png index f3b0a41cb23..a700fb867b8 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-border.snap.png and b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-border.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-container-with-status-message.snap.png b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-container-with-status-message.snap.png index 8ff6c39b18e..2be93e98d84 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-container-with-status-message.snap.png and b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-container-with-status-message.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-large-screen.snap.png b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-large-screen.snap.png index 0d0bc81d5c2..d5be0e921ef 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-large-screen.snap.png and b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-large-screen.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-small-screen.snap.png b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-small-screen.snap.png index 0bce3d4c15e..b1e78da594d 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-small-screen.snap.png and b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/__image_snapshots__/wizardcontainer-have-to-match-small-screen.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/EditButton/__tests__/EditButton.screenshot.test.ts b/packages/dnb-eufemia/src/extensions/forms/Wizard/EditButton/__tests__/EditButton.screenshot.test.ts index 536238c939d..0e4355ac740 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Wizard/EditButton/__tests__/EditButton.screenshot.test.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/EditButton/__tests__/EditButton.screenshot.test.ts @@ -9,6 +9,10 @@ describe('EditButton', () => { style: { width: '20rem', }, + wrapperStyle: { + 'padding-left': '2.5rem', + 'padding-right': '2.5rem', + }, selector: '[data-visual-test="wizard-edit-button"] .dnb-forms-wizard-layout__contents', }) diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/EditButton/__tests__/__image_snapshots__/editbutton-have-to-match-button-with-hr.snap.png b/packages/dnb-eufemia/src/extensions/forms/Wizard/EditButton/__tests__/__image_snapshots__/editbutton-have-to-match-button-with-hr.snap.png index bec834dcb25..ac61ee72952 100644 Binary files a/packages/dnb-eufemia/src/extensions/forms/Wizard/EditButton/__tests__/__image_snapshots__/editbutton-have-to-match-button-with-hr.snap.png and b/packages/dnb-eufemia/src/extensions/forms/Wizard/EditButton/__tests__/__image_snapshots__/editbutton-have-to-match-button-with-hr.snap.png differ diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx index 73f9c1cb82c..add290991b4 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react' import { P } from '../../../../elements' -import { Button, Card } from '../../../../components' +import { Button } from '../../../../components' import Field, { Form, Wizard } from '../../Forms' import { createRequest } from '../../Form/Handler/stories/FormHandler.stories' import { debounceAsync } from '../../../../shared/helpers' @@ -186,19 +186,19 @@ export function AsyncStepChange() { // variant="drawer" > - + - + @@ -72,7 +72,7 @@ export const ValueVisibility = () => { - + ) } diff --git a/packages/dnb-eufemia/src/extensions/forms/stories/PizzaDemo.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/stories/PizzaDemo.stories.tsx index 9fc1acbce44..19d3dec79c8 100644 --- a/packages/dnb-eufemia/src/extensions/forms/stories/PizzaDemo.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/stories/PizzaDemo.stories.tsx @@ -1,5 +1,4 @@ import React from 'react' -import { Card } from '../../../components' import { Field, Form, Wizard, Value, Tools } from '..' import { Provider } from '../../../../shared' @@ -38,7 +37,7 @@ export function PizzaDemo() { Which pizza do you want? - + Your Pizza - + - + Allergies - + @@ -76,14 +75,14 @@ export function PizzaDemo() { Delivery address - + Your name - + - + Your address @@ -105,7 +104,7 @@ export function PizzaDemo() { postalCode={{ required: true, path: '/postalCode' }} city={{ required: true, path: '/city' }} /> - + @@ -113,16 +112,16 @@ export function PizzaDemo() { Summary - + - + - + Deliver address @@ -139,7 +138,7 @@ export function PizzaDemo() { - + diff --git a/packages/dnb-eufemia/src/extensions/forms/types.ts b/packages/dnb-eufemia/src/extensions/forms/types.ts index b65c5707c4e..3228bb4162a 100644 --- a/packages/dnb-eufemia/src/extensions/forms/types.ts +++ b/packages/dnb-eufemia/src/extensions/forms/types.ts @@ -307,7 +307,9 @@ export interface UseFieldProps< // - Validation required?: boolean schema?: AllJSONSchemaVersions + /** @deprecated Use `onChangeValidator` instead */ validator?: Validator + onChangeValidator?: Validator onBlurValidator?: Validator exportValidators?: Record> validateRequired?: ( @@ -434,6 +436,9 @@ export interface ValueProps */ label?: React.ReactNode + /** Use `true` to make the label only readable by screen readers. */ + labelSrOnly?: boolean + /** * Use `true` to inherit the label from a visible (rendered) field with the same path. */ diff --git a/packages/dnb-eufemia/src/extensions/payment-card/__tests__/__snapshots__/PaymentCard.test.tsx.snap b/packages/dnb-eufemia/src/extensions/payment-card/__tests__/__snapshots__/PaymentCard.test.tsx.snap index 1b953ef23f1..5111e15b325 100644 --- a/packages/dnb-eufemia/src/extensions/payment-card/__tests__/__snapshots__/PaymentCard.test.tsx.snap +++ b/packages/dnb-eufemia/src/extensions/payment-card/__tests__/__snapshots__/PaymentCard.test.tsx.snap @@ -21,7 +21,7 @@ exports[`PaymentCard scss has to match style dependencies css 1`] = ` */ :root { --sb-font-family-default: "Roboto", "Helvetica", "Arial", sans-serif; - --sb-font-family-headings: "MaisonNeueHeadings", "Roboto", "Helvetica", + --sb-font-family-heading: "MaisonNeueHeadings", "Roboto", "Helvetica", "Arial", sans-serif; --sb-font-weight-default: normal; --sb-font-weight-basis: normal; diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/DrawerList.d.ts b/packages/dnb-eufemia/src/fragments/drawer-list/DrawerList.d.ts index ee8fbc06e87..6fc74e6756a 100644 --- a/packages/dnb-eufemia/src/fragments/drawer-list/DrawerList.d.ts +++ b/packages/dnb-eufemia/src/fragments/drawer-list/DrawerList.d.ts @@ -13,27 +13,35 @@ export type DrawerListWrapperElement = | React.ReactNode; export type DrawerListDefaultValue = string | number; export type DrawerListValue = string | number; -export type DrawerListDataObject = { +/** @deprecated use `DrawerListDataArrayObject` */ +export type DrawerListDataObject = DrawerListDataArrayObject; +export type DrawerListDataArrayObject = { + [customProperty: string]: unknown; selected_value?: string | React.ReactNode; selectedKey?: string | number; + /** @deprecated use `selectedKey` */ selected_key?: string | number; suffix_value?: string | React.ReactNode; - content?: string | React.ReactNode | string[]; + content?: DrawerListContent; + disabled?: boolean; search_content?: string | React.ReactNode | string[]; }; -export type DrawerListDataObjectUnion = - | string - | React.ReactNode - | DrawerListDataObject; +/** @deprecated use `DrawerListDataArrayItem` */ +export type DrawerListDataObjectUnion = DrawerListDataArrayItem; +export type DrawerListDataArrayItem = + | DrawerListDataArrayObject + | DrawerListContent; +export type DrawerListDataArray = DrawerListDataArrayItem[]; +export type DrawerListDataRecord = Record; +export type DrawerListDataAll = DrawerListDataRecord | DrawerListDataArray; export type DrawerListData = | string - | ((...args: any[]) => any) + | ((...args: any[]) => DrawerListDataAll) + | DrawerListDataAll; +export type DrawerListContent = + | string | React.ReactNode - | Record - | DrawerListDataObjectUnion[]; -export type DrawerListSelectedValue = string | React.ReactNode; -export type DrawerListSuffixValue = string | React.ReactNode; -export type DrawerListContent = string | React.ReactNode | string[]; + | (string | React.ReactNode)[]; export type DrawerListRawData = | any[] | Record @@ -108,7 +116,7 @@ export interface DrawerListProps { */ default_value?: DrawerListDefaultValue; /** - * Define a preselected data entry (index) or key inside an array item. Can be a string or integer. + * Define a preselected `data` entry. In order of priority, `value` can be set to: object key (if `data` is an object), `selectedKey` prop (if `data` is an array), array index (if no `selectedKey`) or content (if `value` is a non-integer string). */ value?: DrawerListValue; /** @@ -146,12 +154,9 @@ export interface DrawerListProps { skip_keysearch?: boolean; opened?: boolean; /** - * The data we want to fill the list with. Provide the data as a JSON string, array, or object in the specified data structure. If you don't have to define a value, you can also send in a function which will be called once the user opens the DrawerList. + * The data we want to fill the list with. The data can be provided as an array or object. Or as a function that returns the data (called when user opens the list). */ data?: DrawerListData; - selected_value?: DrawerListSelectedValue; - suffix_value?: DrawerListSuffixValue; - content?: DrawerListContent; prepared_data?: any[]; raw_data?: DrawerListRawData; /** @@ -188,18 +193,8 @@ export type DrawerListOptionsProps = { export type DrawerListItemProps = { children: React.ReactNode; selected: boolean; - /** - * Define a preselected data entry (index) or key inside an array item. Can be a string or integer. - */ value: string; - on_click: ({ - value - }: { - /** - * Define a preselected data entry (index) or key inside an array item. Can be a string or integer. - */ - value: string; - }) => void; + on_click: ({ value }: { value: string }) => void; }; export type DrawerListAllProps = DrawerListProps & SpacingProps & diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/DrawerList.js b/packages/dnb-eufemia/src/fragments/drawer-list/DrawerList.js index b3373c6bc4c..71d46ee73fe 100644 --- a/packages/dnb-eufemia/src/fragments/drawer-list/DrawerList.js +++ b/packages/dnb-eufemia/src/fragments/drawer-list/DrawerList.js @@ -310,6 +310,7 @@ class DrawerListInstance extends React.PureComponent { selected: !dataItem.ignore_events && _id == selected_item, onClick: this.selectItemHandler, onKeyDown: this.preventTab, + disabled: dataItem.disabled, } if (ignoreEvents) { @@ -468,6 +469,7 @@ DrawerList.Item = React.forwardRef((props, ref) => { selected, // eslint-disable-line active, // eslint-disable-line value, // eslint-disable-line + disabled, // eslint-disable-line ...rest } = props @@ -481,6 +483,8 @@ DrawerList.Item = React.forwardRef((props, ref) => { role, tabIndex: selected ? '0' : '-1', 'aria-selected': active, + disabled, + 'aria-disabled': disabled, } if (selected) { params['aria-current'] = true // has best support on NVDA @@ -523,6 +527,7 @@ DrawerList.Item.propTypes = { selected: PropTypes.bool, active: PropTypes.bool, value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + disabled: PropTypes.bool, } DrawerList.Item.defaultProps = { role: 'option', @@ -577,6 +582,10 @@ ItemContent.propTypes = { hash: PropTypes.string, children: PropTypes.oneOfType([PropTypes.node, PropTypes.object]), } +ItemContent.defaultProps = { + hash: '', + children: undefined, +} DrawerList.HorizontalItem = ({ className, ...props }) => ( {DATA}'], status: 'required', }, value: { - doc: 'Define a preselected data entry (index) or key inside an array item. Can be a string or integer.', + doc: 'Define a preselected `data` entry. In order of priority, `value` can be set to: object key (if `data` is an object), `selectedKey` prop (if `data` is an array), array index (if no `selectedKey`) or content (if `value` is a non-integer string).', type: ['string', 'number'], status: 'optional', }, @@ -165,7 +165,7 @@ export const DrawerListEvents: PropertiesTableProps = { status: 'optional', }, on_select: { - doc: 'Will be called once the user selects an item by a click or keyboard navigation.', + doc: 'Will be called once the user focuses or selects an item by a click or keyboard navigation.', type: 'function', status: 'optional', }, @@ -180,3 +180,36 @@ export const DrawerListEvents: PropertiesTableProps = { status: 'optional', }, } + +export const DrawerListItem: PropertiesTableProps = { + content: { + doc: 'Visual content in the list item', + type: ['string', 'React.node', '(string | React.Node)[]'], + status: 'optional', + }, + disabled: { + doc: 'Disables the list item from selection', + type: 'boolean', + status: 'optional', + }, + selectedKey: { + doc: 'If set, can be used instead of array index by the `value` prop', + type: ['string', 'number'], + status: 'optional', + }, + selected_value: { + doc: 'Replaces the standard value output for selected item. Only used in some implementations (Dropdown, Autocomplete).', + type: ['string', 'React.Node'], + status: 'optional', + }, + suffix_value: { + doc: 'Content placed to the right in the list item.', + type: ['string', 'React.node'], + status: 'optional', + }, + selected_key: { + doc: 'Use prop `selectedKey` instead', + type: ['string', 'number'], + status: 'deprecated', + }, +} diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/DrawerListHelpers.js b/packages/dnb-eufemia/src/fragments/drawer-list/DrawerListHelpers.js index 613b26edae2..561cb400076 100644 --- a/packages/dnb-eufemia/src/fragments/drawer-list/DrawerListHelpers.js +++ b/packages/dnb-eufemia/src/fragments/drawer-list/DrawerListHelpers.js @@ -77,6 +77,7 @@ export const drawerListPropTypes = { PropTypes.string, PropTypes.number, ]), + /** @deprecated use `selectedKey` */ selected_key: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, @@ -89,6 +90,7 @@ export const drawerListPropTypes = { PropTypes.string, PropTypes.node, ]), + disabled: PropTypes.bool, content: PropTypes.oneOfType([ PropTypes.string, PropTypes.node, @@ -290,6 +292,7 @@ export const normalizeData = (props) => { const list = [] for (const key in data) { list.push({ + selectedKey: key, selected_key: key, value: key, content: data[key], @@ -327,13 +330,16 @@ export const getCurrentIndex = (value, data) => { return data?.findIndex((cur) => parseCurrentValue(cur) === value) } // 2. if "selectedKey" is given in data, we now handle it as a value, and not an index. - else if (selectedKeyExists()) { - return data?.findIndex( + if (selectedKeyExists()) { + const index = data?.findIndex( (cur) => String(parseCurrentValue(cur)) === String(value) ) + if (index > -1) { + return index + } } - // 3. if is numeric, handle it as a index. - else if (!isNaN(parseFloat(value))) { + // 3. if is numeric, and no matching "selectedKey", handle it as a index. + if (!isNaN(parseFloat(value))) { return value } @@ -346,6 +352,7 @@ export const getCurrentIndex = (value, data) => { } if ( typeof data[i]?.selectedKey !== 'undefined' || + typeof data[i]?.selected_key !== 'undefined' || data[i]?.type === 'object' ) { return true @@ -398,7 +405,7 @@ export const getCurrentData = (item_index, data) => { data = (data && data.find(({ __id }) => __id == item_index)) || null if (data && data.__isTransformed) { - data = parseCurrentValue(data) + return data.content } return data diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/DrawerListProvider.js b/packages/dnb-eufemia/src/fragments/drawer-list/DrawerListProvider.js index cec84651da6..9bc1005ab4e 100644 --- a/packages/dnb-eufemia/src/fragments/drawer-list/DrawerListProvider.js +++ b/packages/dnb-eufemia/src/fragments/drawer-list/DrawerListProvider.js @@ -1243,6 +1243,10 @@ export default class DrawerListProvider extends React.PureComponent { attributes, } + if (data?.disabled) { + return false + } + const res = dispatchCustomElementEvent( this.state, 'on_pre_change', diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/DrawerList.screenshot.test.ts b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/DrawerList.screenshot.test.ts index 2159f3a077d..7b031251a95 100644 --- a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/DrawerList.screenshot.test.ts +++ b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/DrawerList.screenshot.test.ts @@ -24,16 +24,14 @@ describe.each(['ui', 'sbanken'])('DrawerList for %s', (themeName) => { expect(screenshot).toMatchImageSnapshot() }) - if (themeName === 'sbanken') { - it('have to match the sbanken drawer-list', async () => { - const screenshot = await makeScreenshot({ - style: { - width: '14rem', - }, - selector: - '[data-visual-test="drawer-list"] .dnb-drawer-list__list', - }) - expect(screenshot).toMatchImageSnapshot() + it('have to match the disabled option', async () => { + const screenshot = await makeScreenshot({ + style: { + width: '14rem', + 'padding-top': '3rem', + }, + selector: '[data-visual-test="drawer-list-disabled"]', }) - } + expect(screenshot).toMatchImageSnapshot() + }) }) diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/DrawerList.test.tsx b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/DrawerList.test.tsx index 2ff704cfec1..78ad040941a 100644 --- a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/DrawerList.test.tsx +++ b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/DrawerList.test.tsx @@ -5,10 +5,16 @@ import React from 'react' import { axeComponent, loadScss } from '../../../core/jest/jestSetup' -import { act, render, screen, waitFor } from '@testing-library/react' +import { + act, + render, + screen, + fireEvent, + waitFor, +} from '@testing-library/react' import DrawerList, { DrawerListAllProps, - DrawerListDataObjectUnion, + DrawerListDataArray, DrawerListData, } from '../DrawerList' @@ -32,7 +38,7 @@ const props: DrawerListAllProps = { no_animation: true, } -const mockData: DrawerListDataObjectUnion[] = [ +const mockData: DrawerListDataArray = [ { content: ['1234 56 78901', 'Brukskonto - Kari Nordmann'], }, @@ -81,6 +87,92 @@ describe('DrawerList component', () => { ).toBeInTheDocument() }) + describe('with disabled option', () => { + const disabledOptionProps = { + skip_portal: true, + opened: true, + no_animation: true, + data: [ + { content: 'item 1' }, + { disabled: true, content: 'item 2' }, + { content: 'item 3' }, + ], + } + + it('has correct attributes', async () => { + render() + + const options = document.querySelectorAll('.dnb-drawer-list__option') + expect(options[1].getAttribute('disabled')).toEqual('') + expect(options[1].getAttribute('aria-disabled')).toEqual('true') + }) + + it('sends on_select events', async () => { + const on_select = jest.fn() + + render() + + keydown(40) // down + await waitFor(() => { + expect(on_select).toHaveBeenCalledTimes(1) + expect(on_select.mock.calls[0][0].active_item).toBe(0) + }) + + keydown(40) // down + await waitFor(() => { + // on_select is called when navigating to disabled item + expect(on_select).toHaveBeenCalledTimes(2) + expect(on_select.mock.calls[1][0].active_item).toBe(1) + expect(on_select.mock.calls[1][0].data.disabled).toBe(true) + }) + + keydown(40) // down + await waitFor(() => { + // navigates to next item + expect(on_select).toHaveBeenCalledTimes(3) + expect(on_select.mock.calls[2][0].active_item).toBe(2) + }) + }) + + it('can not be clicked', async () => { + const on_change = jest.fn() + const on_select = jest.fn() + + render( + + ) + + keydown(40) // down + keydown(40) // down + await waitFor(() => { + // verify item is disabled + expect(on_select).toHaveBeenCalledTimes(2) + expect(on_select.mock.calls[1][0].active_item).toBe(1) + expect(on_select.mock.calls[1][0].data.disabled).toBe(true) + }) + + keydown(13) // enter + await waitFor(() => { + // on_change and on_select is not called when attempting to chose a disabled item + expect(on_change).toHaveBeenCalledTimes(0) + expect(on_select).toHaveBeenCalledTimes(2) + }) + + await fireEvent.click( + document.querySelectorAll('.dnb-drawer-list__option')[1] + ) + await waitFor(() => { + // on_change and on_select is not called when attempting to click a disabled item + expect(on_change).toHaveBeenCalledTimes(0) + expect(on_select).toHaveBeenCalledTimes(2) + }) + }) + }) + it('handles default_value correctly on forcing re-render', () => { const { rerender } = render( { const { rerender } = render( ) @@ -204,7 +296,7 @@ describe('DrawerList component', () => { rerender( @@ -214,7 +306,7 @@ describe('DrawerList component', () => { rerender( diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-for-sbanken-have-to-match-the-default-drawer-list.snap.png b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-for-sbanken-have-to-match-the-default-drawer-list.snap.png index c82c6740ebb..5a51cd430d5 100644 Binary files a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-for-sbanken-have-to-match-the-default-drawer-list.snap.png and b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-for-sbanken-have-to-match-the-default-drawer-list.snap.png differ diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-for-sbanken-have-to-match-the-disabled-option.snap.png b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-for-sbanken-have-to-match-the-disabled-option.snap.png new file mode 100644 index 00000000000..8257ee8ac1b Binary files /dev/null and b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-for-sbanken-have-to-match-the-disabled-option.snap.png differ diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-for-sbanken-have-to-match-the-sbanken-drawer-list.snap.png b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-for-sbanken-have-to-match-the-sbanken-drawer-list.snap.png deleted file mode 100644 index c82c6740ebb..00000000000 Binary files a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-for-sbanken-have-to-match-the-sbanken-drawer-list.snap.png and /dev/null differ diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-for-ui-have-to-match-the-disabled-option.snap.png b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-for-ui-have-to-match-the-disabled-option.snap.png new file mode 100644 index 00000000000..3f7766011e0 Binary files /dev/null and b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-for-ui-have-to-match-the-disabled-option.snap.png differ diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-have-to-match-the-default-drawer-list.snap.png b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-have-to-match-the-default-drawer-list.snap.png deleted file mode 100644 index e44f0190b8d..00000000000 Binary files a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__image_snapshots__/drawerlist-have-to-match-the-default-drawer-list.snap.png and /dev/null differ diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__snapshots__/DrawerList.test.tsx.snap b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__snapshots__/DrawerList.test.tsx.snap index fe3fea65d45..f02b3068b3c 100644 --- a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__snapshots__/DrawerList.test.tsx.snap +++ b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/__snapshots__/DrawerList.test.tsx.snap @@ -30,6 +30,8 @@ exports[`DrawerList scss has to match style dependencies css 1`] = ` --drawer-list-option-inner-background: var(--color-white); --drawer-list-list-background: var(--color-white); --drawer-list-list-line-height: var(--line-height-basis); + --drawer-list-option-disabled-background: var(--color-white); + --drawer-list-option-disabled-color: var(--color-black-20); display: block; position: relative; width: inherit; @@ -186,6 +188,13 @@ html[data-visual-test] .dnb-drawer-list--scroll .dnb-drawer-list__options, .dnb- cursor: default; pointer-events: none; } +.dnb-drawer-list__option[disabled] { + --drawer-list-option-inner-background: var( + --drawer-list-option-disabled-background + ); + color: var(--drawer-list-option-disabled-color); + cursor: not-allowed; +} .dnb-drawer-list__triangle { position: absolute; top: calc(var(--drawer-list-focus-border-width) - var(--drawer-list-height) / 2); diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/style/dnb-drawer-list.scss b/packages/dnb-eufemia/src/fragments/drawer-list/style/dnb-drawer-list.scss index 5aa73d34308..5a922d242d5 100644 --- a/packages/dnb-eufemia/src/fragments/drawer-list/style/dnb-drawer-list.scss +++ b/packages/dnb-eufemia/src/fragments/drawer-list/style/dnb-drawer-list.scss @@ -57,6 +57,9 @@ --drawer-list-option-inner-background: var(--color-white); --drawer-list-list-background: var(--color-white); --drawer-list-list-line-height: var(--line-height-basis); + // Disabled option + --drawer-list-option-disabled-background: var(--color-white); + --drawer-list-option-disabled-color: var(--color-black-20); display: block; // has to be block element so we can se the content position: relative; @@ -243,6 +246,14 @@ cursor: default; pointer-events: none; } + + &[disabled] { + --drawer-list-option-inner-background: var( + --drawer-list-option-disabled-background + ); + color: var(--drawer-list-option-disabled-color); + cursor: not-allowed; + } } // arrow diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/style/themes/dnb-drawer-list-theme-sbanken.scss b/packages/dnb-eufemia/src/fragments/drawer-list/style/themes/dnb-drawer-list-theme-sbanken.scss index a836fcf119c..4ae5ccc30d2 100644 --- a/packages/dnb-eufemia/src/fragments/drawer-list/style/themes/dnb-drawer-list-theme-sbanken.scss +++ b/packages/dnb-eufemia/src/fragments/drawer-list/style/themes/dnb-drawer-list-theme-sbanken.scss @@ -13,9 +13,14 @@ --drawer-list-options-border-radius: 0 0 0.5rem 0.5rem; --drawer-list-options-border-radius-reversed: 0.5rem 0.5rem 0 0; --drawer-list-option-border-width: 0.125rem; + --drawer-list-option-border-color: transparent; --drawer-list-option-inner-border-display: none; --drawer-list-option-inner-background: transparent; --drawer-list-list-background: var(--sb-color-white); + --drawer-list-list-option-background: transparent; + // Disabled option + --drawer-list-option-disabled-background: var(--sb-color-gray-light); + --drawer-list-option-disabled-color: var(--sb-color-gray-dark); &__list { margin-top: calc( @@ -31,14 +36,14 @@ &__option__inner { overflow: visible; - background-color: inherit; padding: 1rem 0.75rem; + margin: 0 var(--drawer-list-option-border-width); &::before { content: ''; display: block; position: absolute; - bottom: -3px; + top: -1px; left: 0.5rem; width: calc(100% - 1rem); height: 1px; @@ -56,12 +61,9 @@ } &__option { - border: var(--drawer-list-option-border-width) solid - var(--sb-color-gray-dark-2); - border-bottom-color: transparent; - border-top-color: transparent; - border-right-color: transparent; // Fix ugly border with scrollbar - background-color: transparent; + box-shadow: inset 0 0 0 var(--drawer-list-option-border-width) + var(--drawer-list-option-border-color); + background: var(--drawer-list-list-option-background); z-index: 0; &__item.item-nr-1 { @@ -69,7 +71,7 @@ } @include hover() { - border-color: var(--sb-color-violet); + --drawer-list-option-border-color: var(--sb-color-violet); z-index: 1; .dnb-drawer-list__option__inner::before { @@ -78,7 +80,7 @@ } @include active() { - border-color: var(--sb-color-violet); + --drawer-list-option-border-color: var(--sb-color-violet); z-index: 1; .dnb-drawer-list__option__inner::before { @@ -87,7 +89,7 @@ } &--selected { - background-color: var(--sb-color-violet); + --drawer-list-list-option-background: var(--sb-color-violet); color: var(--sb-color-white); border-right-color: var( --sb-color-gray-dark-2 @@ -122,28 +124,33 @@ @include allAbove(small) { &--selected &__suffix { z-index: 2; // over check icon - background-color: inherit; // to "hide" the check icon + background-color: var( + --drawer-list-list-option-background + ); // to "hide" the check icon } } + &.first-of-type &__inner::before { + content: none; + } + &.last-of-type { border-radius: var(--drawer-list-options-border-radius); - - .dnb-drawer-list__option__inner::before { - display: none; - } } &:focus-visible, &--focus { - border-color: var(--sb-color-blue-dark); - outline: 0.0625rem solid var(--sb-color-blue-dark); - background-color: var(--sb-color-blue-light-3); + --drawer-list-option-border-color: var(--sb-color-blue-dark); + --drawer-list-list-option-background: var(--sb-color-blue-light-3); color: var(--sb-color-blue-dark); font-weight: var(--sb-font-weight-medium); z-index: 1; /* stylelint-disable no-descending-specificity */ + .dnb-drawer-list__option__inner { + --drawer-list-option-inner-background: transparent; + } + .dnb-drawer-list__option__inner::before { display: var(--drawer-list-option-inner-border-display); } diff --git a/packages/dnb-eufemia/src/shared/VisibilityByTheme.tsx b/packages/dnb-eufemia/src/shared/VisibilityByTheme.tsx index b65a1643106..26fc9451402 100644 --- a/packages/dnb-eufemia/src/shared/VisibilityByTheme.tsx +++ b/packages/dnb-eufemia/src/shared/VisibilityByTheme.tsx @@ -61,3 +61,16 @@ export default function VisibilityByTheme({ }) } } + +VisibilityByTheme.Name = function ThemeName() { + const theme = useTheme() + if (theme.isEiendom) { + return 'Eiendom' + } + if (theme.isSbanken) { + return 'Sbanken' + } + if (theme.isUi) { + return 'DNB' + } +} diff --git a/packages/dnb-eufemia/src/style/elements/__tests__/__snapshots__/Elements.test.js.snap b/packages/dnb-eufemia/src/style/elements/__tests__/__snapshots__/Elements.test.js.snap index f2364b55c58..05c84747c72 100644 --- a/packages/dnb-eufemia/src/style/elements/__tests__/__snapshots__/Elements.test.js.snap +++ b/packages/dnb-eufemia/src/style/elements/__tests__/__snapshots__/Elements.test.js.snap @@ -610,7 +610,6 @@ del .dnb-code { * */ .dnb-lead, .dnb-h--xx-large, .dnb-h--x-large, .dnb-h--large, .dnb-h--medium, .dnb-h--basis, .dnb-h--small, .dnb-h--x-small, .dnb-core-style .dnb-lead, .dnb-core-style .dnb-h--xx-large, .dnb-core-style .dnb-h--x-large, .dnb-core-style .dnb-h--large, .dnb-core-style .dnb-h--medium, .dnb-core-style .dnb-h--basis, .dnb-core-style .dnb-h--small, .dnb-core-style .dnb-h--x-small, h1, h2, h3, h4, h5, h6, p, b, small, strong, .dnb-p, .dnb-small, .dnb-table, sub, sup { - --typography-h-default-font-family: var(--font-family-default); --typography-h-default-font-weight: var(--font-weight-medium); --typography-h-xx-large-font-size: var(--font-size-xx-large); --typography-h-xx-large-line-height: var(--line-height-xx-large); @@ -672,7 +671,7 @@ del .dnb-code { .dnb-core-style .dnb-h--small, .dnb-core-style .dnb-h--x-small { padding: 0; - font-family: var(--typography-h-default-font-family); + font-family: var(--font-family-heading); } .dnb-lead:not([class*=dnb-space]), .dnb-h--xx-large:not([class*=dnb-space]), @@ -793,13 +792,13 @@ sub { font-size: var(--typography-lead-small-font-size); line-height: var(--typography-lead-small-line-height); } -.dnb-p--medium { - font-weight: var(--font-weight-medium); -} .dnb-p b, .dnb-p strong { font-weight: var(--font-weight-medium); } +.dnb-p--medium { + font-weight: var(--font-weight-medium); +} .dnb-p--bold { font-weight: var(--font-weight-bold); } @@ -823,7 +822,7 @@ sub { font-size: var(--font-size-medium); line-height: var(--line-height-medium); } -.dnb-p--small, .dnb-p__size--small, .dnb-p > small { +.dnb-p--small, .dnb-p__size--small { font-size: var(--font-size-small); line-height: var(--line-height-small); } @@ -831,12 +830,97 @@ sub { font-size: var(--font-size-x-small); line-height: var(--line-height-x-small); } +.dnb-p > small { + font-size: var(--font-size-small); + line-height: var(--line-height-small); +} .dnb-p > cite { font-weight: var(--font-weight-medium); line-height: var(--line-height-basis); font-style: italic; } +/* + * Typography + * Universal set of helper classes that do not have a specific element. + * The class ".dnb-t" does nothing, only it's modifiers ".dnb-t__[***]" do. + */ +.dnb-t__size--xx-large { + font-size: var(--font-size-xx-large); +} +.dnb-t__size--x-large { + font-size: var(--font-size-x-large); +} +.dnb-t__size--large { + font-size: var(--font-size-large); +} +.dnb-t__size--medium { + font-size: var(--font-size-medium); +} +.dnb-t__size--basis { + font-size: var(--font-size-basis); +} +.dnb-t__size--small { + font-size: var(--font-size-small); +} +.dnb-t__size--x-small { + font-size: var(--font-size-x-small); +} +.dnb-t__line-height--xx-large { + line-height: var(--line-height-xx-large); +} +.dnb-t__line-height--x-large { + line-height: var(--line-height-x-large); +} +.dnb-t__line-height--large { + line-height: var(--line-height-large); +} +.dnb-t__line-height--medium { + line-height: var(--line-height-medium); +} +.dnb-t__line-height--basis { + line-height: var(--line-height-basis); +} +.dnb-t__line-height--small { + line-height: var(--line-height-small); +} +.dnb-t__line-height--x-small { + line-height: var(--line-height-x-small); +} +.dnb-t__weight--regular { + font-weight: var(--font-weight-regular); +} +.dnb-t__weight--medium { + font-weight: var(--font-weight-medium); +} +.dnb-t__weight--bold { + font-weight: var(--font-weight-bold); +} +.dnb-t__align--center { + text-align: center; +} +.dnb-t__align--left { + text-align: left; +} +.dnb-t__align--right { + text-align: right; +} +.dnb-t__family--default { + font-family: var(--font-family-default); +} +.dnb-t__family--heading { + font-family: var(--font-family-heading); +} +.dnb-t__family--monospace { + font-family: var(--font-family-monospace); +} +.dnb-t__decoration--underline { + text-decoration: underline; +} +.dnb-t__slant--italic { + font-style: italic; +} + .dnb-table b, .dnb-table strong { font-weight: var(--font-weight-medium); diff --git a/packages/dnb-eufemia/src/style/themes/theme-eiendom/properties.js b/packages/dnb-eufemia/src/style/themes/theme-eiendom/properties.js index b91cec0012b..3daa8ce876d 100644 --- a/packages/dnb-eufemia/src/style/themes/theme-eiendom/properties.js +++ b/packages/dnb-eufemia/src/style/themes/theme-eiendom/properties.js @@ -2,7 +2,7 @@ export default { '--sb-font-family-default': '"Roboto", "Helvetica", "Arial", sans-serif', - '--sb-font-family-headings': '"MaisonNeueHeadings", "Roboto", "Helvetica",', + '--sb-font-family-heading': '"MaisonNeueHeadings", "Roboto", "Helvetica",', '--sb-font-weight-default': 'normal', '--sb-font-weight-basis': 'normal', '--sb-font-weight-regular': 'normal', @@ -94,6 +94,7 @@ export default { '--color-emerald-green-25': '#c4d4d6', '--color-emerald-green-10': '#e8eeef', '--font-family-default': '"DNB", sans-serif', + '--font-family-heading': 'var(--font-family-default)', '--font-family-monospace': '"DNBMono", "Menlo", "Consolas", "Roboto Mono",', '--font-weight-default': 'normal', '--font-weight-basis': 'normal', diff --git a/packages/dnb-eufemia/src/style/themes/theme-sbanken/fonts.scss b/packages/dnb-eufemia/src/style/themes/theme-sbanken/fonts.scss index 5f474789e9f..fdfe031565d 100644 --- a/packages/dnb-eufemia/src/style/themes/theme-sbanken/fonts.scss +++ b/packages/dnb-eufemia/src/style/themes/theme-sbanken/fonts.scss @@ -13,14 +13,18 @@ font-style: normal; } -// For backwards compatibility -.dnb-typo-medium, -.dnb-typo-bold { +.dnb-typo-medium { font-family: var(--sb-font-family-default); font-weight: var(--sb-font-weight-medium); font-style: normal; } +.dnb-typo-bold { + font-family: var(--sb-font-family-default); + font-weight: var(--sb-font-weight-bold); + font-style: normal; +} + $fonts-path: '../../../../assets/fonts/sbanken' !default; // Maison diff --git a/packages/dnb-eufemia/src/style/themes/theme-sbanken/properties.js b/packages/dnb-eufemia/src/style/themes/theme-sbanken/properties.js index e0ba26fd86d..459b5543cb9 100644 --- a/packages/dnb-eufemia/src/style/themes/theme-sbanken/properties.js +++ b/packages/dnb-eufemia/src/style/themes/theme-sbanken/properties.js @@ -2,7 +2,7 @@ export default { '--sb-font-family-default': '"Roboto", "Helvetica", "Arial", sans-serif', - '--sb-font-family-headings': '"MaisonNeueHeadings", "Roboto", "Helvetica",', + '--sb-font-family-heading': '"MaisonNeueHeadings", "Roboto", "Helvetica",', '--sb-font-weight-default': 'normal', '--sb-font-weight-basis': 'normal', '--sb-font-weight-regular': 'normal', @@ -94,6 +94,7 @@ export default { '--color-emerald-green-25': '#c4d4d6', '--color-emerald-green-10': '#e8eeef', '--font-family-default': 'var(--sb-font-family-default)', + '--font-family-heading': 'var(--sb-font-family-heading)', '--font-family-monospace': '"DNBMono", "Menlo", "Consolas", "Roboto Mono",', '--font-weight-default': 'normal', '--font-weight-basis': 'normal', diff --git a/packages/dnb-eufemia/src/style/themes/theme-sbanken/properties.scss b/packages/dnb-eufemia/src/style/themes/theme-sbanken/properties.scss index ce17072a595..88509776686 100644 --- a/packages/dnb-eufemia/src/style/themes/theme-sbanken/properties.scss +++ b/packages/dnb-eufemia/src/style/themes/theme-sbanken/properties.scss @@ -6,7 +6,7 @@ :root { // Typography Family --sb-font-family-default: 'Roboto', 'Helvetica', 'Arial', sans-serif; - --sb-font-family-headings: 'MaisonNeueHeadings', 'Roboto', 'Helvetica', + --sb-font-family-heading: 'MaisonNeueHeadings', 'Roboto', 'Helvetica', 'Arial', sans-serif; // Typography Weights diff --git a/packages/dnb-eufemia/src/style/themes/theme-sbanken/theme-mapping.scss b/packages/dnb-eufemia/src/style/themes/theme-sbanken/theme-mapping.scss index bf40d28faa0..6acaf9f92d0 100644 --- a/packages/dnb-eufemia/src/style/themes/theme-sbanken/theme-mapping.scss +++ b/packages/dnb-eufemia/src/style/themes/theme-sbanken/theme-mapping.scss @@ -12,6 +12,7 @@ // font-family --font-family-default: var(--sb-font-family-default); + --font-family-heading: var(--sb-font-family-heading); // font-weight --font-weight-medium: var(--sb-font-weight-medium); diff --git a/packages/dnb-eufemia/src/style/themes/theme-ui/properties.js b/packages/dnb-eufemia/src/style/themes/theme-ui/properties.js index b91cec0012b..3daa8ce876d 100644 --- a/packages/dnb-eufemia/src/style/themes/theme-ui/properties.js +++ b/packages/dnb-eufemia/src/style/themes/theme-ui/properties.js @@ -2,7 +2,7 @@ export default { '--sb-font-family-default': '"Roboto", "Helvetica", "Arial", sans-serif', - '--sb-font-family-headings': '"MaisonNeueHeadings", "Roboto", "Helvetica",', + '--sb-font-family-heading': '"MaisonNeueHeadings", "Roboto", "Helvetica",', '--sb-font-weight-default': 'normal', '--sb-font-weight-basis': 'normal', '--sb-font-weight-regular': 'normal', @@ -94,6 +94,7 @@ export default { '--color-emerald-green-25': '#c4d4d6', '--color-emerald-green-10': '#e8eeef', '--font-family-default': '"DNB", sans-serif', + '--font-family-heading': 'var(--font-family-default)', '--font-family-monospace': '"DNBMono", "Menlo", "Consolas", "Roboto Mono",', '--font-weight-default': 'normal', '--font-weight-basis': 'normal', diff --git a/packages/dnb-eufemia/src/style/themes/theme-ui/properties.scss b/packages/dnb-eufemia/src/style/themes/theme-ui/properties.scss index 79aeb2eb6a3..df1ae185498 100644 --- a/packages/dnb-eufemia/src/style/themes/theme-ui/properties.scss +++ b/packages/dnb-eufemia/src/style/themes/theme-ui/properties.scss @@ -6,6 +6,7 @@ :root { // Typography Family --font-family-default: 'DNB', sans-serif; + --font-family-heading: var(--font-family-default); --font-family-monospace: 'DNBMono', 'Menlo', 'Consolas', 'Roboto Mono', 'Ubuntu Monospace', 'Noto Mono', 'Oxygen Mono', 'Liberation Mono', monospace;