` and the `` tags are build in the `@dnb/eufemia`.
-So simply use them in your syntax:
+
+So simply use them for your code syntax:
```html
- My Formatted example
in a Paragraph
+ My formatted text
inside a paragraph
- One line
- New lines
+ Code Syntax
```
-### Code and Typography
+#### Code and Typography
-`` snippets look best when rendered in a _Monotype_ font. Developers will normally have installed some of these fonts on their devices. Here is an example of CSS `font-family` usage:
+When you use `` or `` – the DNB _DNBMono_ font is used, like so:
```css
font-family: var(--font-family-monospace);
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/payment-card/properties.md b/packages/dnb-design-system-portal/src/docs/uilib/extensions/payment-card/properties.md
index e1e0fe65b07..2af6beb2bc8 100644
--- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/payment-card/properties.md
+++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/payment-card/properties.md
@@ -6,7 +6,7 @@ showTabs: true
| Properties | Description |
| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `product_code` | _(mandatory)_ if product code matches one of the codes in the list the card will get that design, if no match is found Default design will be used. |
+| `product_code` | _(required)_ if product code matches one of the codes in the list the card will get that design, if no match is found Default design will be used. |
| `raw_data` | _(optional)_ useful if you want to create custom cards. See Card data properties. |
| `card_status` | _(optional)_ use one of these: `active`, `blocked`, `expired`. Defaults to `active`. |
| `variant` | _(optional)_ defines the appearance. Use one of these: `normal` or `compact`. Defaults to `normal`. |
@@ -20,25 +20,25 @@ showTabs: true
| Properties | Type | Description |
| ------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `productCode` | `string` | _(mandatory)_ product code for the given card. |
-| `productName` | `string` | _(mandatory)_ product name. Can be blank. |
-| `displayName` | `string` | _(mandatory)_ the visible product name. Can be empty. |
-| `cardDesign` | `object` | _(mandatory)_ object that describes the style properties of the card. can be imported from `@dnb/eufemia/extensions/payment-card/utils/CardDesigns` (see available designs below) or a custom one can be created. |
-| `cardType` | `Union Type` | _(mandatory)_ import CardType from `@dnb/eufemia/extensions/payment-card/utils/Types` to use. Can be CardType.Visa, CardType.Mastercard or CardType.None |
-| `productType` | `Union Type` | _(mandatory)_ import ProductType from `@dnb/eufemia/extensions/payment-card/utils/Types` to use. Can be ProductType.BankAxept, ProductType.Saga, ProductType.PrivateBanking or ProductType.None |
+| `productCode` | `string` | _(required)_ product code for the given card. |
+| `productName` | `string` | _(required)_ product name. Can be blank. |
+| `displayName` | `string` | _(required)_ the visible product name. Can be empty. |
+| `cardDesign` | `object` | _(required)_ object that describes the style properties of the card. can be imported from `@dnb/eufemia/extensions/payment-card/utils/CardDesigns` (see available designs below) or a custom one can be created. |
+| `cardType` | `Union Type` | _(required)_ import CardType from `@dnb/eufemia/extensions/payment-card/utils/Types` to use. Can be CardType.Visa, CardType.Mastercard or CardType.None |
+| `productType` | `Union Type` | _(required)_ import ProductType from `@dnb/eufemia/extensions/payment-card/utils/Types` to use. Can be ProductType.BankAxept, ProductType.Saga, ProductType.PrivateBanking or ProductType.None |
## Card Design
| Properties | Type | Description |
| ---------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `name` | `string` | _(mandatory)_ string Name of design |
-| `cardStyle` | `string` | _(mandatory)_ css class. mainly to set background and color |
-| `bankLogo` | `Union Type` | _(mandatory)_ Union Type. import DNB from ./card/utils/Types to use. Can be DNB.Colored('HexValue') or DNB.Metalic |
-| `visa` | `Union Type` | _(mandatory)_ Union Type. import Visa from ./card/utils/Types to use. Can be Visa.Colored('HexValue') or Visa.Metalic |
-| `mastercard` | `Union Type` | _(mandatory)_ Union Type. import Mastercard from ./card/utils/Types to use. Can be Mastercard.Default, Mastercard.DefaultWhite Mastercard.Metalic or Mastercard.MetalicBlack |
-| `bankAxept` | `Union Type` | _(mandatory)_ Union Type. import BankAxept from ./card/utils/Types to use. Can be BankAxept.White or BankAxept.Black |
-| `saga` | `Union Type` | _(mandatory)_ Union Type. import Saga from ./card/utils/Types to use. Can be Saga.Gold, Saga.Platinum, Saga.VisaPlatinum, or Saga.None |
-| `privateBanking` | `Union Type` | _(mandatory)_ Union Type. import PB from ./card/utils/Types to use. Can be PB.Default, PB.Platinum or PB.None |
+| `name` | `string` | _(required)_ string Name of design |
+| `cardStyle` | `string` | _(required)_ css class. mainly to set background and color |
+| `bankLogo` | `Union Type` | _(required)_ Union Type. import DNB from ./card/utils/Types to use. Can be DNB.Colored('HexValue') or DNB.Metalic |
+| `visa` | `Union Type` | _(required)_ Union Type. import Visa from ./card/utils/Types to use. Can be Visa.Colored('HexValue') or Visa.Metalic |
+| `mastercard` | `Union Type` | _(required)_ Union Type. import Mastercard from ./card/utils/Types to use. Can be Mastercard.Default, Mastercard.DefaultWhite Mastercard.Metalic or Mastercard.MetalicBlack |
+| `bankAxept` | `Union Type` | _(required)_ Union Type. import BankAxept from ./card/utils/Types to use. Can be BankAxept.White or BankAxept.Black |
+| `saga` | `Union Type` | _(required)_ Union Type. import Saga from ./card/utils/Types to use. Can be Saga.Gold, Saga.Platinum, Saga.VisaPlatinum, or Saga.None |
+| `privateBanking` | `Union Type` | _(required)_ Union Type. import PB from ./card/utils/Types to use. Can be PB.Default, PB.Platinum or PB.None |
## List of designs
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/helpers/Examples.js b/packages/dnb-design-system-portal/src/docs/uilib/helpers/Examples.js
index da9d33ad0c2..a1b8f5dfeba 100644
--- a/packages/dnb-design-system-portal/src/docs/uilib/helpers/Examples.js
+++ b/packages/dnb-design-system-portal/src/docs/uilib/helpers/Examples.js
@@ -24,7 +24,7 @@ export function CoreStyleExample() {
Wrapper with the DNB Body Style (CSS reset)
- Read more about .dnb-core-style
+ Read more about .dnb-core-style
and Use Eufemia Styles elsewhere
diff --git a/packages/dnb-design-system-portal/src/shared/menu/SearchBar.js b/packages/dnb-design-system-portal/src/shared/menu/SearchBar.js
index 1bbb04226bd..358c3116d14 100644
--- a/packages/dnb-design-system-portal/src/shared/menu/SearchBar.js
+++ b/packages/dnb-design-system-portal/src/shared/menu/SearchBar.js
@@ -39,7 +39,7 @@ export const SearchBarInput = () => {
debounce(
({ value }) => {
searchIndex.current
- .search(value)
+ ?.search(value)
.then(({ hits }) => {
updateData(
makeHitsHumanFriendly({ hits, setHidden, emptyData })
diff --git a/packages/dnb-design-system-portal/src/shared/tags/Tabbar.js b/packages/dnb-design-system-portal/src/shared/tags/Tabbar.js
index 6e60c518f2a..77823250be3 100644
--- a/packages/dnb-design-system-portal/src/shared/tags/Tabbar.js
+++ b/packages/dnb-design-system-portal/src/shared/tags/Tabbar.js
@@ -147,7 +147,7 @@ Tabbar.defaultProps = {
children: null,
}
Tabbar.ContentWrapper = (props) => (
-
+
)
const tabsWrapperStyle = css`
@@ -175,11 +175,13 @@ const tabsWrapperStyle = css`
}
@media screen and (max-width: 40em) {
- ${'' /* .dnb-tabs__tabs {
+ ${
+ '' /* .dnb-tabs__tabs {
NB: Now this gets handled automatically
margin: 0 -2rem;
padding: 0 2rem;
- } */}
+ } */
+ }
.dnb-tabs__tabs .dnb-button.fullscreen {
display: none;
}
diff --git a/packages/dnb-eufemia-sandbox/stories/components/InfoCard.js b/packages/dnb-eufemia-sandbox/stories/components/InfoCard.js
new file mode 100644
index 00000000000..0341ab55d34
--- /dev/null
+++ b/packages/dnb-eufemia-sandbox/stories/components/InfoCard.js
@@ -0,0 +1,69 @@
+/**
+ * @dnb/eufemia Component Story
+ *
+ */
+
+ import React from 'react'
+ import { Wrapper, Box } from '../helpers'
+ // import styled from '@emotion/styled'
+
+ import { InfoCard } from '@dnb/eufemia/src/components'
+ import { add as Svg } from '@dnb/eufemia/src/icons'
+
+ export default {
+ title: 'Eufemia/Components/InfoCard',
+ }
+
+ export const InfoCardSandbox = () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ console.log('accept')} accept_button_text='accept' text='This is a description of some information or a tip that will inform the user of something that will help them.' />
+ console.log('accept')} accept_button_text='Accept' on_close={() => console.log('close')} close_button_text='Close' text='This is a description of some information or a tip that will inform the user of something that will help them.' />
+
+
+
+ console.log('accept')} accept_button_text='Accept' on_close={() => console.log('close')} close_button_text='Close' text='This is a description of some information or a tip that will inform the user of something that will help them.' />
+
+
+
+
+ )
+
diff --git a/packages/dnb-eufemia-sandbox/stories/components/Pagination.js b/packages/dnb-eufemia-sandbox/stories/components/Pagination.js
index 05eb6b5c136..93e85b9fe95 100644
--- a/packages/dnb-eufemia-sandbox/stories/components/Pagination.js
+++ b/packages/dnb-eufemia-sandbox/stories/components/Pagination.js
@@ -11,6 +11,7 @@ import { P } from '@dnb/eufemia/src/elements'
import { Button, Section } from '@dnb/eufemia/src/components'
import Pagination, {
createPagination,
+ Bar,
} from '@dnb/eufemia/src/components/pagination/Pagination'
export default {
@@ -40,7 +41,7 @@ for (let i = 1; i <= 300; i++) {
tableItems.push({ ssn: i, text: String(i), expanded: false })
}
-export const Infinity = () => {
+export const InfinitySandbox = () => {
const props = {
current_page: 3,
// page_count: 30,
@@ -66,11 +67,43 @@ export const Infinity = () => {
)
}
+export const PaginationNoChildren = () => (
+
+
+ {
+ console.log(page)
+ }}
+ />
+
+
+ {
+ console.log(page)
+ }}
+ />
+
+
+)
+
export const PaginationSandbox = () => (
+
+ {
+ console.log(page)
+ }}
+ />
+
{({ page, setContent }) => {
diff --git a/packages/dnb-eufemia-sandbox/stories/components/Tag.tsx b/packages/dnb-eufemia-sandbox/stories/components/Tag.tsx
index 18983d35323..d1db75b397d 100644
--- a/packages/dnb-eufemia-sandbox/stories/components/Tag.tsx
+++ b/packages/dnb-eufemia-sandbox/stories/components/Tag.tsx
@@ -7,7 +7,23 @@ import React, { useState } from 'react'
import { Tag } from '@dnb/eufemia/src/components'
import { Provider } from '@dnb/eufemia/src/shared'
import { Box, Wrapper } from '../helpers'
-import { account, bell, car_1, chip, send } from '@dnb/eufemia/src/icons'
+import {
+ account,
+ bell,
+ car_1,
+ chip,
+ save,
+ scooter,
+ scissors,
+ share_ios,
+ save_medium,
+ scissors_medium,
+ scooter_medium,
+ share_ios_medium,
+ tag,
+} from '@dnb/eufemia/src/icons'
+
+import { TagProps } from '@dnb/eufemia/src/components/tag/Tag'
export default {
title: 'Eufemia/Components/Tag',
@@ -16,7 +32,9 @@ export default {
export const Default = () => {
return (
- Default
+
+ Default
+
)
}
@@ -28,7 +46,9 @@ export const TagSandbox = () => {
-
+
+
+
)
@@ -38,10 +58,11 @@ export const TagMultiple = () => {
return (
- {' '}
- {' '}
- {' '}
- {' '}
+
+ {' '}
+ {' '}
+ {' '}
+
)
@@ -50,7 +71,9 @@ export const TagMultiple = () => {
export const TagWithIcon = () => {
return (
-
+
+
+
)
}
@@ -58,7 +81,9 @@ export const TagWithIcon = () => {
export const TagWithSkeleton = () => {
return (
-
+
+
+
)
}
@@ -66,7 +91,207 @@ export const TagWithSkeleton = () => {
export const TagWithSpace = () => {
return (
-
+
+
+
+
+ )
+}
+
+const tags: TagProps[] = [
+ {
+ icon: chip,
+ text: 'Data',
+ },
+]
+
+export const TagGroupData = () => {
+ return (
+
+
+
+ )
+}
+
+export const TagClickable = () => {
+ return (
+
+
+ {
+ console.log('Tag is clicked!')
+ }}
+ />{' '}
+
+
+ )
+}
+
+export const TagMultipleClickable = () => {
+ return (
+
+
+
+ {
+ console.log('Click1')
+ }}
+ text={'First'}
+ />{' '}
+ {
+ console.log('Click2')
+ }}
+ text={'Second'}
+ />{' '}
+ {
+ console.log('Click3')
+ }}
+ text={'Third'}
+ />{' '}
+ {
+ console.log('Click4')
+ }}
+ text={'Fourth'}
+ />{' '}
+
+
+
+ )
+}
+
+export const TagMixed = () => {
+ return (
+
+
+
+ {' '}
+ {
+ console.log('Click2')
+ }}
+ text={'Second'}
+ />{' '}
+ {' '}
+ {
+ console.log('Click4')
+ }}
+ text={'Fourth'}
+ />{' '}
+
+
+
+ )
+}
+
+export const TagWithMediumSizedIcons = () => {
+ return (
+
+
+
+ {' '}
+ {' '}
+ {' '}
+ {' '}
+
+
+
+ )
+}
+
+export const TagWithoutGroup = () => {
+ return (
+
+
)
}
+
+export const TagRemovable = () => {
+ return (
+
+
+
+ {
+ console.log('Removed!')
+ }}
+ />
+
+
+
+ )
+}
+
+export const TagRemovableClickable = () => {
+ return (
+
+
+
+ {
+ console.log('Clicked!')
+ }}
+ onDelete={() => {
+ console.log('Removed!')
+ }}
+ />
+
+
+
+ )
+}
+
+export const TagMultipleRemovable = () => {
+ return (
+
+
+
+ {
+ console.log('Delete1')
+ }}
+ text={'First'}
+ />{' '}
+ {
+ console.log('Delete2')
+ }}
+ text={'Second'}
+ />{' '}
+ {
+ console.log('Delete3')
+ }}
+ text={'Third'}
+ />{' '}
+ {
+ console.log('Delete4')
+ }}
+ text={'Fourth'}
+ />{' '}
+
+
+
+ )
+}
diff --git a/packages/dnb-eufemia-sandbox/stories/elements/Table.js b/packages/dnb-eufemia-sandbox/stories/elements/Table.js
deleted file mode 100644
index c27a6259795..00000000000
--- a/packages/dnb-eufemia-sandbox/stories/elements/Table.js
+++ /dev/null
@@ -1,332 +0,0 @@
-/**
- * @dnb/eufemia Element Story
- *
- */
-
-import React from 'react'
-import { Wrapper, Box } from '../helpers'
-import styled from '@emotion/styled'
-
-import {
- Button,
- Switch,
- Checkbox,
- Dropdown,
- Input,
- DatePicker,
- NumberFormat,
-} from '@dnb/eufemia/src/components'
-import { Table } from '@dnb/eufemia/src/elements'
-
-export default {
- title: 'Eufemia/Elements/Table',
-}
-
-const CustomWrapper = styled(Wrapper)`
- .mint_col {
- background-color: var(--color-mint-green-12);
- }
- .white_col {
- background-color: var(--color-white);
- }
-`
-
-export const TableSandbox = () => (
-
-
-
-
-
-
-
-
-
- A Table Caption
-
-
- .dnb-table__th
-
-
-
-
-
-
-
-
-
-
-
- {'.dnb-table__tr--even > .dnb-table__td'}
-
-
-
-
- {'.dnb-table__tr--odd > .dnb-table__td'}
-
-
-
-
-
-
-
- Superheros and sidekicks
-
-
-
-
-
-
-
- Batman
- Robin
- The Flash
- Kid Flash
-
-
- Skill
- Smarts
- Dex, acrobat
- Super speed
- Super speed
-
-
- Skill
- Smarts
- Dex, acrobat
- Super speed
- Super speed
-
-
-
-
-)
-
-const data = [
- {
- selected_value: 'AA',
- content: 'A',
- },
- {
- content: [
-
- 12345678902
- ,
- 'B',
- ],
- },
- {
- selected_value: 'CC',
- content: [
-
- 11345678962
- ,
- 'C',
- ],
- },
- {
- content: E ,
- },
- <>Custom content {'🔥'}>,
- [Custom content X {'🔥'} ],
- {
- content: 'EE',
- },
- {
- content: 'EEE',
- },
- {
- content: ['F', 'F', 'F', 'F', 'F'],
- },
- {
- content: 'G',
- },
- {
- content: 'H',
- },
-]
-
-export const StickyTable = () => (
-
- A Table Caption
-
-
-
- Header
-
-
-
-
-
-
-
-
-
-
-
-
- Empty row below
-
-
-
-
- {Array(12)
- .fill()
- .map(() => (
-
- Column which spans over four columns
- Five
- Scrollable table
-
- ))}
-
-
-)
diff --git a/packages/dnb-eufemia/babel.config.js b/packages/dnb-eufemia/babel.config.js
index 4b1252a73a8..f07e9b45757 100644
--- a/packages/dnb-eufemia/babel.config.js
+++ b/packages/dnb-eufemia/babel.config.js
@@ -75,6 +75,13 @@ const productionPlugins = [
mode: 'unsafe-wrap',
},
],
+ [
+ 'babel-plugin-fully-specified',
+ {
+ ensureFileExists: ['umd'].includes(process.env.BABEL_ENV),
+ includePackages: ['date-fns'],
+ },
+ ],
]
if (typeof process.env.BABEL_ENV !== 'undefined') {
diff --git a/packages/dnb-eufemia/jest.config.js b/packages/dnb-eufemia/jest.config.js
index 65bde1aad31..6f2c8a3eabc 100644
--- a/packages/dnb-eufemia/jest.config.js
+++ b/packages/dnb-eufemia/jest.config.js
@@ -4,7 +4,7 @@ const config = {
testRegex: '(/__tests__/\\.js|(\\.|/)(test|spec))\\.(js|jsx|ts|tsx)?$',
modulePathIgnorePatterns: [
'not_in_use',
- '/scripts/release/__tests__/postbuild.test*',
+ '/scripts/release/*',
'/build/',
'/assets/',
'/stories/',
diff --git a/packages/dnb-eufemia/package.json b/packages/dnb-eufemia/package.json
index 7de687bd6fb..ede0826af95 100644
--- a/packages/dnb-eufemia/package.json
+++ b/packages/dnb-eufemia/package.json
@@ -21,7 +21,7 @@
"audit:ci:yarn-outdated": "babel-node ./scripts/prepub/audit/toOpt && audit-ci --config ./audit-ci.json --package-manager=yarn --report-type full && babel-node ./scripts/prepub/audit/toDev",
"build": "yarn build:prebuild && yarn build:esm && yarn build:copy",
"prebuild:ci": "yarn build",
- "postbuild:ci": "./scripts/release/postbuild.sh && yarn test:build",
+ "postbuild:ci": "./scripts/release/postbuild.sh",
"build:cjs": "./scripts/release/babel-cjs.sh",
"build:clean": "rm -rf build/**",
"build:copy": "./scripts/release/copy-build-artifacts.sh",
@@ -32,9 +32,9 @@
"build:pack": "yarn build && yarn publish:prepare && cd ./build && yarn pack",
"build:prebuild": "babel-node ./scripts/prepub/runPrepub.js",
"build:resources": "babel-node ./scripts/prepub/resources/makeResourcesPackage.js",
- "build:types": "babel-node ./scripts/prepub/generateTypes.js && TEST_POST_TYPES=1 jest ./postTypeGeneration.test.js",
+ "build:types": "babel-node ./scripts/prepub/generateTypes.js && yarn test:types",
"build:types:dev": "nodemon --exec 'babel-node ./scripts/prepub/generateTypes.js' --ext js --watch './src/**/*' --watch './scripts/**/*'",
- "build:umd": "cross-env NODE_ENV=production BABEL_ENV=umd rollup -c ./rollup.config.js",
+ "build:umd": "./scripts/release/babel-umd.sh",
"precommit": "yarn lint-staged",
"dev:icons": "nodemon --exec 'babel-node ./scripts/tools/convertIcons' --ignore '/icons/**' --ignore '*.json'",
"dev:resources": "nodemon --exec 'babel-node ./scripts/prepub/resources/makeResourcesPackage.js' --ext js,html,json,css,scss --watch './build/style/**/*' --watch './scripts/**/*' --ignore '*.json'",
@@ -56,14 +56,13 @@
"lint:styles": "stylelint './src/**/*.{js,scss}'",
"lint:styles:staged": "stylelint './{src,scripts}/**/*.{js,scss}'",
"make-properties": "babel-node ./scripts/prepub/makeProperties",
- "publish:ci": "yarn publish:prepare && cd ./build && dotenv semantic-release",
+ "publish:prepare": "./scripts/release/publish-prepare.sh",
+ "publish:ci": "./scripts/release/publish-release.sh",
"publish:dry": "dotenv semantic-release --no-ci --dry-run",
- "publish:prepare": "babel-node ./scripts/release/prepareForRelease.js",
"reset": "rm -rf ./node_modules ./components ./elements ./es ./icons ./extensions ./shared ./style ./umd",
"skeleton:font": "nodemon --exec 'babel-node ./scripts/tools/createSkeletonFont.js'",
"start": "yarn workspace dnb-eufemia-sandbox start",
"test": "jest",
- "test:build": "jest --ci --rootDir ./scripts/release",
"test:ci": "jest --ci",
"test:screenshots": "jest --config=./jest.config.screenshots.js --forceExit --detectOpenHandles",
"test:screenshots:ci": "jest --config=./jest.config.screenshots.js --ci --forceExit --detectOpenHandles",
@@ -71,6 +70,7 @@
"test:screenshots:update": "jest --config=./jest.config.screenshots.js --updateSnapshot --detectOpenHandles",
"test:screenshots:watch": "jest --config=./jest.config.screenshots.js --forceExit --detectOpenHandles --watchAll --testPathPattern ",
"test:staged": "jest --bail --findRelatedTests ",
+ "test:types": "TEST_POST_TYPES=1 jest --ci --rootDir ./scripts/prepub ./postTypeGeneration.test.js",
"test:update": "jest --updateSnapshot",
"test:watch": "jest --watchAll --testPathPattern ",
"prettier:diff": "prettier --list-different '{scripts,src}/**/*.{md,js}'",
@@ -145,6 +145,7 @@
"@wojtekmaj/enzyme-adapter-react-17": "0.6.5",
"audit-ci": "5.1.2",
"babel-jest": "27.3.1",
+ "babel-plugin-fully-specified": "1.3.0",
"babel-plugin-optimize-clsx": "2.6.2",
"babel-plugin-search-and-replace": "1.1.0",
"babel-plugin-transform-react-remove-prop-types": "0.4.24",
diff --git a/packages/dnb-eufemia/scripts/prepub/tasks/__tests__/postTypeGeneration.test.js b/packages/dnb-eufemia/scripts/prepub/tasks/__tests__/postTypeGeneration.test.js
index 37e1858b549..dbeb73022ff 100644
--- a/packages/dnb-eufemia/scripts/prepub/tasks/__tests__/postTypeGeneration.test.js
+++ b/packages/dnb-eufemia/scripts/prepub/tasks/__tests__/postTypeGeneration.test.js
@@ -7,19 +7,19 @@ import fs from 'fs-extra'
if (process.env.TEST_POST_TYPES === '1') {
describe('generateTypes did generate', () => {
- it('Modal index', async () => {
+ it('Input index', async () => {
const content = await fs.readFile(
- require.resolve('@dnb/eufemia/src/components/Modal.d.ts'),
+ require.resolve('@dnb/eufemia/src/components/Input.d.ts'),
'utf-8'
)
- expect(content).toContain('export default Modal')
+ expect(content).toContain('export default Input')
})
- it('Modal components', async () => {
+ it('Input components', async () => {
const content = await fs.readFile(
- require.resolve('@dnb/eufemia/src/components/modal/Modal.d.ts'),
+ require.resolve('@dnb/eufemia/src/components/input/Input.d.ts'),
'utf-8'
)
- expect(content).toContain('export interface ModalProps')
+ expect(content).toContain('export interface InputProps')
})
})
} else {
diff --git a/packages/dnb-eufemia/scripts/release/__tests__/postbuild.test.js b/packages/dnb-eufemia/scripts/release/__tests__/postbuild.test.js
index 21cf49fc89a..61b5c13366b 100644
--- a/packages/dnb-eufemia/scripts/release/__tests__/postbuild.test.js
+++ b/packages/dnb-eufemia/scripts/release/__tests__/postbuild.test.js
@@ -6,12 +6,13 @@
import fs from 'fs-extra'
import path from 'path'
+import packpath from 'packpath'
const buildStages = ['es', 'esm', 'cjs']
describe('type definitions', () => {
it.each(buildStages)('has d.ts index file on stage %s', (stage) => {
- const file = path.resolve(`./build/${stage}/index.d.ts`)
+ const file = path.resolve(packpath.self(), `build/${stage}/index.d.ts`)
const exists = fs.existsSync(file)
expect(exists).toBe(true)
})
@@ -21,14 +22,20 @@ describe('type definitions', () => {
(stage) => {
expect(
fs.existsSync(
- path.resolve(`./build/${stage}/components/Input.d.ts`)
+ path.resolve(
+ packpath.self(),
+ `build/${stage}/components/Input.d.ts`
+ )
)
).toBe(true)
// To ensure babel did not compile the d.ts file
expect(
fs.readFileSync(
- path.resolve(`./build/${stage}/components/input/Input.d.ts`),
+ path.resolve(
+ packpath.self(),
+ `build/${stage}/components/input/Input.d.ts`
+ ),
'utf-8'
)
).toMatch(/export interface/g)
@@ -39,7 +46,9 @@ describe('type definitions', () => {
describe('babel build', () => {
it.each(buildStages)('has correctly compiled on stage %s', (stage) => {
expect(
- fs.existsSync(path.resolve(`./build/${stage}/components/Input.js`))
+ fs.existsSync(
+ path.resolve(packpath.self(), `build/${stage}/components/Input.js`)
+ )
).toBe(true)
switch (stage) {
@@ -47,7 +56,7 @@ describe('babel build', () => {
{
{
const content = fs.readFileSync(
- path.resolve(`./build/${stage}/index.js`),
+ path.resolve(packpath.self(), `build/${stage}/index.js`),
'utf-8'
)
expect(content).toContain(
@@ -57,13 +66,27 @@ describe('babel build', () => {
// Has extra cjs package
expect(
- fs.existsSync(path.resolve(`./build/${stage}/package.json`))
+ fs.existsSync(
+ path.resolve(
+ packpath.self(),
+ `build/${stage}/package.json`
+ )
+ )
).toBe(true)
+
+ const packageJson = fs.readJsonSync(
+ path.resolve(packpath.self(), `build/${stage}/package.json`)
+ )
+
+ expect(packageJson.type).toBe('commonjs')
}
{
const content = fs.readFileSync(
- path.resolve(`./build/${stage}/components/input/Input.js`),
+ path.resolve(
+ packpath.self(),
+ `build/${stage}/components/input/Input.js`
+ ),
'utf-8'
)
expect(content).toContain('var Input = function')
@@ -73,7 +96,8 @@ describe('babel build', () => {
{
const content = fs.readFileSync(
path.resolve(
- `./build/${stage}/components/breadcrumb/Breadcrumb.js`
+ packpath.self(),
+ `build/${stage}/components/breadcrumb/Breadcrumb.js`
),
'utf-8'
)
@@ -87,7 +111,7 @@ describe('babel build', () => {
{
{
const content = fs.readFileSync(
- path.resolve(`./build/${stage}/index.js`),
+ path.resolve(packpath.self(), `build/${stage}/index.js`),
'utf-8'
)
expect(content).toContain('export default {};')
@@ -95,7 +119,10 @@ describe('babel build', () => {
{
const content = fs.readFileSync(
- path.resolve(`./build/${stage}/components/input/Input.js`),
+ path.resolve(
+ packpath.self(),
+ `build/${stage}/components/input/Input.js`
+ ),
'utf-8'
)
expect(content).toContain('export { Input as default };')
@@ -108,7 +135,8 @@ describe('babel build', () => {
{
const content = fs.readFileSync(
path.resolve(
- `./build/${stage}/components/breadcrumb/Breadcrumb.js`
+ packpath.self(),
+ `build/${stage}/components/breadcrumb/Breadcrumb.js`
),
'utf-8'
)
@@ -125,7 +153,7 @@ describe('babel build', () => {
{
{
const content = fs.readFileSync(
- path.resolve(`./build/${stage}/index.js`),
+ path.resolve(packpath.self(), `build/${stage}/index.js`),
'utf-8'
)
expect(content).toContain('export default {};')
@@ -133,7 +161,10 @@ describe('babel build', () => {
{
const content = fs.readFileSync(
- path.resolve(`./build/${stage}/components/input/Input.js`),
+ path.resolve(
+ packpath.self(),
+ `build/${stage}/components/input/Input.js`
+ ),
'utf-8'
)
expect(content).toMatch(/export default class Input extends/g)
@@ -146,7 +177,8 @@ describe('babel build', () => {
{
const content = fs.readFileSync(
path.resolve(
- `./build/${stage}/components/breadcrumb/Breadcrumb.js`
+ packpath.self(),
+ `build/${stage}/components/breadcrumb/Breadcrumb.js`
),
'utf-8'
)
@@ -163,14 +195,16 @@ describe('babel build', () => {
if (stage == 'cjs') {
const exists = fs.existsSync(
path.resolve(
- `./build/${stage}/components/breadcrumb/Breadcrumb.tsx`
+ packpath.self(),
+ `build/${stage}/components/breadcrumb/Breadcrumb.tsx`
)
)
expect(exists).toBe(false)
} else {
const content = fs.readFileSync(
path.resolve(
- `./build/${stage}/components/breadcrumb/Breadcrumb.tsx`
+ packpath.self(),
+ `build/${stage}/components/breadcrumb/Breadcrumb.tsx`
),
'utf-8'
)
diff --git a/packages/dnb-eufemia/scripts/release/__tests__/prepareForRelease.test.js b/packages/dnb-eufemia/scripts/release/__tests__/prepareForRelease.test.js
index 9868b17eb02..e1be7adc53a 100644
--- a/packages/dnb-eufemia/scripts/release/__tests__/prepareForRelease.test.js
+++ b/packages/dnb-eufemia/scripts/release/__tests__/prepareForRelease.test.js
@@ -10,7 +10,7 @@ import { cleanupPackage, writeLibVersion } from '../prepareForRelease'
describe('cleanupPackage', () => {
it('gets preparered properly and have the expeted props', async () => {
- const filepath = path.resolve(packpath.self(), './package.json')
+ const filepath = path.resolve(packpath.self(), 'package.json')
const packageString = await fs.readFile(filepath, 'utf-8')
const cleanedPackage = await cleanupPackage({
packageString,
@@ -29,8 +29,6 @@ describe('cleanupPackage', () => {
describe('writeLibVersion', () => {
it('should write package version in to given files', async () => {
- // const fs = jest.createMockFromModule('fs')
-
const mockFile = './__mocks__/version.mock'
await writeLibVersion({
destPath: __dirname,
@@ -51,3 +49,65 @@ if(typeof window !== 'undefined'){
)
})
})
+
+describe('package.json', () => {
+ const packageJsonFile = path.resolve(
+ packpath.self(),
+ 'build/package.json'
+ )
+
+ let packageJson = {}
+
+ beforeAll(async () => {
+ packageJson = await fs.readJson(path.resolve(packageJsonFile))
+ })
+
+ it('exists inside build', () => {
+ expect(fs.existsSync(path.resolve(packageJsonFile))).toBeTruthy()
+ })
+
+ it('has type="module"', () => {
+ expect(packageJson.type).toBe('module')
+ })
+
+ it('has not these deleted fields', () => {
+ expect(packageJson.release).toBeFalsy()
+ expect(packageJson.scripts).toBeFalsy()
+ expect(packageJson.devDependencies).toBeFalsy()
+ expect(packageJson.resolutions).toBeFalsy()
+ expect(packageJson.volta).toBeFalsy()
+ })
+
+ it('has sideEffects fields', () => {
+ expect(packageJson.sideEffects).toEqual(
+ expect.arrayContaining([
+ '*.scss',
+ 'umd/*',
+ 'style/**/*',
+ 'es/style/**/*',
+ 'esm/style/**/*',
+ ])
+ )
+ })
+
+ it('has peerDependencies', () => {
+ expect(packageJson.peerDependencies).toEqual(
+ expect.objectContaining({
+ react: expect.anything(),
+ 'react-dom': expect.anything(),
+ })
+ )
+ })
+
+ it('has main and module fields to be equal', () => {
+ expect(packageJson.main).toBe('./index.js')
+ expect(packageJson.module).toBe('./index.js')
+ expect(packageJson.typings).toBe('./index.d.ts')
+ })
+
+ it('has publishConfig', () => {
+ expect(packageJson.publishConfig).toEqual(
+ expect.objectContaining({ access: 'public' })
+ )
+ })
+})
diff --git a/packages/dnb-eufemia/scripts/release/babel-cjs.sh b/packages/dnb-eufemia/scripts/release/babel-cjs.sh
index e0a03ec52ac..20e9303b9ff 100755
--- a/packages/dnb-eufemia/scripts/release/babel-cjs.sh
+++ b/packages/dnb-eufemia/scripts/release/babel-cjs.sh
@@ -1,5 +1,7 @@
#!/bin/bash
+set -e # Exit immediately if a command exits with a non-zero status.
+
echo 'Building cjs bundle ...'
cross-env \
diff --git a/packages/dnb-eufemia/scripts/release/babel-es.sh b/packages/dnb-eufemia/scripts/release/babel-es.sh
index 7f9fa8fee5f..3eb873f74b7 100755
--- a/packages/dnb-eufemia/scripts/release/babel-es.sh
+++ b/packages/dnb-eufemia/scripts/release/babel-es.sh
@@ -1,5 +1,7 @@
#!/bin/bash
+set -e # Exit immediately if a command exits with a non-zero status.
+
echo 'Building es bundle ...'
cross-env \
diff --git a/packages/dnb-eufemia/scripts/release/babel-esm.sh b/packages/dnb-eufemia/scripts/release/babel-esm.sh
index fb5edd9f2b8..252bbbb493a 100755
--- a/packages/dnb-eufemia/scripts/release/babel-esm.sh
+++ b/packages/dnb-eufemia/scripts/release/babel-esm.sh
@@ -1,5 +1,7 @@
#!/bin/bash
+set -e # Exit immediately if a command exits with a non-zero status.
+
echo 'Building esm bundle ...'
cross-env \
diff --git a/packages/dnb-eufemia/scripts/release/babel-umd.sh b/packages/dnb-eufemia/scripts/release/babel-umd.sh
new file mode 100755
index 00000000000..ac8853c7e94
--- /dev/null
+++ b/packages/dnb-eufemia/scripts/release/babel-umd.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+set -e # Exit immediately if a command exits with a non-zero status.
+
+echo 'Building umd bundles ...'
+
+cross-env \
+NODE_ENV=production \
+BABEL_ENV=umd \
+rollup -c ./rollup.config.js
+
+echo 'Building umd bundles done!'
diff --git a/packages/dnb-eufemia/scripts/release/copy-build-artifacts.sh b/packages/dnb-eufemia/scripts/release/copy-build-artifacts.sh
index 7460966ac8e..1c6257bbfda 100755
--- a/packages/dnb-eufemia/scripts/release/copy-build-artifacts.sh
+++ b/packages/dnb-eufemia/scripts/release/copy-build-artifacts.sh
@@ -1,5 +1,7 @@
#!/bin/bash
+set -e # Exit immediately if a command exits with a non-zero status.
+
echo 'Copy artifacts ...'
rm -rf build/**/{__tests__,cjs}
diff --git a/packages/dnb-eufemia/scripts/release/postbuild.sh b/packages/dnb-eufemia/scripts/release/postbuild.sh
index 6c6306f8f21..7fc70f20f5b 100755
--- a/packages/dnb-eufemia/scripts/release/postbuild.sh
+++ b/packages/dnb-eufemia/scripts/release/postbuild.sh
@@ -1,5 +1,7 @@
#!/bin/bash
+set -e # Exit immediately if a command exits with a non-zero status.
+
echo 'Postbuild started ...'
yarn build:types
@@ -11,4 +13,8 @@ echo 'Can be enabled in future if needed -> yarn build:resources'
yarn build:copy
yarn prettier:other
+echo 'Testing the postbuild ...'
+
+jest --ci --rootDir ./scripts/release ./postbuild.test.js
+
echo 'Postbuild done!'
diff --git a/packages/dnb-eufemia/scripts/release/publish-prepare.sh b/packages/dnb-eufemia/scripts/release/publish-prepare.sh
new file mode 100755
index 00000000000..70ea41fb985
--- /dev/null
+++ b/packages/dnb-eufemia/scripts/release/publish-prepare.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+set -e # Exit immediately if a command exits with a non-zero status.
+
+echo 'Prepare before publish has started ...'
+
+babel-node ./scripts/release/prepareForRelease.js
+
+echo 'Testing the postbuild before publish ...'
+
+jest --ci --rootDir ./scripts/release ./prepareForRelease.test.js
+
+echo 'Prepare before publish is done!'
diff --git a/packages/dnb-eufemia/scripts/release/publish-release.sh b/packages/dnb-eufemia/scripts/release/publish-release.sh
new file mode 100755
index 00000000000..9bb5a434ed6
--- /dev/null
+++ b/packages/dnb-eufemia/scripts/release/publish-release.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+set -e # Exit immediately if a command exits with a non-zero status.
+
+echo 'Publish release started ...'
+
+cd ./build
+
+dotenv semantic-release
+
+echo 'Publish release done!'
diff --git a/packages/dnb-eufemia/src/components/Avatar.js b/packages/dnb-eufemia/src/components/Avatar.js
new file mode 100644
index 00000000000..846c71153f4
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/Avatar.js
@@ -0,0 +1,14 @@
+/**
+ * ATTENTION: This file is auto generated by using "prepareTemplates".
+ * Do not change the content!
+ *
+ */
+
+/**
+ * Library Index avatar to autogenerate all the components and extensions
+ * Used by "prepareAvatars"
+ */
+
+import Avatar from './avatar/Avatar'
+export * from './avatar/Avatar'
+export default Avatar
diff --git a/packages/dnb-eufemia/src/components/InfoCard.js b/packages/dnb-eufemia/src/components/InfoCard.js
new file mode 100644
index 00000000000..2dd906071fa
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/InfoCard.js
@@ -0,0 +1,14 @@
+/**
+ * ATTENTION: This file is auto generated by using "prepareTemplates".
+ * Do not change the content!
+ *
+ */
+
+/**
+ * Library Index info-card to autogenerate all the components and extensions
+ * Used by "prepareInfoCards"
+ */
+
+import InfoCard from './info-card/InfoCard'
+export * from './info-card/InfoCard'
+export default InfoCard
diff --git a/packages/dnb-eufemia/src/components/Timeline.js b/packages/dnb-eufemia/src/components/Timeline.js
new file mode 100644
index 00000000000..68f9c862243
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/Timeline.js
@@ -0,0 +1,14 @@
+/**
+ * ATTENTION: This file is auto generated by using "prepareTemplates".
+ * Do not change the content!
+ *
+ */
+
+/**
+ * Library Index timeline to autogenerate all the components and extensions
+ * Used by "prepareTimelines"
+ */
+
+import Timeline from './timeline/Timeline'
+export * from './timeline/Timeline'
+export default Timeline
diff --git a/packages/dnb-eufemia/src/components/avatar/Avatar.tsx b/packages/dnb-eufemia/src/components/avatar/Avatar.tsx
new file mode 100644
index 00000000000..b95427f8f35
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/avatar/Avatar.tsx
@@ -0,0 +1,163 @@
+import React from 'react'
+import classnames from 'classnames'
+
+// Components
+import { createSpacingClasses } from '../space/SpacingHelper'
+import { createSkeletonClass } from '../skeleton/SkeletonHelper'
+
+// Elements
+import Img, { ImgProps } from '../../elements/Img'
+
+// Shared
+import Context from '../../shared/Context'
+import { ISpacingProps, SkeletonTypes } from '../../shared/interfaces'
+import {
+ extendPropsWithContext,
+ warn,
+} from '../../shared/component-helper'
+
+// Internal
+import AvatarGroup, { AvatarGroupContext } from './AvatarGroup'
+
+export type AvatarSizes = 'small' | 'medium' | 'large' | 'x-large'
+export type AvatarVariants = 'primary' | 'secondary' | 'tertiary'
+
+export interface AvatarProps {
+ /**
+ * Used in combination with `src` to provide an alt attribute for the `img` element.
+ * Default: null
+ */
+ alt?: string
+
+ /**
+ * Custom className on the component root
+ * Default: null
+ */
+ className?: string
+
+ /**
+ * Skeleton should be applied when loading content
+ * Default: null
+ */
+ skeleton?: SkeletonTypes
+
+ /**
+ * The content of the component. Can be used instead of prop "data".
+ * Default: null
+ */
+ children?: React.ReactNode
+
+ /**
+ * The size of the component.
+ * Default: medium.
+ */
+ size?: AvatarSizes
+
+ /**
+ * Specifies the path to the image
+ * Default: null
+ */
+ src?: string
+
+ /**
+ * Props applied to the `img` element if the component is used to display an image.
+ * Default: null
+ */
+ imgProps?: ImgProps
+
+ /**
+ * The variant of the component.
+ * Default: primary.
+ */
+ variant?: AvatarVariants
+}
+
+export const defaultProps = {
+ alt: null,
+ className: null,
+ size: 'medium',
+ src: null,
+ imgProps: null,
+ variant: 'primary',
+ skeleton: false,
+ children: null,
+}
+
+function Avatar(localProps: AvatarProps & ISpacingProps) {
+ // Every component should have a context
+ const context = React.useContext(Context)
+ const avatarGroupContext = React.useContext(AvatarGroupContext)
+
+ // Extract additional props from global context
+ const {
+ alt,
+ className,
+ children: childrenProp,
+ size,
+ skeleton,
+ variant,
+ src,
+ imgProps,
+ ...props
+ } = extendPropsWithContext(
+ { ...defaultProps, ...localProps },
+ defaultProps,
+ context?.Avatar,
+ avatarGroupContext
+ )
+
+ let children = null
+
+ const skeletonClasses = createSkeletonClass('shape', skeleton, context)
+ const spacingClasses = createSpacingClasses(props)
+
+ const childrenIsString = typeof childrenProp === 'string'
+
+ if (src || imgProps) {
+ const imageProps = { src, alt, ...imgProps }
+ children =
+ } else if (childrenIsString) {
+ const firstLetterUpperCase = childrenProp.charAt(0).toUpperCase()
+ children = (
+
+ {firstLetterUpperCase}
+
+ )
+ } else {
+ children = childrenProp
+ }
+
+ if (!avatarGroupContext) {
+ warn(
+ `Avatar group required: An Avatar requires an Avatar.Group with label description as a parent component. This is to ensure correct semantic and accessibility.`
+ )
+ }
+
+ return (
+
+ {childrenIsString && (
+
+ {childrenProp}
+
+ )}
+ {children}
+
+ )
+}
+
+Avatar.Group = AvatarGroup
+
+export { AvatarGroup }
+
+export default Avatar
diff --git a/packages/dnb-eufemia/src/components/avatar/AvatarGroup.tsx b/packages/dnb-eufemia/src/components/avatar/AvatarGroup.tsx
new file mode 100644
index 00000000000..2a0f657a72e
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/avatar/AvatarGroup.tsx
@@ -0,0 +1,167 @@
+import React from 'react'
+import classnames from 'classnames'
+
+// Components
+import { createSpacingClasses } from '../space/SpacingHelper'
+import { AvatarSizes, AvatarVariants } from './Avatar'
+
+// Shared
+import Context from '../../shared/Context'
+import { ISpacingProps } from '../../shared/interfaces'
+import { extendPropsWithContext } from '../../shared/component-helper'
+
+export interface AvatarGroupProps {
+ /**
+ * Label to describe the avatar group
+ * Default: null
+ */
+ label: string
+
+ /**
+ * Custom className on the component root
+ * Default: null
+ */
+ className?: string
+
+ /**
+ * Number of max displayed elements, including the "elements hidden text (+x)".
+ * Default: 4
+ */
+ maxElements?: number
+
+ /**
+ * The avatars to group.
+ * Default: null
+ */
+ children?: React.ReactNode
+
+ /**
+ * The size of the Avatars, and "elements hidden text (+x)".
+ * Default: medium.
+ */
+ size?: AvatarSizes
+
+ /**
+ * The variant of the Avatars.
+ * Default: primary.
+ */
+ variant?: AvatarVariants
+}
+
+export const defaultProps = {
+ label: null,
+ className: null,
+ maxElements: 4,
+ size: 'medium',
+ children: null,
+ variant: 'primary',
+}
+
+export const AvatarGroupContext = React.createContext(null)
+
+function AvatarGroup(localProps: AvatarGroupProps & ISpacingProps) {
+ // Every component should have a context
+ const context = React.useContext(Context)
+ // Extract additional props from global context
+ const {
+ label,
+ className,
+ children: childrenProp,
+ size,
+ maxElements: maxElementsProp,
+ variant,
+ ...props
+ } = extendPropsWithContext(
+ { ...defaultProps, ...localProps },
+ defaultProps,
+ context?.AvatarGroup
+ )
+
+ const maxElements =
+ maxElementsProp && maxElementsProp > 0 ? maxElementsProp : 4
+
+ let children = childrenProp
+ let numOfHiddenAvatars = 0
+
+ if (Array.isArray(childrenProp)) {
+ const total = childrenProp.length
+
+ if (total > maxElements) {
+ numOfHiddenAvatars = total - maxElements + 1
+ }
+
+ children = childrenProp
+ .slice(0, total - numOfHiddenAvatars)
+ .map((child, i) => {
+ const appliedSize = child.props.size ? child.props.size : size
+ const appliedVariant = child.props.variant
+ ? child.props.variant
+ : variant
+ return React.cloneElement(child, {
+ size: appliedSize,
+ variant: appliedVariant,
+ style: { ...child.props.style, zIndex: total - i },
+ key: i,
+ })
+ })
+ }
+
+ const spacingClasses = createSpacingClasses(props)
+
+ return (
+
+
+
+ {label}
+
+
+ {children}
+
+ {numOfHiddenAvatars ? (
+
+ +{numOfHiddenAvatars}
+
+ ) : null}
+
+
+ )
+}
+
+export interface ElementsHiddenProps {
+ /**
+ * The avatars to group.
+ * Default: null
+ */
+ children?: React.ReactNode
+
+ /**
+ * The size of the "elements hidden text (+x)".
+ * Default: medium.
+ */
+ size?: AvatarSizes
+}
+
+function ElementsHidden(props: ElementsHiddenProps) {
+ const { size, children } = props
+ return (
+
+ {children}
+
+ )
+}
+
+export default AvatarGroup
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/Avatar.screenshot.test.js b/packages/dnb-eufemia/src/components/avatar/__tests__/Avatar.screenshot.test.js
new file mode 100644
index 00000000000..52085ff5938
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/avatar/__tests__/Avatar.screenshot.test.js
@@ -0,0 +1,177 @@
+/**
+ * Screenshot Test
+ * This file will not run on "test:staged" because we don't require any related files
+ */
+
+import {
+ testPageScreenshot,
+ setupPageScreenshot,
+} from '../../../core/jest/jestSetupScreenshots'
+
+describe('Avatar screenshot', () => {
+ setupPageScreenshot({ url: '/uilib/components/avatar/demos' })
+
+ describe('size', () => {
+ it('have to match default size', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-size-default"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match small size', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-size-small"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match medium size', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-size-medium"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match large size', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-size-large"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match x-large size', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-size-x-large"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+ })
+
+ describe('variant', () => {
+ it('have to match default variant', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-variant-default"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match primary variant', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-variant-primary"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match secondary variant', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-variant-secondary"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match tertiary variant', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-variant-tertiary"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+ })
+
+ describe('children', () => {
+ it('have to match icon of variant primary as children', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-children-icon-primary"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match icon of variant secondary as children', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-children-icon-secondary"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match icon of variant tertiary as children', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-children-icon-tertiary"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match logo as children', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-children-logo"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+ })
+
+ describe('src', () => {
+ it('have to match png image of local src', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-image-local-png"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match svg image of local src', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-image-local-svg"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match image of external src', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-image-external"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match image when passing imgProps', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-image-props"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+ })
+
+ describe('grouping', () => {
+ it('have to match grouping of small avatars', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-grouped-small"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match grouping of medium avatars', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-grouped-medium"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match grouping of large avatars', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-grouped-large"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match grouping of x-large avatars', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-grouped-x-large"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match grouping of img avatars', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="avatar-grouped-image"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+ })
+})
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/Avatar.test.tsx b/packages/dnb-eufemia/src/components/avatar/__tests__/Avatar.test.tsx
new file mode 100644
index 00000000000..2cae5246ebb
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/avatar/__tests__/Avatar.test.tsx
@@ -0,0 +1,278 @@
+import React from 'react'
+import { render, screen, within } from '@testing-library/react'
+import Avatar from '../Avatar'
+import { confetti as Confetti } from '../../../icons'
+import Icon from '../../Icon'
+
+import {
+ loadScss,
+ axeComponent,
+ mount,
+} from '../../../core/jest/jestSetup'
+
+describe('Avatar', () => {
+ it('renders without properties', () => {
+ render(
+
+
+
+ )
+
+ expect(screen.queryByTestId('avatar')).not.toBeNull()
+ })
+
+ it('renders children as text', () => {
+ const children = 'E'
+ render(
+
+ {children}
+
+ )
+
+ expect(screen.queryByTestId('avatar-text').textContent).toBe(children)
+ })
+
+ it('renders text children by first char uppercased', () => {
+ const children = 'easy'
+ render(
+
+ {children}
+
+ )
+
+ expect(screen.queryByTestId('avatar-text').textContent).toBe('E')
+ })
+
+ it('renders a label for screen readers when passing children as text', () => {
+ const children = 'Ola Nordmann'
+ render(
+
+ {children}
+
+ )
+
+ expect(screen.queryByTestId('avatar-label').textContent).toBe(children)
+ })
+
+ it('renders children as Icon', () => {
+ render(
+
+
+
+
+
+ )
+
+ const avatar = screen.queryByTestId('avatar')
+ expect(within(avatar).queryByTestId('confetti-icon')).not.toBeNull()
+ expect(screen.queryByTestId('avatar-label')).toBeNull()
+ })
+
+ it('renders img from src', () => {
+ const img_src = '/android-chrome-192x192.png'
+ render(
+
+
+
+ )
+
+ expect(screen.queryByRole('img').getAttribute('src')).toBe(img_src)
+ expect(screen.queryByTestId('avatar-label')).toBeNull()
+ })
+
+ it('renders alt for img from src', () => {
+ const img_alt = 'custom_alt_label'
+ render(
+
+
+
+ )
+
+ expect(screen.findByAltText(img_alt)).not.toBeNull()
+ expect(screen.queryByRole('img').getAttribute('alt')).toBe(img_alt)
+ expect(screen.queryByTestId('avatar-label')).toBeNull()
+ })
+
+ it('renders imgProps', () => {
+ const img_src = '/android-chrome-192x192.png'
+ const img_width = '48'
+ const img_height = '48'
+ const img_alt = 'custom_alt_label'
+ const imgProps = {
+ width: img_width,
+ height: img_height,
+ src: img_src,
+ alt: img_alt,
+ }
+
+ render(
+
+
+
+ )
+
+ const avatar = screen.queryByTestId('avatar')
+ const image = within(avatar).queryByRole('img')
+
+ expect(image.getAttribute('src')).toBe(img_src)
+ expect(image.getAttribute('alt')).toBe(img_alt)
+ expect(image.getAttribute('width')).toBe(img_width)
+ expect(image.getAttribute('height')).toBe(img_height)
+ })
+
+ it('warns when Avatar is used without a Avatar.Group as parent component', () => {
+ process.env.NODE_ENV = 'development'
+ global.console.log = jest.fn()
+ mount( )
+ expect(global.console.log).toBeCalled()
+ })
+
+ describe('AvatarGroup', () => {
+ it('renders the label', () => {
+ const label = 'avatar'
+ render(
+
+ A
+ B
+ C
+
+ )
+ expect(screen.queryByTestId('avatar-group-label')).not.toBeNull()
+ expect(screen.queryByTestId('avatar-group-label').textContent).toBe(
+ label
+ )
+ })
+
+ it('renders the "elements left"-avatar when having more avatars than maxElements', () => {
+ render(
+
+ A
+ B
+ C
+
+ )
+
+ const avatarsDisplayed = screen.queryAllByTestId('avatar')
+ const avatarElementsLeft = screen.queryByTestId('elements-left')
+
+ expect(avatarElementsLeft).not.toBeNull()
+ expect(avatarElementsLeft.textContent).toBe('+2')
+
+ expect(avatarsDisplayed.length).toBe(1)
+ })
+
+ it('renders the "elements left"-avatar when having multiple avatars, and maxElement 1', () => {
+ render(
+
+ A
+ B
+ C
+
+ )
+
+ const avatarsDisplayed = screen.queryAllByTestId('avatar')
+ const avatarElementsLeft = screen.queryByTestId('elements-left')
+
+ expect(avatarElementsLeft).not.toBeNull()
+ expect(avatarElementsLeft.textContent).toBe('+3')
+
+ expect(avatarsDisplayed.length).toBe(0)
+ })
+
+ it('does not render "elements left"-avatar when num of avatars is the same as maxElements', () => {
+ render(
+
+ A
+ B
+ C
+
+ )
+
+ const avatarsDisplayed = screen.queryAllByTestId('avatar')
+
+ expect(screen.queryByTestId('elements-left')).toBeNull()
+ expect(avatarsDisplayed.length).toBe(3)
+ })
+
+ it('does not render "elements left"-avatar when num of avatars is less than maxElements', () => {
+ render(
+
+ A
+ B
+ C
+
+ )
+
+ const avatarsDisplayed = screen.queryAllByTestId('avatar')
+
+ expect(screen.queryByTestId('elements-left')).toBeNull()
+ expect(avatarsDisplayed.length).toBe(3)
+ })
+
+ it('does not render "elements left"-avatar when maxElements is 0', () => {
+ render(
+
+ A
+ B
+ C
+
+ )
+
+ const avatarsDisplayed = screen.queryAllByTestId('avatar')
+
+ expect(screen.queryByTestId('elements-left')).toBeNull()
+ expect(avatarsDisplayed.length).toBe(3)
+ })
+
+ it('does not render "elements left"-avatar when maxElements is not a number', () => {
+ render(
+
+ A
+ B
+
+ )
+
+ const avatarsDisplayed = screen.queryAllByTestId('avatar')
+
+ expect(screen.queryByTestId('elements-left')).toBeNull()
+ expect(avatarsDisplayed.length).toBe(2)
+ })
+
+ it('renders "elements left"-avatar when maxElements is not a number, and five or more avatars', () => {
+ render(
+
+ A
+ B
+ C
+ D
+ E
+
+ )
+
+ const avatarsDisplayed = screen.queryAllByTestId('avatar')
+
+ expect(screen.queryByTestId('elements-left')).not.toBeNull()
+ expect(avatarsDisplayed.length).toBe(3)
+ })
+ })
+})
+
+describe('Avatar aria', () => {
+ it('should validate', async () => {
+ const Component = render(E )
+ expect(await axeComponent(Component)).toHaveNoViolations()
+ })
+})
+
+describe('Avatar scss', () => {
+ it('have to match snapshot', () => {
+ const scss = loadScss(require.resolve('../style/dnb-avatar.scss'))
+ expect(scss).toMatchSnapshot()
+ })
+ it('have to match default theme snapshot', () => {
+ const scss = loadScss(
+ require.resolve('../style/themes/dnb-avatar-theme-ui.scss')
+ )
+ expect(scss).toMatchSnapshot()
+ })
+})
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/Avatar.test.tsx.snap b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/Avatar.test.tsx.snap
new file mode 100644
index 00000000000..9b252c19c10
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/Avatar.test.tsx.snap
@@ -0,0 +1,197 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Avatar scss have to match default theme snapshot 1`] = `
+"/*
+* Avatar theme
+*
+*/
+/**
+ * This file is only used to make themes independent
+ * so that they can get imported individually, without the core styles
+ *
+ */
+/*
+ * Utilities
+ */
+.dnb-avatar {
+ color: var(--color-pistachio); }
+ .dnb-avatar svg {
+ color: var(--color-pistachio);
+ fill: var(--color-pistachio); }
+ .dnb-avatar--primary {
+ background-color: var(--color-emerald-green); }
+ .dnb-avatar--secondary {
+ background-color: var(--color-sea-green); }
+ .dnb-avatar--tertiary {
+ background-color: var(--color-mint-green);
+ color: var(--color-emerald-green); }
+ .dnb-avatar--tertiary svg {
+ color: var(--color-emerald-green);
+ fill: var(--color-emerald-green); }
+"
+`;
+
+exports[`Avatar scss have to match snapshot 1`] = `
+"/*
+* DNB Avatar
+*
+*/
+/**
+ * This file is only used to make components independent
+ * so that they can get imported individually, without the core styles
+ *
+ */
+/*
+ * Utilities
+ */
+/*
+ * Scopes
+ *
+ */
+/*
+ * Document Reset
+ *
+ */
+.dnb-avatar {
+ font-family: var(--font-family-default);
+ font-weight: var(--font-weight-basis);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ line-height: var(--line-height-basis);
+ color: var(--color-black-80, #333);
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ /**
+ * Ensure consistency and use the same as HTML reset -> html {...}
+ * between base and code package
+ */
+ -moz-tab-size: 4;
+ tab-size: 4;
+ -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%;
+ word-break: break-word;
+ /**
+ * 1. Remove repeating backgrounds in all browsers (opinionated).
+ * 2. Add border box sizing in all browsers (opinionated).
+ */
+ /**
+ * 1. Add text decoration inheritance in all browsers (opinionated).
+ * 2. Add vertical alignment inheritance in all browsers (opinionated).
+ */
+ margin: 0;
+ padding: 0; }
+ .dnb-avatar *,
+ .dnb-avatar ::before,
+ .dnb-avatar ::after {
+ background-repeat: no-repeat;
+ /* 1 */
+ box-sizing: border-box;
+ /* 2 */ }
+ .dnb-avatar ::before,
+ .dnb-avatar ::after {
+ text-decoration: inherit;
+ /* 1 */
+ vertical-align: inherit;
+ /* 2 */ }
+
+/*
+* Avatar component
+*
+*/
+:root {
+ --avatar-font-size--small: var(--font-size-x-small);
+ --avatar-font-size--medium: var(--font-size-basis);
+ --avatar-font-size--large: var(--font-size-x-large);
+ --avatar-font-size--x-large: var(--font-size-x-large);
+ --avatar-line-height--small: var(--line-height-x-small);
+ --avatar-line-height--medium: var(--line-height-basis);
+ --avatar-line-height--large: var(--line-height-large);
+ --avatar-line-height--x-large: var(--line-height-large);
+ --avatar-width--small: 1.5rem;
+ --avatar-height--small: 1.5rem;
+ --avatar-width--medium: 2rem;
+ --avatar-height--medium: 2rem;
+ --avatar-width--large: 4rem;
+ --avatar-height--large: 4rem;
+ --avatar-width--x-large: 5rem;
+ --avatar-height--x-large: 5rem; }
+
+.dnb-avatar {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ line-height: 1;
+ overflow: hidden;
+ user-select: none;
+ border-radius: 50%;
+ font-weight: var(--font-weight-medium); }
+ .dnb-avatar,
+ .dnb-core-style .dnb-avatar {
+ line-height: var(--button-height); }
+ .dnb-avatar--size-small {
+ width: var(--avatar-width--small);
+ height: var(--avatar-height--small);
+ font-size: var(--avatar-font-size--small); }
+ .dnb-avatar--size-small,
+ .dnb-core-style .dnb-avatar--size-small {
+ line-height: var(--avatar-line-height--small); }
+ .dnb-avatar--size-medium {
+ width: var(--avatar-width--medium);
+ height: var(--avatar-height--medium);
+ font-size: var(--avatar-font-size--medium); }
+ .dnb-avatar--size-medium,
+ .dnb-core-style .dnb-avatar--size-medium {
+ line-height: var(--avatar-line-height--medium); }
+ .dnb-avatar--size-large {
+ width: var(--avatar-width--large);
+ height: var(--avatar-height--large);
+ font-size: var(--avatar-font-size--large); }
+ .dnb-avatar--size-large,
+ .dnb-core-style .dnb-avatar--size-large {
+ line-height: var(--avatar-line-height--large); }
+ .dnb-avatar--size-x-large {
+ width: var(--avatar-width--x-large);
+ height: var(--avatar-height--x-large);
+ font-size: var(--avatar-font-size--x-large); }
+ .dnb-avatar--size-x-large,
+ .dnb-core-style .dnb-avatar--size-x-large {
+ line-height: var(--avatar-line-height--x-large); }
+ .dnb-avatar__group {
+ display: inline-flex;
+ justify-content: flex;
+ flex-direction: row; }
+ .dnb-avatar__group .dnb-avatar {
+ box-sizing: content-box;
+ border: 0.125rem solid var(--color-black-3); }
+ .dnb-avatar__group .dnb-avatar--size-small {
+ margin-left: -0.5rem; }
+ .dnb-avatar__group .dnb-avatar--size-medium {
+ margin-left: -0.75rem; }
+ .dnb-avatar__group .dnb-avatar--size-large {
+ margin-left: -1rem; }
+ .dnb-avatar__group .dnb-avatar--size-x-large {
+ margin-left: -1.5rem; }
+ .dnb-avatar__group .dnb-avatar:nth-of-type(2) {
+ margin-left: 0; }
+ .dnb-avatar__group--elements-left {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--color-black-55); }
+ .dnb-avatar__group--elements-left--size-small {
+ font-size: var(--font-size-x-small);
+ margin-left: 0.125rem; }
+ .dnb-avatar__group--elements-left--size-medium {
+ font-size: var(--font-size-small);
+ margin-left: 0.125rem; }
+ .dnb-avatar__group--elements-left--size-large {
+ font-size: var(--font-size-large);
+ margin-left: var(--spacing-xx-small); }
+ .dnb-avatar__group--elements-left--size-x-large {
+ font-size: var(--font-size-large);
+ margin-left: var(--spacing-xx-small); }
+"
+`;
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-children-have-to-match-icon-of-variant-primary-as-children-1-8f383.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-children-have-to-match-icon-of-variant-primary-as-children-1-8f383.snap.png
new file mode 100644
index 00000000000..d166068b5cc
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-children-have-to-match-icon-of-variant-primary-as-children-1-8f383.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-children-have-to-match-icon-of-variant-secondary-as-children-1-08a3d.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-children-have-to-match-icon-of-variant-secondary-as-children-1-08a3d.snap.png
new file mode 100644
index 00000000000..7507e32a6ff
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-children-have-to-match-icon-of-variant-secondary-as-children-1-08a3d.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-children-have-to-match-icon-of-variant-tertiary-as-children-1-e7ead.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-children-have-to-match-icon-of-variant-tertiary-as-children-1-e7ead.snap.png
new file mode 100644
index 00000000000..5bd15632aeb
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-children-have-to-match-icon-of-variant-tertiary-as-children-1-e7ead.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-children-have-to-match-logo-as-children-1-46cdc.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-children-have-to-match-logo-as-children-1-46cdc.snap.png
new file mode 100644
index 00000000000..46fde7e4e62
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-children-have-to-match-logo-as-children-1-46cdc.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-img-avatars-1-34d8d.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-img-avatars-1-34d8d.snap.png
new file mode 100644
index 00000000000..32b88f96a4c
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-img-avatars-1-34d8d.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-large-avatars-1-4003c.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-large-avatars-1-4003c.snap.png
new file mode 100644
index 00000000000..bc80332c326
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-large-avatars-1-4003c.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-medium-avatars-1-c4108.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-medium-avatars-1-c4108.snap.png
new file mode 100644
index 00000000000..c0ad72f5ba4
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-medium-avatars-1-c4108.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-small-avatars-1-9a812.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-small-avatars-1-9a812.snap.png
new file mode 100644
index 00000000000..69f3bba01d3
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-small-avatars-1-9a812.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-x-large-avatars-1-df878.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-x-large-avatars-1-df878.snap.png
new file mode 100644
index 00000000000..2ae1c957d32
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-grouping-have-to-match-grouping-of-x-large-avatars-1-df878.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-default-size-1-8e7a8.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-default-size-1-8e7a8.snap.png
new file mode 100644
index 00000000000..d1573537d3e
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-default-size-1-8e7a8.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-large-size-1-e443c.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-large-size-1-e443c.snap.png
new file mode 100644
index 00000000000..c4a9a28c762
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-large-size-1-e443c.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-medium-size-1-7a038.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-medium-size-1-7a038.snap.png
new file mode 100644
index 00000000000..9820e4c9fce
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-medium-size-1-7a038.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-small-size-1-101e7.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-small-size-1-101e7.snap.png
new file mode 100644
index 00000000000..23db7a57ca6
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-small-size-1-101e7.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-x-large-size-1-4487c.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-x-large-size-1-4487c.snap.png
new file mode 100644
index 00000000000..95bd3f6b05d
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-size-have-to-match-x-large-size-1-4487c.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-src-have-to-match-image-of-external-src-1-dd3e5.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-src-have-to-match-image-of-external-src-1-dd3e5.snap.png
new file mode 100644
index 00000000000..a906948eabc
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-src-have-to-match-image-of-external-src-1-dd3e5.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-src-have-to-match-image-when-passing-img-props-1-14fa6.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-src-have-to-match-image-when-passing-img-props-1-14fa6.snap.png
new file mode 100644
index 00000000000..2a83e38a42b
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-src-have-to-match-image-when-passing-img-props-1-14fa6.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-src-have-to-match-png-image-of-local-src-1-a55c4.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-src-have-to-match-png-image-of-local-src-1-a55c4.snap.png
new file mode 100644
index 00000000000..ea095546d48
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-src-have-to-match-png-image-of-local-src-1-a55c4.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-src-have-to-match-svg-image-of-local-src-1-44759.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-src-have-to-match-svg-image-of-local-src-1-44759.snap.png
new file mode 100644
index 00000000000..c0d50805699
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-src-have-to-match-svg-image-of-local-src-1-44759.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-variant-have-to-match-default-variant-1-22bd7.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-variant-have-to-match-default-variant-1-22bd7.snap.png
new file mode 100644
index 00000000000..bb2068f67f2
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-variant-have-to-match-default-variant-1-22bd7.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-variant-have-to-match-primary-variant-1-e7e35.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-variant-have-to-match-primary-variant-1-e7e35.snap.png
new file mode 100644
index 00000000000..d1573537d3e
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-variant-have-to-match-primary-variant-1-e7e35.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-variant-have-to-match-secondary-variant-1-d6086.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-variant-have-to-match-secondary-variant-1-d6086.snap.png
new file mode 100644
index 00000000000..42808cb0198
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-variant-have-to-match-secondary-variant-1-d6086.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-variant-have-to-match-tertiary-variant-1-5b058.snap.png b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-variant-have-to-match-tertiary-variant-1-5b058.snap.png
new file mode 100644
index 00000000000..5f07d6fb2e8
Binary files /dev/null and b/packages/dnb-eufemia/src/components/avatar/__tests__/__snapshots__/avatar-screenshot-test-js-avatar-screenshot-variant-have-to-match-tertiary-variant-1-5b058.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/avatar/index.js b/packages/dnb-eufemia/src/components/avatar/index.js
new file mode 100644
index 00000000000..e2a31822249
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/avatar/index.js
@@ -0,0 +1,8 @@
+/**
+ * Component Entry
+ *
+ */
+
+import Avatar from './Avatar'
+export default Avatar
+export * from './Avatar'
diff --git a/packages/dnb-eufemia/src/components/avatar/style.js b/packages/dnb-eufemia/src/components/avatar/style.js
new file mode 100644
index 00000000000..e8efd23ae13
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/avatar/style.js
@@ -0,0 +1,6 @@
+/**
+ * Web Style Import
+ *
+ */
+
+import './style/dnb-avatar.scss'
diff --git a/packages/dnb-eufemia/src/components/avatar/style/_avatar.scss b/packages/dnb-eufemia/src/components/avatar/style/_avatar.scss
new file mode 100644
index 00000000000..6ce449ada92
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/avatar/style/_avatar.scss
@@ -0,0 +1,145 @@
+/*
+* Avatar component
+*
+*/
+
+:root {
+ --avatar-font-size--small: var(--font-size-x-small);
+ --avatar-font-size--medium: var(--font-size-basis);
+ --avatar-font-size--large: var(--font-size-x-large);
+ --avatar-font-size--x-large: var(--font-size-x-large);
+ --avatar-line-height--small: var(--line-height-x-small);
+ --avatar-line-height--medium: var(--line-height-basis);
+ --avatar-line-height--large: var(--line-height-large);
+ --avatar-line-height--x-large: var(--line-height-large);
+ --avatar-width--small: 1.5rem;
+ --avatar-height--small: 1.5rem;
+ --avatar-width--medium: 2rem;
+ --avatar-height--medium: 2rem;
+ --avatar-width--large: 4rem;
+ --avatar-height--large: 4rem;
+ --avatar-width--x-large: 5rem;
+ --avatar-height--x-large: 5rem;
+}
+
+.dnb-avatar {
+ position: relative;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ line-height: 1;
+ overflow: hidden;
+ user-select: none;
+
+ border-radius: 50%;
+ font-weight: var(--font-weight-medium);
+
+ // Safari needs a correct CSS specificity
+ &,
+ .dnb-core-style & {
+ line-height: var(--button-height);
+ }
+
+ &--size-small {
+ width: var(--avatar-width--small);
+ height: var(--avatar-height--small);
+ font-size: var(--avatar-font-size--small);
+ // Safari needs a correct CSS specificity
+ &,
+ .dnb-core-style & {
+ line-height: var(--avatar-line-height--small);
+ }
+ }
+
+ &--size-medium {
+ width: var(--avatar-width--medium);
+ height: var(--avatar-height--medium);
+ font-size: var(--avatar-font-size--medium);
+ // Safari needs a correct CSS specificity
+ &,
+ .dnb-core-style & {
+ line-height: var(--avatar-line-height--medium);
+ }
+ }
+
+ &--size-large {
+ width: var(--avatar-width--large);
+ height: var(--avatar-height--large);
+ font-size: var(--avatar-font-size--large);
+ // Safari needs a correct CSS specificity
+ &,
+ .dnb-core-style & {
+ line-height: var(--avatar-line-height--large);
+ }
+ }
+
+ &--size-x-large {
+ width: var(--avatar-width--x-large);
+ height: var(--avatar-height--x-large);
+ font-size: var(--avatar-font-size--x-large);
+ // Safari needs a correct CSS specificity
+ &,
+ .dnb-core-style & {
+ line-height: var(--avatar-line-height--x-large);
+ }
+ }
+
+ &__group {
+ display: inline-flex;
+ justify-content: flex;
+ flex-direction: row;
+
+ .dnb-avatar {
+ box-sizing: content-box;
+ border: 0.125rem solid var(--color-black-3);
+
+ &--size-small {
+ margin-left: -0.5rem;
+ }
+
+ &--size-medium {
+ margin-left: -0.75rem;
+ }
+
+ &--size-large {
+ margin-left: -1rem;
+ }
+
+ &--size-x-large {
+ margin-left: -1.5rem;
+ }
+
+ &:nth-of-type(2) {
+ margin-left: 0;
+ }
+ }
+
+ &--elements-left {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ color: var(--color-black-55);
+
+ &--size-small {
+ font-size: var(--font-size-x-small);
+ margin-left: 0.125rem;
+ }
+
+ &--size-medium {
+ font-size: var(--font-size-small);
+ margin-left: 0.125rem;
+ }
+
+ &--size-large {
+ font-size: var(--font-size-large);
+ margin-left: var(--spacing-xx-small);
+ }
+
+ &--size-x-large {
+ font-size: var(--font-size-large);
+ margin-left: var(--spacing-xx-small);
+ }
+ }
+ }
+}
diff --git a/packages/dnb-eufemia/src/components/avatar/style/dnb-avatar.scss b/packages/dnb-eufemia/src/components/avatar/style/dnb-avatar.scss
new file mode 100644
index 00000000000..55a8ba79067
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/avatar/style/dnb-avatar.scss
@@ -0,0 +1,12 @@
+/*
+* DNB Avatar
+*
+*/
+
+@import '../../../style/components/imports.scss';
+
+.dnb-avatar {
+ @include componentReset();
+}
+
+@import './_avatar.scss';
diff --git a/packages/dnb-eufemia/src/components/avatar/style/index.d.ts b/packages/dnb-eufemia/src/components/avatar/style/index.d.ts
new file mode 100644
index 00000000000..e400b4a3433
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/avatar/style/index.d.ts
@@ -0,0 +1,6 @@
+/**
+ * Web Style Import
+ *
+ */
+
+import './dnb-avatar.scss'
diff --git a/packages/dnb-eufemia/src/components/avatar/style/index.js b/packages/dnb-eufemia/src/components/avatar/style/index.js
new file mode 100644
index 00000000000..e400b4a3433
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/avatar/style/index.js
@@ -0,0 +1,6 @@
+/**
+ * Web Style Import
+ *
+ */
+
+import './dnb-avatar.scss'
diff --git a/packages/dnb-eufemia/src/components/avatar/style/themes/dnb-avatar-theme-ui.scss b/packages/dnb-eufemia/src/components/avatar/style/themes/dnb-avatar-theme-ui.scss
new file mode 100644
index 00000000000..4f3a9f3290b
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/avatar/style/themes/dnb-avatar-theme-ui.scss
@@ -0,0 +1,29 @@
+/*
+* Avatar theme
+*
+*/
+
+@import '../../../../style/themes/imports.scss';
+
+.dnb-avatar {
+ color: var(--color-pistachio);
+
+ svg {
+ color: var(--color-pistachio);
+ fill: var(--color-pistachio);
+ }
+ &--primary {
+ background-color: var(--color-emerald-green);
+ }
+ &--secondary {
+ background-color: var(--color-sea-green);
+ }
+ &--tertiary {
+ background-color: var(--color-mint-green);
+ color: var(--color-emerald-green);
+ svg {
+ color: var(--color-emerald-green);
+ fill: var(--color-emerald-green);
+ }
+ }
+}
diff --git a/packages/dnb-eufemia/src/components/avatar/style/themes/ui.js b/packages/dnb-eufemia/src/components/avatar/style/themes/ui.js
new file mode 100644
index 00000000000..2528055918c
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/avatar/style/themes/ui.js
@@ -0,0 +1,6 @@
+/**
+ * Imports the default theme
+ *
+ */
+
+import './dnb-avatar-theme-ui.scss'
diff --git a/packages/dnb-eufemia/src/components/breadcrumb/BreadcrumbItem.tsx b/packages/dnb-eufemia/src/components/breadcrumb/BreadcrumbItem.tsx
index 053538e5841..568b57f6427 100644
--- a/packages/dnb-eufemia/src/components/breadcrumb/BreadcrumbItem.tsx
+++ b/packages/dnb-eufemia/src/components/breadcrumb/BreadcrumbItem.tsx
@@ -88,7 +88,7 @@ export default function BreadcrumbItem(localProps: BreadcrumbItemProps) {
{isInteractive ? (
) : (
-
+
{
).toBe('Page 2')
})
- it('current will have aria-current="true', () => {
+ it('current item will have aria-current="page', () => {
render(
{
)
const lastElem = screen.getAllByTestId('breadcrumb-item').slice(-1)[0]
- expect(lastElem.getAttribute('aria-current')).toBe('true')
+ expect(lastElem.getAttribute('aria-current')).toBe('page')
})
it('variant collapse opens the collapsed content on click', () => {
diff --git a/packages/dnb-eufemia/src/components/button/Button.js b/packages/dnb-eufemia/src/components/button/Button.js
index 94ec459c8cd..a547e586356 100644
--- a/packages/dnb-eufemia/src/components/button/Button.js
+++ b/packages/dnb-eufemia/src/components/button/Button.js
@@ -33,7 +33,17 @@ import Anchor from '../../elements/Anchor'
import Tooltip from '../tooltip/Tooltip'
export const buttonVariantPropType = {
- variant: PropTypes.oneOf(['primary', 'secondary', 'tertiary', 'signal']),
+ variant: PropTypes.oneOf([
+ 'primary',
+ 'secondary',
+ 'tertiary',
+ 'signal',
+
+ /**
+ * For internal use only (as of now)
+ */
+ 'unstyled',
+ ]),
}
/**
diff --git a/packages/dnb-eufemia/src/components/button/__tests__/Button.screenshot.test.js b/packages/dnb-eufemia/src/components/button/__tests__/Button.screenshot.test.js
index 8a3a7fa677d..f1f95b87bca 100644
--- a/packages/dnb-eufemia/src/components/button/__tests__/Button.screenshot.test.js
+++ b/packages/dnb-eufemia/src/components/button/__tests__/Button.screenshot.test.js
@@ -148,6 +148,17 @@ describe('Button target blank', () => {
})
})
+describe('Button unstyled screenshot', () => {
+ setupPageScreenshot({ url: '/uilib/components/button/demos' })
+
+ it('have to match "dnb-button--unstyled" with icon', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="button-unstyled"]',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+})
+
describe('Button tertiary screenshot', () => {
setupPageScreenshot({ url: '/uilib/components/button/demos' })
diff --git a/packages/dnb-eufemia/src/components/button/__tests__/Button.test.js b/packages/dnb-eufemia/src/components/button/__tests__/Button.test.js
index d41bbcf47a4..5b92ea26b14 100644
--- a/packages/dnb-eufemia/src/components/button/__tests__/Button.test.js
+++ b/packages/dnb-eufemia/src/components/button/__tests__/Button.test.js
@@ -176,6 +176,11 @@ describe('Button component', () => {
expect(Comp.find('.dnb-button--tertiary').exists()).toBe(true)
})
+ it('has variant unstyled', () => {
+ const Comp = mount( )
+ expect(Comp.find('.dnb-button--unstyled').exists()).toBe(true)
+ })
+
it('will replace icon with icon component', () => {
const Comp = mount(
>
)}
diff --git a/packages/dnb-eufemia/src/components/date-picker/DatePickerFooter.js b/packages/dnb-eufemia/src/components/date-picker/DatePickerFooter.js
index c6ff70f10cd..9f4cf9032e1 100644
--- a/packages/dnb-eufemia/src/components/date-picker/DatePickerFooter.js
+++ b/packages/dnb-eufemia/src/components/date-picker/DatePickerFooter.js
@@ -19,12 +19,15 @@ export default class DatePickerFooter extends React.PureComponent {
onSubmit: PropTypes.func,
onCancel: PropTypes.func,
onReset: PropTypes.func,
+
+ resetButtonText: PropTypes.string,
}
static defaultProps = {
onSubmit: null,
onCancel: null,
onReset: null,
+ resetButtonText: null,
}
onSubmitHandler = (args) => {
@@ -87,7 +90,7 @@ export default class DatePickerFooter extends React.PureComponent {
}
render() {
- const { isRange } = this.props
+ const { isRange, resetButtonText } = this.props
const { show_reset_button, show_cancel_button, show_submit_button } =
this.context.props
@@ -95,13 +98,17 @@ export default class DatePickerFooter extends React.PureComponent {
if (
!isRange &&
!isTrue(show_submit_button) &&
- !isTrue(show_cancel_button)
+ !isTrue(show_cancel_button) &&
+ !isTrue(show_reset_button)
) {
return <>>
}
- const { submit_button_text, cancel_button_text, reset_button_text } =
- this.context.translation.DatePicker
+ const {
+ submit_button_text,
+ cancel_button_text,
+ reset_button_text: reset_button_text_translation,
+ } = this.context.translation.DatePicker
return (
@@ -116,7 +123,7 @@ export default class DatePickerFooter extends React.PureComponent {
{(isTrue(show_reset_button) && (
{
const resetElem = Comp.find('button[data-visual-test="reset"]')
expect(resetElem.exists()).toBe(true)
+ expect(resetElem.text()).toMatch('Tilbakestill')
const cancelElem = Comp.find('button[data-visual-test="cancel"]')
expect(cancelElem.exists()).toBe(true)
+ expect(cancelElem.text()).toMatch('Avbryt')
const submitElem = Comp.find('button[data-visual-test="submit"]')
expect(submitElem.exists()).toBe(true)
+ expect(submitElem.text()).toMatch('Ok')
expect(
Comp.find('input.dnb-date-picker__input--year').instance().value
@@ -353,7 +356,72 @@ describe('DatePicker component', () => {
expect(on_submit.mock.calls[0][0].date).toBe(date)
})
- it('has a warking month correction', () => {
+ it('footers reset button text is set by prop reset_button_text', () => {
+ const reset_button_text = 'custom reset button text'
+
+ const Comp = mount(
+
+ )
+
+ const resetElem = Comp.find('button[data-visual-test="reset"]')
+ expect(resetElem.exists()).toBe(true)
+ expect(resetElem.text()).toMatch(reset_button_text)
+ })
+
+ it('footer is rendered when show_reset_button is provided', () => {
+ const Comp = mount( )
+
+ const datePickerFooter = Comp.find('.dnb-date-picker__footer')
+ expect(datePickerFooter.exists()).toBe(true)
+ })
+
+ it('footer is rendered when show_cancel_button is provided', () => {
+ const Comp = mount(
+
+ )
+
+ const datePickerFooter = Comp.find('.dnb-date-picker__footer')
+ expect(datePickerFooter.exists()).toBe(true)
+ })
+
+ it('footer is rendered when show_submit_button is provided', () => {
+ const Comp = mount(
+
+ )
+
+ const datePickerFooter = Comp.find('.dnb-date-picker__footer')
+ expect(datePickerFooter.exists()).toBe(true)
+ })
+
+ it('footer is rendered when range is provided', () => {
+ const Comp = mount( )
+
+ const datePickerFooter = Comp.find('.dnb-date-picker__footer')
+ expect(datePickerFooter.exists()).toBe(true)
+ })
+
+ it('footer is not rendered', () => {
+ const Comp = mount(
+
+ )
+
+ const datePickerFooter = Comp.find('.dnb-date-picker__footer')
+ expect(datePickerFooter.exists()).toBe(false)
+ })
+
+ it('has a working month correction', () => {
const Comp = mount( )
const dayElem = Comp.find('input.dnb-date-picker__input--day').at(0)
diff --git a/packages/dnb-eufemia/src/components/index.js b/packages/dnb-eufemia/src/components/index.js
index e08742e2d93..c9d8f5862b0 100644
--- a/packages/dnb-eufemia/src/components/index.js
+++ b/packages/dnb-eufemia/src/components/index.js
@@ -12,6 +12,7 @@
// import all the available components
import Accordion from './accordion/Accordion'
import Autocomplete from './autocomplete/Autocomplete'
+import Avatar from './avatar/Avatar'
import Breadcrumb from './breadcrumb/Breadcrumb'
import Button from './button/Button'
import Checkbox from './checkbox/Checkbox'
@@ -27,6 +28,7 @@ import Heading from './heading/Heading'
import HelpButton from './help-button/HelpButton'
import Icon from './icon/Icon'
import IconPrimary from './icon-primary/IconPrimary'
+import InfoCard from './info-card/InfoCard'
import Input from './input/Input'
import InputMasked from './input-masked/InputMasked'
import Logo from './logo/Logo'
@@ -44,6 +46,7 @@ import Switch from './switch/Switch'
import Tabs from './tabs/Tabs'
import Tag from './tag/Tag'
import Textarea from './textarea/Textarea'
+import Timeline from './timeline/Timeline'
import ToggleButton from './toggle-button/ToggleButton'
import Tooltip from './tooltip/Tooltip'
@@ -51,6 +54,7 @@ import Tooltip from './tooltip/Tooltip'
export {
Accordion,
Autocomplete,
+ Avatar,
Breadcrumb,
Button,
Checkbox,
@@ -66,6 +70,7 @@ export {
HelpButton,
Icon,
IconPrimary,
+ InfoCard,
Input,
InputMasked,
Logo,
@@ -83,6 +88,7 @@ export {
Tabs,
Tag,
Textarea,
+ Timeline,
ToggleButton,
Tooltip,
}
diff --git a/packages/dnb-eufemia/src/components/info-card/InfoCard.tsx b/packages/dnb-eufemia/src/components/info-card/InfoCard.tsx
new file mode 100644
index 00000000000..5adfce8ce55
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/info-card/InfoCard.tsx
@@ -0,0 +1,249 @@
+import React from 'react'
+import classnames from 'classnames'
+
+// Components
+import Button, { ButtonProps } from '../button/Button'
+import IconPrimary, { IconPrimaryIcon } from '../icon-primary/IconPrimary'
+import Img, { ImgProps } from '../../elements/Img'
+import Div from '../../elements/Div'
+import H3 from '../../elements/H3'
+import P from '../../elements/P'
+
+// Icons
+import { lightbulb_medium as LightbulbIcon } from '../../icons'
+
+// Shared
+import { createSpacingClasses } from '../space/SpacingHelper'
+import { createSkeletonClass } from '../skeleton/SkeletonHelper'
+import Context from '../../shared/Context'
+import { ISpacingProps, SkeletonTypes } from '../../shared/interfaces'
+import { extendPropsWithContext } from '../../shared/component-helper'
+
+export interface InfoCardProps {
+ /**
+ * Used in combination with `src` to provide an alt attribute for the `img` element.
+ * Default: null
+ */
+ alt?: string
+ /**
+ * Aligns the content to center, rather than left
+ * Default: false
+ */
+ centered?: boolean
+ /**
+ * Custom className on the component root
+ * Default: null
+ */
+ className?: string
+ /**
+ * Replace the default icon with custom icon.
+ * Default: Lightbulb (icon)
+ */
+ icon?: IconPrimaryIcon
+ /**
+ * Props applied to the `img` element if the component is used to display an image. Replace the 'icon'
+ * Default: null
+ */
+ imgProps?: ImgProps
+ /**
+ * Skeleton should be applied when loading content
+ * Default: null
+ */
+ skeleton?: SkeletonTypes
+ /**
+ * Specifies the path to the image
+ * Default: null
+ */
+ src?: string
+ /**
+ * Image src, will replace the 'icon' with the image
+ * Default: null
+ */
+ text: string
+ /**
+ * Component title
+ * Default: null
+ */
+ title?: string
+ /**
+ * Is called when the close button is clicked
+ * Default: null
+ */
+ onClose?: React.MouseEventHandler
+ /**
+ * The text of the close button.
+ * Default: null
+ */
+ closeButtonText?: string
+ /**
+ * Is called when the accept button is clicked
+ * Default: null
+ */
+ onAccept?: React.MouseEventHandler
+ /**
+ * The text of the accept button.
+ * Default: null
+ */
+ acceptButtonText?: string
+ /**
+ * Additional attributes for the close button.
+ * Default: null
+ */
+ closeButtonAttributes?: ButtonProps
+ /**
+ * Additional attributes for the accept button
+ * Default: null
+ */
+ acceptButtonAttributes?: ButtonProps
+}
+
+export const defaultProps = {
+ alt: null,
+ centered: false,
+ className: null,
+ skeleton: false,
+ icon: LightbulbIcon,
+ imgProps: null,
+ src: null,
+ title: null,
+ onAccept: null,
+ onClose: null,
+ closeButtonText: null,
+ acceptButtonText: null,
+ closeButtonAttributes: null,
+ acceptButtonAttributes: null,
+}
+
+function InfoCard(localProps: InfoCardProps & ISpacingProps) {
+ // Every component should have a context
+ const context = React.useContext(Context)
+ // Extract additional props from global context
+ const {
+ alt,
+ centered,
+ title,
+ skeleton,
+ className,
+ icon,
+ src,
+ imgProps,
+ text,
+ onClose,
+ onAccept,
+ closeButtonText,
+ acceptButtonText,
+ closeButtonAttributes,
+ acceptButtonAttributes,
+ ...props
+ } = extendPropsWithContext({
+ ...defaultProps,
+ ...localProps,
+ })
+
+ const skeletonClasses = createSkeletonClass('shape', skeleton, context)
+ const spacingClasses = createSpacingClasses(props)
+
+ const closeButtonIsHidden = !onClose && !closeButtonText
+ const acceptButtonIsHidden = !onAccept && !acceptButtonText
+
+ return (
+
+
+ {getIllustration()}
+
+
+
+ {title && (
+
+ {title}
+
+ )}
+
+ {text}
+
+
+ {getButtons()}
+
+
+ )
+
+ function getButtons() {
+ if (closeButtonIsHidden && acceptButtonIsHidden) return null
+
+ return (
+
+ {!acceptButtonIsHidden && (
+
+ )}
+ {!closeButtonIsHidden && (
+
+ )}
+
+ )
+ }
+
+ function getIllustration() {
+ if (src || imgProps) {
+ const imageProps = { src, alt, ...imgProps }
+ return (
+
+ )
+ }
+ return (
+
+ )
+ }
+}
+
+export default InfoCard
diff --git a/packages/dnb-eufemia/src/components/info-card/__tests__/InfoCard.screenshot.test.js b/packages/dnb-eufemia/src/components/info-card/__tests__/InfoCard.screenshot.test.js
new file mode 100644
index 00000000000..211ff569e8d
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/info-card/__tests__/InfoCard.screenshot.test.js
@@ -0,0 +1,40 @@
+/**
+ * Screenshot Test
+ * This file will not run on "test:staged" because we don't require any related files
+ */
+
+ import {
+ testPageScreenshot,
+ setupPageScreenshot,
+} from '../../../core/jest/jestSetupScreenshots'
+
+describe('InfoCard screenshot', () => {
+ setupPageScreenshot({ url: '/uilib/components/info-card/demos' })
+
+ it('renders correct default component', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="info-card-basic"] .dnb-info-card',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+ it('renders correct component with title and buttons', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="info-card-buttons"] .dnb-info-card',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+ it('renders the centered component', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="info-card-buttons-centered"] .dnb-info-card',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+ it('renders with custom icon', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="info-card-custom-icon"] .dnb-info-card',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+
+})
diff --git a/packages/dnb-eufemia/src/components/info-card/__tests__/InfoCard.test.tsx b/packages/dnb-eufemia/src/components/info-card/__tests__/InfoCard.test.tsx
new file mode 100644
index 00000000000..a0f11fa202d
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/info-card/__tests__/InfoCard.test.tsx
@@ -0,0 +1,180 @@
+import React from 'react'
+import { fireEvent, render, screen, within } from '@testing-library/react'
+import InfoCard from '../InfoCard'
+import { confetti as Confetti } from '../../../icons'
+
+import { loadScss, axeComponent } from '../../../core/jest/jestSetup'
+
+describe('InfoCard', () => {
+ it('renders with no props', () => {
+ render( )
+
+ expect(screen.queryByTestId('info-card')).not.toBeNull()
+ })
+
+ it('renders the title', () => {
+ const title = 'my title'
+
+ render( )
+
+ expect(screen.queryByTestId('info-card-title')).not.toBeNull()
+ expect(screen.queryByTestId('info-card-title').textContent).toMatch(
+ title
+ )
+ })
+
+ it('renders the text', () => {
+ const text = 'my-text'
+
+ render( )
+
+ expect(screen.queryByTestId('info-card-text')).not.toBeNull()
+ expect(screen.queryByTestId('info-card-text').textContent).toMatch(
+ text
+ )
+ })
+
+ it('renders the icon', () => {
+ const icon =
+
+ render( )
+
+ const iconContainer = screen.queryByTestId('info-card-icon')
+
+ expect(iconContainer).not.toBeNull()
+ expect(
+ within(iconContainer).queryByTestId('custom-icon')
+ ).not.toBeNull()
+ })
+
+ it('renders the image', () => {
+ const img_src = '/android-chrome-192x192.png'
+
+ render(
+
+ )
+
+ expect(screen.queryByRole('img').getAttribute('src')).toBe(img_src)
+ })
+
+ it('renders imgProps', () => {
+ const img_src = '/android-chrome-192x192.png'
+ const img_width = '16'
+ const img_height = '16'
+ const img_alt = 'custom_alt_label'
+ const imgProps = {
+ width: img_width,
+ height: img_height,
+ src: img_src,
+ alt: img_alt,
+ }
+
+ render( )
+
+ const infoCard = screen.queryByTestId('info-card')
+ const image = within(infoCard).queryByRole('img')
+
+ expect(image.getAttribute('src')).toBe(img_src)
+ expect(image.getAttribute('alt')).toBe(img_alt)
+ expect(image.getAttribute('width')).toBe(img_width)
+ expect(image.getAttribute('height')).toBe(img_height)
+ })
+
+ it('does not render the buttons', () => {
+ render( )
+
+ expect(screen.queryByTestId('into-card-accept-button')).toBeNull()
+ expect(screen.queryByTestId('into-card-close-button')).toBeNull()
+ })
+
+ it('renders the accept button when on_accept is provided', () => {
+ const onAccept = jest.fn()
+ render( )
+
+ const buttonElement = screen.queryByTestId('into-card-accept-button')
+
+ expect(buttonElement).not.toBeNull()
+
+ fireEvent.click(buttonElement)
+
+ expect(onAccept).toHaveBeenCalled()
+ })
+
+ it('renders the accept button text', () => {
+ const acceptButtonText = 'some text'
+ render( )
+
+ const buttonElement = screen.queryByTestId('into-card-accept-button')
+
+ expect(buttonElement).not.toBeNull()
+ expect(buttonElement.textContent).toMatch(acceptButtonText)
+ })
+
+ it('renders the close button when on_close is provided', () => {
+ const onClose = jest.fn()
+ render( )
+
+ const buttonElement = screen.queryByTestId('into-card-close-button')
+
+ expect(buttonElement).not.toBeNull()
+
+ fireEvent.click(buttonElement)
+
+ expect(onClose).toHaveBeenCalled()
+ })
+
+ it('renders the close button text', () => {
+ const closeButtonText = 'some text'
+ render( )
+
+ const buttonElement = screen.queryByTestId('into-card-close-button')
+
+ expect(buttonElement).not.toBeNull()
+ expect(buttonElement.textContent).toMatch(closeButtonText)
+ })
+
+ it('renders the accept button with additional props', () => {
+ const href = 'href'
+
+ render(
+
+ )
+
+ const buttonElement = screen.queryByTestId('into-card-accept-button')
+
+ expect(buttonElement.getAttribute('href')).toMatch(href)
+ })
+ it('renders the close button with additional props', () => {
+ const href = 'href'
+
+ render(
+
+ )
+
+ const buttonElement = screen.queryByTestId('into-card-close-button')
+
+ expect(buttonElement.getAttribute('href')).toMatch(href)
+ })
+
+ describe('InfoCard aria', () => {
+ it('should validate', async () => {
+ const Component = render( )
+ expect(await axeComponent(Component)).toHaveNoViolations()
+ })
+ })
+
+ describe('InfoCard scss', () => {
+ it('have to match snapshot', () => {
+ const scss = loadScss(require.resolve('../style/dnb-info-card.scss'))
+ expect(scss).toMatchSnapshot()
+ })
+ })
+})
diff --git a/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/InfoCard.test.tsx.snap b/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/InfoCard.test.tsx.snap
new file mode 100644
index 00000000000..31330e185f5
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/InfoCard.test.tsx.snap
@@ -0,0 +1,102 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`InfoCard InfoCard scss have to match snapshot 1`] = `
+"/*
+* DNB InfoCard
+*
+*/
+/**
+ * This file is only used to make components independent
+ * so that they can get imported individually, without the core styles
+ *
+ */
+/*
+ * Utilities
+ */
+/*
+ * Scopes
+ *
+ */
+/*
+ * Document Reset
+ *
+ */
+.dnb-info-card {
+ font-family: var(--font-family-default);
+ font-weight: var(--font-weight-basis);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ line-height: var(--line-height-basis);
+ color: var(--color-black-80, #333);
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ /**
+ * Ensure consistency and use the same as HTML reset -> html {...}
+ * between base and code package
+ */
+ -moz-tab-size: 4;
+ tab-size: 4;
+ -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%;
+ word-break: break-word;
+ /**
+ * 1. Remove repeating backgrounds in all browsers (opinionated).
+ * 2. Add border box sizing in all browsers (opinionated).
+ */
+ /**
+ * 1. Add text decoration inheritance in all browsers (opinionated).
+ * 2. Add vertical alignment inheritance in all browsers (opinionated).
+ */
+ margin: 0;
+ padding: 0; }
+ .dnb-info-card *,
+ .dnb-info-card ::before,
+ .dnb-info-card ::after {
+ background-repeat: no-repeat;
+ /* 1 */
+ box-sizing: border-box;
+ /* 2 */ }
+ .dnb-info-card ::before,
+ .dnb-info-card ::after {
+ text-decoration: inherit;
+ /* 1 */
+ vertical-align: inherit;
+ /* 2 */ }
+
+/*
+* InfoCard component
+*
+*/
+.dnb-info-card {
+ display: flex;
+ flex-direction: row;
+ background: white;
+ border: 1px solid var(--color-black-8);
+ border-radius: 0.25rem;
+ padding: 1rem;
+ box-shadow: var(--shadow-default); }
+ .dnb-info-card--centered {
+ flex-direction: column;
+ align-items: center;
+ text-align: center; }
+ .dnb-info-card--content {
+ flex-direction: column;
+ margin-top: 0.1875rem; }
+ .dnb-info-card--centered .dnb-info-card--content {
+ flex-direction: column;
+ margin-top: 0; }
+ .dnb-info-card--icon {
+ color: var(--color-emerald-green); }
+ .dnb-info-card--image {
+ height: 4rem;
+ width: 4rem; }
+ .dnb-info-card--buttons {
+ display: flex;
+ flex-direction: row; }
+ .dnb-info-card--buttons-centered {
+ flex-direction: column;
+ align-items: center;
+ text-align: center; }
+"
+`;
diff --git a/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/info-card-screenshot-test-js-info-card-screenshot-renders-correct-component-with-title-and-buttons-1-8a82a.snap.png b/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/info-card-screenshot-test-js-info-card-screenshot-renders-correct-component-with-title-and-buttons-1-8a82a.snap.png
new file mode 100644
index 00000000000..eccde0d5a3b
Binary files /dev/null and b/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/info-card-screenshot-test-js-info-card-screenshot-renders-correct-component-with-title-and-buttons-1-8a82a.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/info-card-screenshot-test-js-info-card-screenshot-renders-correct-default-component-1-7706e.snap.png b/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/info-card-screenshot-test-js-info-card-screenshot-renders-correct-default-component-1-7706e.snap.png
new file mode 100644
index 00000000000..91335009c9b
Binary files /dev/null and b/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/info-card-screenshot-test-js-info-card-screenshot-renders-correct-default-component-1-7706e.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/info-card-screenshot-test-js-info-card-screenshot-renders-the-centered-component-1-3c565.snap.png b/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/info-card-screenshot-test-js-info-card-screenshot-renders-the-centered-component-1-3c565.snap.png
new file mode 100644
index 00000000000..ee42e363425
Binary files /dev/null and b/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/info-card-screenshot-test-js-info-card-screenshot-renders-the-centered-component-1-3c565.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/info-card-screenshot-test-js-info-card-screenshot-renders-with-custom-icon-1-54299.snap.png b/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/info-card-screenshot-test-js-info-card-screenshot-renders-with-custom-icon-1-54299.snap.png
new file mode 100644
index 00000000000..35cf386831f
Binary files /dev/null and b/packages/dnb-eufemia/src/components/info-card/__tests__/__snapshots__/info-card-screenshot-test-js-info-card-screenshot-renders-with-custom-icon-1-54299.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/info-card/index.d.ts b/packages/dnb-eufemia/src/components/info-card/index.d.ts
new file mode 100644
index 00000000000..d2ba7ba29c4
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/info-card/index.d.ts
@@ -0,0 +1,8 @@
+/**
+ * Component Entry
+ *
+ */
+
+import InfoCard from './InfoCard';
+export default InfoCard;
+export * from './InfoCard';
diff --git a/packages/dnb-eufemia/src/components/info-card/index.js b/packages/dnb-eufemia/src/components/info-card/index.js
new file mode 100644
index 00000000000..1bf51e14341
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/info-card/index.js
@@ -0,0 +1,9 @@
+/**
+ * Component Entry
+ *
+ */
+
+ import InfoCard from './InfoCard';
+ export default InfoCard;
+ export * from './InfoCard';
+
\ No newline at end of file
diff --git a/packages/dnb-eufemia/src/components/info-card/style.js b/packages/dnb-eufemia/src/components/info-card/style.js
new file mode 100644
index 00000000000..52bd16bdf8c
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/info-card/style.js
@@ -0,0 +1,6 @@
+/**
+ * Web Style Import
+ *
+ */
+
+ import './style/dnb-info-card.scss'
diff --git a/packages/dnb-eufemia/src/components/info-card/style/_info-card.scss b/packages/dnb-eufemia/src/components/info-card/style/_info-card.scss
new file mode 100644
index 00000000000..7639e4b08df
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/info-card/style/_info-card.scss
@@ -0,0 +1,52 @@
+/*
+* InfoCard component
+*
+*/
+
+.dnb-info-card {
+ display: flex;
+ flex-direction: row;
+
+ background: white;
+
+ border: 1px solid var(--color-black-8);
+ border-radius: 0.25rem;
+ padding: 1rem;
+
+ @include defaultDropShadow();
+
+ &--centered {
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ }
+
+ &--content {
+ flex-direction: column;
+ margin-top: 0.1875rem;
+ }
+
+ &--centered &--content {
+ flex-direction: column;
+ margin-top: 0;
+ }
+
+ &--icon {
+ color: var(--color-emerald-green);
+ }
+
+ &--image {
+ height: 4rem;
+ width: 4rem;
+ }
+
+ &--buttons {
+ display: flex;
+ flex-direction: row;
+ &-centered {
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ }
+ }
+}
diff --git a/packages/dnb-eufemia/src/components/info-card/style/dnb-info-card.scss b/packages/dnb-eufemia/src/components/info-card/style/dnb-info-card.scss
new file mode 100644
index 00000000000..44d049b5a6d
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/info-card/style/dnb-info-card.scss
@@ -0,0 +1,12 @@
+/*
+* DNB InfoCard
+*
+*/
+
+@import '../../../style/components/imports.scss';
+
+.dnb-info-card {
+ @include componentReset();
+}
+
+@import './_info-card.scss';
diff --git a/packages/dnb-eufemia/src/components/info-card/style/index.d.ts b/packages/dnb-eufemia/src/components/info-card/style/index.d.ts
new file mode 100644
index 00000000000..19795ed88cf
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/info-card/style/index.d.ts
@@ -0,0 +1,6 @@
+/**
+ * Web Style Import
+ *
+ */
+
+import './dnb-info-card.scss';
diff --git a/packages/dnb-eufemia/src/components/info-card/style/index.js b/packages/dnb-eufemia/src/components/info-card/style/index.js
new file mode 100644
index 00000000000..c491ff8b620
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/info-card/style/index.js
@@ -0,0 +1,6 @@
+/**
+ * Web Style Import
+ *
+ */
+
+import './dnb-info-card.scss'
diff --git a/packages/dnb-eufemia/src/components/lib.js b/packages/dnb-eufemia/src/components/lib.js
index 64918bb1478..9a3e4f07e1b 100644
--- a/packages/dnb-eufemia/src/components/lib.js
+++ b/packages/dnb-eufemia/src/components/lib.js
@@ -14,6 +14,7 @@ import { registerElement } from '../shared/component-helper'
// import all the available components
import Accordion from './accordion/Accordion'
import Autocomplete from './autocomplete/Autocomplete'
+import Avatar from './avatar/Avatar'
import Breadcrumb from './breadcrumb/Breadcrumb'
import Button from './button/Button'
import Checkbox from './checkbox/Checkbox'
@@ -29,6 +30,7 @@ import Heading from './heading/Heading'
import HelpButton from './help-button/HelpButton'
import Icon from './icon/Icon'
import IconPrimary from './icon-primary/IconPrimary'
+import InfoCard from './info-card/InfoCard'
import Input from './input/Input'
import InputMasked from './input-masked/InputMasked'
import Logo from './logo/Logo'
@@ -46,6 +48,7 @@ import Switch from './switch/Switch'
import Tabs from './tabs/Tabs'
import Tag from './tag/Tag'
import Textarea from './textarea/Textarea'
+import Timeline from './timeline/Timeline'
import ToggleButton from './toggle-button/ToggleButton'
import Tooltip from './tooltip/Tooltip'
@@ -53,6 +56,7 @@ import Tooltip from './tooltip/Tooltip'
export {
Accordion,
Autocomplete,
+ Avatar,
Breadcrumb,
Button,
Checkbox,
@@ -68,6 +72,7 @@ export {
HelpButton,
Icon,
IconPrimary,
+ InfoCard,
Input,
InputMasked,
Logo,
@@ -85,6 +90,7 @@ export {
Tabs,
Tag,
Textarea,
+ Timeline,
ToggleButton,
Tooltip,
}
@@ -93,6 +99,7 @@ export const getComponents = () => {
return {
Accordion,
Autocomplete,
+ Avatar,
Breadcrumb,
Button,
Checkbox,
@@ -108,6 +115,7 @@ export const getComponents = () => {
HelpButton,
Icon,
IconPrimary,
+ InfoCard,
Input,
InputMasked,
Logo,
@@ -125,6 +133,7 @@ export const getComponents = () => {
Tabs,
Tag,
Textarea,
+ Timeline,
ToggleButton,
Tooltip,
}
diff --git a/packages/dnb-eufemia/src/components/pagination/Pagination.js b/packages/dnb-eufemia/src/components/pagination/Pagination.js
index ce7587c268a..9a1d5030104 100644
--- a/packages/dnb-eufemia/src/components/pagination/Pagination.js
+++ b/packages/dnb-eufemia/src/components/pagination/Pagination.js
@@ -320,6 +320,10 @@ Pagination.Content = PaginationContent
const PaginationWrapper = Pagination
const InfinityMarkerWrapper = InfinityMarker
+export const Bar = (props) => (
+ null} {...props} />
+)
+
export const createPagination = (initProps = {}) => {
const store = React.createRef({})
const rerender = React.createRef(null)
diff --git a/packages/dnb-eufemia/src/components/pagination/__tests__/Pagination.test.js b/packages/dnb-eufemia/src/components/pagination/__tests__/Pagination.test.js
index bf2db13caf0..9fba132fe02 100644
--- a/packages/dnb-eufemia/src/components/pagination/__tests__/Pagination.test.js
+++ b/packages/dnb-eufemia/src/components/pagination/__tests__/Pagination.test.js
@@ -12,7 +12,7 @@ import {
toJson,
loadScss,
} from '../../../core/jest/jestSetup'
-import Component, { createPagination } from '../Pagination'
+import Component, { createPagination, Bar } from '../Pagination'
const snapshotProps = {
...fakeProps(require.resolve('../Pagination'), {
@@ -577,6 +577,13 @@ describe('Infinity scroller', () => {
expect(on_end).toHaveBeenCalledTimes(1)
})
+ it('should show pagination bar using Bar component', () => {
+ const Comp = mount( )
+
+ expect(Comp.exists('.dnb-pagination__bar')).toBe(true)
+ expect(Comp.exists('.dnb-pagination__indicator')).toBe(false)
+ })
+
// compare the snapshot
it('have to match snapshot', async () => {
const CheckComponent = mount( )
diff --git a/packages/dnb-eufemia/src/components/pagination/__tests__/__snapshots__/Pagination.test.js.snap b/packages/dnb-eufemia/src/components/pagination/__tests__/__snapshots__/Pagination.test.js.snap
index fdbae939174..30b3e4e8c1b 100644
--- a/packages/dnb-eufemia/src/components/pagination/__tests__/__snapshots__/Pagination.test.js.snap
+++ b/packages/dnb-eufemia/src/components/pagination/__tests__/__snapshots__/Pagination.test.js.snap
@@ -361,6 +361,12 @@ exports[`Pagination bar have to match snapshot 1`] = `
},
}
}
+ 4={
+ Object {
+ "displayName": "Bar",
+ "props": Object {},
+ }
+ }
align="left"
button_title={null}
class={null}
@@ -529,6 +535,12 @@ exports[`Pagination bar have to match snapshot 1`] = `
},
}
}
+ 4={
+ Object {
+ "displayName": "Bar",
+ "props": Object {},
+ }
+ }
align="left"
button_title={null}
class={null}
@@ -704,6 +716,12 @@ exports[`Pagination bar have to match snapshot 1`] = `
},
}
}
+ 4={
+ Object {
+ "displayName": "Bar",
+ "props": Object {},
+ }
+ }
align="left"
button_title={null}
class={null}
diff --git a/packages/dnb-eufemia/src/components/tag/Tag.tsx b/packages/dnb-eufemia/src/components/tag/Tag.tsx
index 1dc5c547f4c..e8890b6fbfb 100644
--- a/packages/dnb-eufemia/src/components/tag/Tag.tsx
+++ b/packages/dnb-eufemia/src/components/tag/Tag.tsx
@@ -2,21 +2,27 @@ import React from 'react'
import classnames from 'classnames'
// Components
-import { createSkeletonClass } from '../skeleton/SkeletonHelper'
-import { createSpacingClasses } from '../space/SpacingHelper'
-import Icon, { IconPrimaryIcon } from '../icon-primary/IconPrimary'
+import IconPrimary, { IconPrimaryIcon } from '../icon-primary/IconPrimary'
+import Button from '../button/Button'
// Shared
import Context from '../../shared/Context'
import { ISpacingProps, SkeletonTypes } from '../../shared/interfaces'
-import { extendPropsWithContext } from '../../shared/component-helper'
+import {
+ warn,
+ extendPropsWithContext,
+} from '../../shared/component-helper'
+
+// Internal
+import TagGroup from './TagGroup'
+import { TagGroupContext } from './TagContext'
export interface TagProps {
/**
* The content of the tag element, can be a string or a React Element.
* Default: null
*/
- text?: React.ReactNode
+ text?: string | React.ReactNode
/**
* Icon displaying on the left side
@@ -41,6 +47,24 @@ export interface TagProps {
* Default: null
*/
children?: string | React.ReactNode // ReactNode allows multiple elements, strings, numbers, fragments, portals...
+
+ /**
+ * Handle the click event on 'tag' element
+ * Default: null
+ */
+ onClick?: React.MouseEventHandler
+
+ /**
+ * Handle the delete event on 'tag' element
+ * Default: null
+ */
+ onDelete?: React.MouseEventHandler
+
+ /**
+ * Handle the delete event on 'tag' element
+ * Default: null
+ */
+ omitOnKeyUpDeleteEvent?: boolean
}
export const defaultProps = {
@@ -49,47 +73,122 @@ export const defaultProps = {
text: null,
children: null,
icon: null,
+ onClick: null,
+ onDelete: null,
+ omitOnKeyUpDeleteEvent: false,
}
-function Tag(localProps: TagProps & ISpacingProps) {
+const Tag = (localProps: TagProps & ISpacingProps) => {
// Every component should have a context
const context = React.useContext(Context)
+ const tagGroupContext = React.useContext(TagGroupContext)
+
// Extract additional props from global context
- const { className, skeleton, children, icon, text, ...props } =
- extendPropsWithContext(
- { ...defaultProps, ...localProps },
- defaultProps,
- context?.translation?.Tag,
- context?.Tag
- )
- const skeletonClasses = createSkeletonClass('shape', skeleton, context)
- const spacingClasses = createSpacingClasses(props)
+ const {
+ className,
+ skeleton,
+ children,
+ text,
+ onClick,
+ onDelete,
+ omitOnKeyUpDeleteEvent,
+ ...props
+ } = extendPropsWithContext(
+ { ...defaultProps, ...localProps },
+ defaultProps,
+ context?.translation?.Tag,
+ context?.Tag,
+ tagGroupContext
+ )
const content = text || children
+ const isClickable = !!onClick
+ const isRemovable = !!onDelete && !isClickable
+ const isInteractive = isClickable || isRemovable
+
+ const tagClassNames = classnames(
+ 'dnb-tag',
+ className,
+ isInteractive && 'dnb-tag--interactive',
+ isRemovable && 'dnb-tag--removable'
+ )
+
+ const isDeleteKeyboardEvent = (keyboardEvent) => {
+ return (
+ keyboardEvent.key === 'Backspace' || keyboardEvent.key === 'Delete'
+ )
+ }
+
+ const handleKeyUp = (event) => {
+ if (onDelete && isDeleteKeyboardEvent(event)) {
+ onDelete(event)
+ }
+ }
+
+ if (!isInteractive) {
+ props.element = 'span'
+ props.type = ''
+ }
+
+ if (isRemovable) {
+ props.icon = getDeleteIcon()
+ }
+
+ if (!tagGroupContext) {
+ warn(
+ `Tag group required: A Tag requires a Tag.Group with label description as a parent component. This is to ensure correct semantic and accessibility.`
+ )
+ }
return (
- handleKeyUp(e)
+ : undefined
+ }
{...props}
- >
- {icon && (
-
-
-
- )}
- {content && (
-
- {content}
-
- )}
-
+ />
)
+
+ function getDeleteIcon() {
+ return (
+
+
+
+
+ }
+ />
+ )
+ }
}
+Tag.Group = TagGroup
+
export default Tag
diff --git a/packages/dnb-eufemia/src/components/tag/TagContext.tsx b/packages/dnb-eufemia/src/components/tag/TagContext.tsx
new file mode 100644
index 00000000000..abd01d4c4c9
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/tag/TagContext.tsx
@@ -0,0 +1,3 @@
+import React from 'react'
+
+export const TagGroupContext = React.createContext(null)
diff --git a/packages/dnb-eufemia/src/components/tag/TagGroup.tsx b/packages/dnb-eufemia/src/components/tag/TagGroup.tsx
new file mode 100644
index 00000000000..235db8bade5
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/tag/TagGroup.tsx
@@ -0,0 +1,80 @@
+import React from 'react'
+import classnames from 'classnames'
+
+// Components
+import { createSpacingClasses } from '../space/SpacingHelper'
+
+// Shared
+import Context from '../../shared/Context'
+import { ISpacingProps } from '../../shared/interfaces'
+import { extendPropsWithContext } from '../../shared/component-helper'
+import { TagGroupContext } from './TagContext'
+
+export interface TagGroupProps {
+ /**
+ * Aria label to describe the tag group
+ * Default: null
+ */
+ label: string
+
+ /**
+ * Custom className on the component root
+ * Default: null
+ */
+ className?: string
+
+ /**
+ * The tags to group.
+ * Default: null
+ */
+ children?: React.ReactNode
+}
+
+export const defaultProps = {
+ label: null,
+ className: null,
+ children: null,
+}
+
+function TagGroup(localProps: TagGroupProps & ISpacingProps) {
+ // Every component should have a context
+ const context = React.useContext(Context)
+ // Extract additional props from global context
+ const {
+ label,
+ className,
+ children: childrenProp,
+ ...props
+ } = extendPropsWithContext(
+ { ...defaultProps, ...localProps },
+ defaultProps,
+ context?.TagGroup
+ )
+
+ let children = childrenProp
+
+ if (Array.isArray(childrenProp)) {
+ children = [...childrenProp].map((child) => {
+ return child
+ })
+ }
+
+ const spacingClasses = createSpacingClasses(props)
+
+ return (
+
+
+
+ {label}
+
+ {children}
+
+
+ )
+}
+
+export default TagGroup
diff --git a/packages/dnb-eufemia/src/components/tag/__tests__/Tag.screenshot.test.js b/packages/dnb-eufemia/src/components/tag/__tests__/Tag.screenshot.test.js
index 79d10b4af07..b445d1b2307 100644
--- a/packages/dnb-eufemia/src/components/tag/__tests__/Tag.screenshot.test.js
+++ b/packages/dnb-eufemia/src/components/tag/__tests__/Tag.screenshot.test.js
@@ -3,7 +3,7 @@
* This file will not run on "test:staged" because we don't require any related files
*/
-import {
+ import {
testPageScreenshot,
setupPageScreenshot,
} from '../../../core/jest/jestSetupScreenshots'
@@ -17,17 +17,32 @@ describe('Tag screenshot', () => {
})
expect(screenshot).toMatchImageSnapshot()
})
- it('have to match Tag with primary icon', async () => {
+
+ it('have to match Tag with icon', async () => {
const screenshot = await testPageScreenshot({
selector: '[data-visual-test="tag-icon"] .dnb-tag',
})
expect(screenshot).toMatchImageSnapshot()
})
- it('have to match Tag with secondary icon', async () => {
+ it('have to match a clickable Tag', async () => {
const screenshot = await testPageScreenshot({
- selector: '[data-visual-test="tag-secondary-icon"] .dnb-tag',
+ selector: '[data-visual-test="tag-clickable"] .dnb-tag',
+ })
+
+ expect(screenshot).toMatchImageSnapshot()
})
- expect(screenshot).toMatchImageSnapshot()
})
+it('have to match a removable Tag', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="tag-removable"] .dnb-tag',
+ })
+ expect(screenshot).toMatchImageSnapshot()
})
+
+it('have to match a removable Tag list', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="tag-removable-list"] .dnb-tag',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+})
\ No newline at end of file
diff --git a/packages/dnb-eufemia/src/components/tag/__tests__/Tag.test.tsx b/packages/dnb-eufemia/src/components/tag/__tests__/Tag.test.tsx
index 24a645280e4..d373966f480 100644
--- a/packages/dnb-eufemia/src/components/tag/__tests__/Tag.test.tsx
+++ b/packages/dnb-eufemia/src/components/tag/__tests__/Tag.test.tsx
@@ -1,12 +1,62 @@
import React from 'react'
-import { render, screen } from '@testing-library/react'
+import { fireEvent, render, screen } from '@testing-library/react'
import Tag from '../Tag'
-import { axeComponent, loadScss } from '../../../core/jest/jestSetup'
+import {
+ axeComponent,
+ loadScss,
+ mount,
+} from '../../../core/jest/jestSetup'
import { Provider } from '../../../shared'
+describe('Tag Group', () => {
+ it('renders without children', () => {
+ render( )
+
+ expect(screen.queryByTestId('tag-group')).not.toBeNull()
+ expect(screen.queryByTestId('tag')).toBeNull()
+ })
+
+ it('renders the label', () => {
+ const label = 'tags'
+ render( )
+ expect(screen.queryByTestId('tag-group-label')).not.toBeNull()
+ expect(screen.queryByTestId('tag-group-label').textContent).toBe(label)
+ })
+
+ it('renders a tag group with multiple tag elements by children', () => {
+ render(
+
+
+
+
+
+
+ )
+
+ expect(screen.queryAllByTestId('tag')).toHaveLength(4)
+ })
+
+ it('renders a tag group with className if className is provided', () => {
+ const customClassName = 'custom-class'
+
+ render(
+
+ ClassName
+
+ )
+ expect(screen.queryByTestId('tag-group').className).toMatch(
+ customClassName
+ )
+ })
+})
+
describe('Tag', () => {
it('renders without properties', () => {
- render( )
+ render(
+
+
+
+ )
expect(screen.queryByTestId('tag')).not.toBeNull()
})
@@ -14,53 +64,293 @@ describe('Tag', () => {
it('renders a tag with content by text prop', () => {
const text = 'This is a tag'
- render( )
+ render(
+
+
+
+ )
- expect(screen.queryByTestId('tag-text')).not.toBeNull()
- expect(screen.queryByTestId('tag-text').textContent).toBe(text)
+ expect(
+ screen.queryByTestId('tag').querySelector('.dnb-button__text')
+ ).not.toBeNull()
+ expect(
+ screen.queryByTestId('tag').querySelector('.dnb-button__text')
+ .textContent
+ ).toBe(text)
})
it('renders a tag with content by children prop', () => {
const text = 'This is a tag'
- render({text} )
+ render(
+
+ {text}
+
+ )
- expect(screen.queryByTestId('tag-text')).not.toBeNull()
- expect(screen.queryByTestId('tag-text').textContent).toBe(text)
+ expect(
+ screen.queryByTestId('tag').querySelector('.dnb-button__text')
+ ).not.toBeNull()
+ expect(
+ screen.queryByTestId('tag').querySelector('.dnb-button__text')
+ .textContent
+ ).toBe(text)
})
- it('renders a tag with content if both text and children prop is used', () => {
+ it('renders a tag with content if both text and children prop is defined', () => {
const text = 'This is a tag'
- render({text} )
+ render(
+
+ {text}
+
+ )
- expect(screen.queryByTestId('tag-text')).not.toBeNull()
- expect(screen.queryByTestId('tag-text').textContent).toBe(text)
+ expect(
+ screen.queryByTestId('tag').querySelector('.dnb-button__text')
+ ).not.toBeNull()
+ expect(
+ screen.queryByTestId('tag').querySelector('.dnb-button__text')
+ .textContent
+ ).toBe(text)
})
it('renders a tag with skeleton if skeleton is true', () => {
const skeletonClassName = 'dnb-skeleton'
- render(ClassName )
+ render(
+
+ ClassName
+
+ )
expect(screen.queryByTestId('tag').className).toMatch(
skeletonClassName
)
})
+ it('does not render a clickable Tag as default', () => {
+ render(
+
+
+
+ )
+
+ expect(screen.queryByRole('button')).toBeNull()
+ expect(screen.queryByTestId('tag').textContent).toBe('Tag with text')
+ })
+
+ it('does support icon', () => {
+ render(
+
+
+
+ )
+
+ expect(
+ screen.queryByTestId('tag').querySelector('.dnb-icon')
+ ).toBeTruthy()
+ })
+
+ describe('with onClick', () => {
+ it('renders a clickable tag with the correct attributes if onClick is defined', () => {
+ const clickableClassName = 'dnb-tag--interactive'
+
+ render(
+
+ {
+ console.log('onClick')
+ }}
+ >
+ Clickable
+
+
+ )
+ expect(screen.queryByTestId('tag').className).toMatch(
+ clickableClassName
+ )
+ expect(screen.queryByRole('button')).not.toBeNull()
+ })
+
+ it('fires onClick event if onClick is defined', () => {
+ const onClick = jest.fn()
+ render(
+
+ onClick
+
+ )
+
+ fireEvent.click(screen.getByRole('button'))
+ expect(onClick).toHaveBeenCalledTimes(1)
+ })
+
+ it('does support icon', () => {
+ render(
+
+
+
+ )
+
+ expect(
+ screen.queryByTestId('tag').querySelector('.dnb-icon')
+ ).toBeTruthy()
+ })
+ })
+
+ describe('with onDelete', () => {
+ it('renders a removable tag with the correct attributes if onDelete is defined', () => {
+ const clickableClassName = 'dnb-tag--interactive'
+ const removableClassName = 'dnb-tag--removable'
+
+ render(
+
+ {
+ console.log('onDelete')
+ }}
+ >
+ Removable
+
+
+ )
+ expect(screen.queryByTestId('tag').className).toMatch(
+ removableClassName
+ )
+ expect(screen.queryByTestId('tag').className).toMatch(
+ clickableClassName
+ )
+ expect(screen.queryByRole('button')).not.toBeNull()
+ })
+
+ it('fires onClick event if onDelete is defined', () => {
+ const onClick = jest.fn()
+ render(
+
+ onDelete
+
+ )
+
+ fireEvent.click(screen.getByRole('button'))
+ expect(onClick).toHaveBeenCalledTimes(1)
+ })
+
+ it('renders the close button if onDelete is defined', () => {
+ render(
+
+
+
+ )
+
+ expect(
+ screen.queryByTestId('tag').querySelector('.dnb-icon')
+ ).toBeTruthy()
+ })
+
+ it('does not support icon if onDelete', () => {
+ render(
+
+
+
+ )
+
+ expect(
+ screen.queryByTestId('tag').querySelector('.dnb-icon')
+ ).toBeTruthy()
+ expect(
+ screen.queryByTestId('tag').querySelectorAll('.dnb-icon').length
+ ).toBe(1)
+ })
+
+ it('renders the delete icon if onDelete is provided', () => {
+ render(
+
+
+
+ )
+
+ expect(screen.queryByTestId('tag-delete-icon')).not.toBeNull()
+ })
+
+ it('fires onClick event if both onClick and onDelete are defined', () => {
+ const onClick = jest.fn()
+ const onDelete = jest.fn()
+
+ render(
+
+
+ onClick
+
+
+ )
+
+ fireEvent.click(screen.getByRole('button'))
+ expect(onClick).toHaveBeenCalledTimes(1)
+ expect(onDelete).toHaveBeenCalledTimes(0)
+ })
+
+ it('fires onClick event when releasing Space or Delete (key up)', () => {
+ const onClick = jest.fn()
+
+ render(
+
+ Keyboard
+
+ )
+
+ fireEvent.keyUp(screen.getByRole('button'), {
+ key: 'Backspace',
+ keyCode: 'Backspace',
+ })
+
+ fireEvent.keyUp(screen.getByRole('button'), {
+ key: 'Delete',
+ keyCode: 'Delete',
+ })
+
+ expect(onClick).toHaveBeenCalledTimes(2)
+ })
+ })
+
+ it('warns when Tag is used without a Tag.Group as parent component', () => {
+ process.env.NODE_ENV = 'development'
+ global.console.log = jest.fn()
+ mount( )
+ expect(global.console.log).toBeCalled()
+ })
+
+ it('renders a tag with className if className is provided', () => {
+ const customClassName = 'custom-class'
+
+ render(
+
+ ClassName
+
+ )
+ expect(screen.queryByTestId('tag').className).toMatch(customClassName)
+ })
+
it('renders a tag with provider', () => {
render(
-
+
+
+
)
- expect(screen.queryByTestId('tag-text')).not.toBeNull()
+ expect(
+ screen.queryByTestId('tag').querySelector('.dnb-button__text')
+ ).not.toBeNull()
})
})
describe('Tag aria', () => {
it('should validate', async () => {
- const Component = render( )
+ const Component = render(
+
+
+
+ )
expect(await axeComponent(Component)).toHaveNoViolations()
})
})
diff --git a/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/Tag.test.tsx.snap b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/Tag.test.tsx.snap
index a4363e73b76..f770c06c8c0 100644
--- a/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/Tag.test.tsx.snap
+++ b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/Tag.test.tsx.snap
@@ -21,6 +21,430 @@ exports[`Tag scss have to match snapshot 1`] = `
* Document Reset
*
*/
+/*
+* DNB Button
+*
+*/
+/*
+* DNB icon
+*
+*/
+/*
+* Icon component
+*
+*/
+.dnb-icon {
+ display: inline-block;
+ vertical-align: middle;
+ font-size: 1rem;
+ line-height: 1rem;
+ color: inherit;
+ width: 1em;
+ height: 1em; }
+ .dnb-icon img,
+ .dnb-icon svg {
+ width: inherit;
+ height: inherit;
+ shape-rendering: geometricPrecision;
+ vertical-align: top; }
+ .dnb-icon svg[width='100%'] {
+ width: inherit; }
+ .dnb-icon svg[height='100%'] {
+ height: inherit; }
+ .dnb-icon--inherit-color svg:not([fill]),
+ .dnb-icon--inherit-color svg [fill] {
+ fill: currentColor; }
+ .dnb-icon--inherit-color svg [stroke] {
+ stroke: currentColor; }
+ .dnb-icon--small {
+ font-size: 0.75rem; }
+ .dnb-icon--default {
+ font-size: 1rem; }
+ .dnb-icon--medium {
+ font-size: 1.5rem; }
+ .dnb-icon--large {
+ font-size: 2rem; }
+ .dnb-icon--x-large {
+ font-size: 2.5rem; }
+ .dnb-icon--xx-large {
+ font-size: 3rem; }
+ .dnb-icon--custom-size {
+ width: auto;
+ height: auto; }
+ .dnb-icon--auto {
+ font-size: 1em; }
+ .dnb-icon--auto > .dnb-icon--wrapper {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center; }
+ h1 .dnb-icon,
+ h2 .dnb-icon,
+ h3 .dnb-icon,
+ h4 .dnb-icon,
+ h5 .dnb-icon,
+ h6 .dnb-icon {
+ vertical-align: middle; }
+ .dnb-icon.dnb-skeleton {
+ color: var(--skeleton-color) !important; }
+ .dnb-icon.dnb-skeleton::before, .dnb-icon.dnb-skeleton::after {
+ content: none !important; }
+ @media screen and (-ms-high-contrast: none) {
+ .dnb-icon {
+ flex: none; } }
+
+/*
+* DNB FormStatus
+*
+*/
+.dnb-form-status {
+ font-family: var(--font-family-default);
+ font-weight: var(--font-weight-basis);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ line-height: var(--line-height-basis);
+ color: var(--color-black-80, #333);
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ /**
+ * Ensure consistency and use the same as HTML reset -> html {...}
+ * between base and code package
+ */
+ -moz-tab-size: 4;
+ tab-size: 4;
+ -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%;
+ word-break: break-word;
+ /**
+ * 1. Remove repeating backgrounds in all browsers (opinionated).
+ * 2. Add border box sizing in all browsers (opinionated).
+ */
+ /**
+ * 1. Add text decoration inheritance in all browsers (opinionated).
+ * 2. Add vertical alignment inheritance in all browsers (opinionated).
+ */
+ margin: 0;
+ padding: 0; }
+ .dnb-form-status *,
+ .dnb-form-status ::before,
+ .dnb-form-status ::after {
+ background-repeat: no-repeat;
+ /* 1 */
+ box-sizing: border-box;
+ /* 2 */ }
+ .dnb-form-status ::before,
+ .dnb-form-status ::after {
+ text-decoration: inherit;
+ /* 1 */
+ vertical-align: inherit;
+ /* 2 */ }
+
+/*
+ * FormStatus component
+ *
+ */
+:root {
+ --form-status-radius: 0.25rem; }
+
+.dnb-form-status {
+ display: flex;
+ opacity: 1;
+ transition: height 400ms cubic-bezier(0.42, 0, 0, 1), opacity 400ms cubic-bezier(0.42, 0, 0, 1), margin 400ms cubic-bezier(0.42, 0, 0, 1), padding 400ms cubic-bezier(0.42, 0, 0, 1); }
+ .dnb-form-status--hidden {
+ will-change: height, opacity, margin, padding;
+ width: 0;
+ height: 0;
+ opacity: 0; }
+ .dnb-form-status--is-animating {
+ overflow: hidden;
+ width: auto; }
+ .dnb-form-status--disappear, .dnb-form-status--hidden {
+ margin: 0 !important;
+ padding: 0 !important; }
+ .dnb-form-status__shell {
+ display: flex;
+ justify-content: flex-start;
+ align-items: flex-start;
+ min-width: inherit;
+ border-radius: var(--form-status-radius); }
+ .dnb-form-status__text {
+ padding: 0.625rem 1rem;
+ cursor: text;
+ color: inherit;
+ line-height: var(--line-height-small);
+ font-size: var(--font-size-small);
+ white-space: normal; }
+ button .dnb-form-status__text {
+ cursor: inherit; }
+ .dnb-form-status__text .dnb-anchor {
+ font-size: inherit; }
+ .dnb-icon + .dnb-form-status__text {
+ padding-left: 0.5rem; }
+ .dnb-form-status__shell > .dnb-icon {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin: 0.3333333em 0.3333333em 0.3333333em 0.6666666em; }
+ .dnb-form-status__size--large .dnb-form-status__text {
+ padding-top: 1.125rem;
+ padding-bottom: 1.125rem; }
+ .dnb-form-status__size--large .dnb-form-status__shell > .dnb-icon {
+ margin-top: 0.6666666em;
+ margin-bottom: 0.6666666em; }
+ .dnb-form-status--stretch {
+ flex-grow: 1; }
+ .dnb-form-status--stretch .dnb-form-status__shell {
+ width: 100%; }
+ .dnb-form-status--stretch .dnb-form-status__text {
+ max-width: 47rem; }
+ .dnb-form-status[hidden] {
+ display: none; }
+ .dnb-form-status--no-animation,
+ html[data-visual-test] .dnb-form-status {
+ transition-duration: 1ms !important; }
+ @media screen and (-ms-high-contrast: none) {
+ .dnb-form-status__shell > .dnb-icon {
+ border-width: 1px; } }
+
+.dnb-button {
+ font-family: var(--font-family-default);
+ font-weight: var(--font-weight-basis);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ line-height: var(--line-height-basis);
+ color: var(--color-black-80, #333);
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ /**
+ * Ensure consistency and use the same as HTML reset -> html {...}
+ * between base and code package
+ */
+ -moz-tab-size: 4;
+ tab-size: 4;
+ -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%;
+ word-break: break-word;
+ /**
+ * 1. Remove repeating backgrounds in all browsers (opinionated).
+ * 2. Add border box sizing in all browsers (opinionated).
+ */
+ /**
+ * 1. Add text decoration inheritance in all browsers (opinionated).
+ * 2. Add vertical alignment inheritance in all browsers (opinionated).
+ */
+ margin: 0;
+ padding: 0; }
+ .dnb-button *,
+ .dnb-button ::before,
+ .dnb-button ::after {
+ background-repeat: no-repeat;
+ /* 1 */
+ box-sizing: border-box;
+ /* 2 */ }
+ .dnb-button ::before,
+ .dnb-button ::after {
+ text-decoration: inherit;
+ /* 1 */
+ vertical-align: inherit;
+ /* 2 */ }
+
+/*
+* Button component
+*
+*/
+:root {
+ --button-font-size: var(--font-size-basis);
+ --button-font-size-small: var(--font-size-small);
+ --button-width: 2.5rem;
+ --button-height: 2.5rem;
+ --button-width--small: 1.5rem;
+ --button-height--small: 1.5rem;
+ --button-width--medium: 2rem;
+ --button-height--medium: 2rem;
+ --button-width--large: 3rem;
+ --button-height--large: 3rem;
+ --button-icon-size: 1rem;
+ --button-border-width: 0.0625rem;
+ --button-border-width--hover: 0.1875rem;
+ --button-border-radius: calc(var(--button-height) / 2);
+ --button-border-radius--small: calc(var(--button-height--small) / 2);
+ --button-border-radius--medium: calc(var(--button-height--medium) / 2);
+ --button-border-radius--large: calc(var(--button-height--large) / 2); }
+
+.dnb-button {
+ position: relative;
+ user-select: none;
+ -webkit-user-select: none;
+ cursor: pointer;
+ white-space: nowrap;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--button-width);
+ height: auto;
+ padding: 0;
+ border: var(--button-border-width) solid transparent;
+ border-radius: var(--button-border-radius);
+ text-decoration: none;
+ font-size: var(--font-size-small);
+ /* stylelint-disable-next-line */ }
+ .dnb-button--wrap {
+ overflow-wrap: break-word;
+ white-space: normal; }
+ .dnb-button,
+ .dnb-core-style .dnb-button {
+ line-height: var(--button-height); }
+ .dnb-button__text {
+ margin: 0.5rem 0;
+ font-size: var(--button-font-size);
+ line-height: var(--line-height-basis);
+ color: inherit;
+ transform: translateY(-0.03125rem); }
+ .dnb-button__text [data-os='linux'] {
+ transform: translateY(-0.035rem); }
+ .dnb-button__alignment {
+ display: inline-block;
+ width: 0; }
+ .dnb-button__bounding {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ left: 0;
+ transform: scale(1.1, 1.4);
+ background-color: transparent;
+ border-radius: var(--button-border-radius); }
+ .dnb-button--has-text {
+ padding-left: 1.5rem;
+ padding-right: 1.5rem; }
+ .dnb-button--size-small {
+ width: var(--button-width--small);
+ font-size: var(--button-font-size-small);
+ border-radius: var(--button-border-radius--small); }
+ .dnb-button--size-small,
+ .dnb-core-style .dnb-button--size-small {
+ line-height: var(--button-height--small); }
+ .dnb-button--size-small .dnb-button__text {
+ margin: 0; }
+ .dnb-button--has-text.dnb-button--size-small {
+ padding-left: 1rem;
+ padding-right: 1rem; }
+ .dnb-button--has-text.dnb-button--icon-position-left.dnb-button--size-small {
+ padding-left: 0.5rem; }
+ .dnb-button--has-text.dnb-button--icon-position-right.dnb-button--size-small {
+ padding-right: 0.5rem; }
+ .dnb-button--size-medium {
+ width: var(--button-width--medium);
+ border-radius: var(--button-border-radius--medium); }
+ .dnb-button--size-medium,
+ .dnb-core-style .dnb-button--size-medium {
+ line-height: var(--button-height--medium); }
+ .dnb-button--size-medium .dnb-button__text {
+ margin: 0; }
+ .dnb-button--has-text.dnb-button--size-medium {
+ padding-left: 1rem;
+ padding-right: 1rem; }
+ .dnb-button--has-text.dnb-button--icon-position-left.dnb-button--size-medium {
+ padding-left: 0.5rem; }
+ .dnb-button--has-text.dnb-button--icon-position-right.dnb-button--size-medium {
+ padding-right: 0.5rem; }
+ .dnb-button--size-large {
+ width: var(--button-width--large);
+ border-radius: var(--button-border-radius--large); }
+ .dnb-button--size-large,
+ .dnb-core-style .dnb-button--size-large {
+ line-height: var(--button-height--large); }
+ .dnb-button--has-text.dnb-button--size-large {
+ padding-left: 2rem;
+ padding-right: 2rem; }
+ .dnb-button--has-text.dnb-button--icon-position-left.dnb-button--size-large {
+ padding-left: 1rem; }
+ .dnb-button--has-text.dnb-button--icon-position-right.dnb-button--size-large {
+ padding-right: 1rem; }
+ .dnb-button--has-text {
+ width: auto; }
+ .dnb-button--has-text .dnb-button__icon {
+ margin: 0 calc(var(--button-icon-size) / 2); }
+ .dnb-button--has-text.dnb-button--icon-position-left {
+ padding-left: 0.5rem; }
+ .dnb-button--has-text.dnb-button--icon-position-right {
+ padding-right: 0.5rem; }
+ .dnb-button--has-text.dnb-button--has-icon .dnb-button__icon {
+ order: 2; }
+ .dnb-button--has-text.dnb-button--has-icon .dnb-button__text {
+ order: 1; }
+ .dnb-button:not(.dnb-button--has-text) .dnb-button__icon {
+ width: inherit; }
+ .dnb-button__icon.dnb-icon svg:not([width]):not([height]) {
+ width: var(--button-icon-size);
+ height: var(--button-icon-size); }
+ [href] > .dnb-button__icon.dnb-icon {
+ line-height: var(--button-font-size); }
+ .dnb-button--has-text.dnb-button--has-icon.dnb-button--icon-position-left .dnb-button__icon, .dnb-button--has-text.dnb-button--has-icon.dnb-button--icon-position-top .dnb-button__icon {
+ order: 1; }
+ .dnb-button--has-text.dnb-button--has-icon.dnb-button--icon-position-left > *,
+ .dnb-button--has-text.dnb-button--has-icon.dnb-button--icon-position-left .dnb-button__text, .dnb-button--has-text.dnb-button--has-icon.dnb-button--icon-position-top > *,
+ .dnb-button--has-text.dnb-button--has-icon.dnb-button--icon-position-top .dnb-button__text {
+ order: 2; }
+ .dnb-button--stretch {
+ width: 100%; }
+ .dnb-button--reset {
+ margin: 0;
+ padding: 0;
+ width: auto;
+ height: auto;
+ overflow: visible;
+ border: none;
+ border-radius: 0;
+ background-color: transparent;
+ appearance: none;
+ box-shadow: none;
+ color: inherit;
+ font: inherit;
+ text-align: inherit;
+ line-height: inherit; }
+ html:not([data-whatintent='touch']) .dnb-button--reset:hover[disabled] {
+ cursor: not-allowed; }
+ html:not([data-whatintent='touch']) .dnb-button--reset:hover:not([disabled]) {
+ box-shadow: none;
+ border: none; }
+ .dnb-button--reset:not([disabled]):focus, .dnb-button--reset:not([disabled]):active {
+ outline: none; }
+ html[data-whatinput='keyboard'] .dnb-button--reset:not([disabled]):focus, html[data-whatinput='keyboard'] .dnb-button--reset:not([disabled]):active {
+ --border-color: var(--color-emerald-green);
+ box-shadow: 0 0 0 0.125rem var(--border-color);
+ border-color: transparent; }
+ @media screen and (-ms-high-contrast: none) {
+ html[data-whatinput='keyboard'] .dnb-button--reset:not([disabled]):focus, html[data-whatinput='keyboard'] .dnb-button--reset:not([disabled]):active {
+ box-shadow: 0 0 0 0.125rem var(--color-emerald-green); } }
+ html[data-whatinput='mouse'] .dnb-button--reset:not([disabled]):focus,
+ html[data-whatinput='mouse'] .dnb-button--reset:not([disabled]):active {
+ box-shadow: none;
+ color: inherit;
+ border: none; }
+ .dnb-button[type='button'], .dnb-button[type='reset'], .dnb-button[type='submit'] {
+ appearance: none;
+ -moz-appearance: none;
+ -webkit-appearance: none; }
+ .dnb-button[disabled] {
+ cursor: not-allowed; }
+ .dnb-form-row--vertical .dnb-form-row__content > .dnb-button {
+ align-self: flex-start; }
+ .dnb-form-row--horizontal .dnb-form-row__content .dnb-button__text {
+ white-space: nowrap; }
+ .dnb-button + .dnb-form-status {
+ margin-top: 0.5rem; }
+ @media screen and (-ms-high-contrast: none) {
+ .dnb-button {
+ flex: none; }
+ .dnb-button__icon, .dnb-button__text {
+ transform: translateY(-0.0625rem); } }
+
+/* Firefox includes a hidden border which messes up button dimensions */
+button.dnb-button::-moz-focus-inner {
+ border: none; }
+
.dnb-tag {
font-family: var(--font-family-default);
font-weight: var(--font-weight-basis);
@@ -68,22 +492,206 @@ exports[`Tag scss have to match snapshot 1`] = `
* Tag component
*
*/
-:root {
- --tag-border-radius: 2.5rem; }
+/*
+* Button mixins
+*
+*/
+/*
+* Tag mixins
+*
+*/
+.dnb-tag.dnb-button {
+ appearance: none;
+ background-color: var(--color-black-8); }
+ .dnb-tag.dnb-button.dnb-button--size-small {
+ padding-left: 0.5rem;
+ padding-right: 0.5rem; }
+ .dnb-tag.dnb-button.dnb-button--size-small.dnb-button--has-icon {
+ padding-left: 0; }
+ .dnb-tag.dnb-button .dnb-button__text {
+ font-size: var(--font-size-x-small);
+ transform: none; }
-.dnb-tag {
- display: inline-flex;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- padding: var(--spacing-xx-small) var(--spacing-x-small);
- font-size: var(--font-size-x-small);
- background-color: var(--color-black-8);
- border-radius: var(--tag-border-radius); }
- .dnb-tag__icon {
- display: flex;
- align-items: center; }
- .dnb-tag__text {
- color: var(--color-black-80); }
+.dnb-tag:not(.dnb-tag--interactive) {
+ user-select: unset;
+ cursor: unset; }
+ .dnb-tag:not(.dnb-tag--interactive) .dnb-button__text {
+ cursor: text; }
+
+.dnb-tag--interactive.dnb-button {
+ color: var(--color-sea-green);
+ --border-color: var(--color-sea-green);
+ box-shadow: inset 0 0 0 0.0625rem var(--border-color);
+ /* iOS fix - because \\"inset\\" works not fine with border-radius */
+ /* Safari fix - because \\"inset\\" works not fine with border-radius if the user zooms the page */
+ border-color: transparent; }
+ @supports (-webkit-touch-callout: none) {
+ .dnb-tag--interactive.dnb-button {
+ box-shadow: 0 0 0 0.0625rem var(--border-color); } }
+ @media not all and (min-resolution: 0.001dpcm) {
+ @supports (-webkit-appearance: none) and (stroke-color: transparent) and (not (-webkit-overflow-scrolling: touch)) {
+ .dnb-tag--interactive.dnb-button {
+ box-shadow: 0 0 0 0.0625rem var(--border-color); } } }
+ @media screen and (-ms-high-contrast: none) {
+ .dnb-tag--interactive.dnb-button {
+ box-shadow: inset 0 0 0 1px var(--color-sea-green); } }
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:hover[disabled] {
+ cursor: not-allowed; }
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:hover:not([disabled]) {
+ color: var(--color-sea-green);
+ background-color: var(--color-black-8);
+ --border-color: var(--color-emerald-green);
+ box-shadow: 0 0 0 0.125rem var(--border-color);
+ border-color: transparent; }
+ @media screen and (-ms-high-contrast: none) {
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:hover:not([disabled]) {
+ box-shadow: 0 0 0 0.125rem var(--color-emerald-green); } }
+ @media screen and (-ms-high-contrast: none) {
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:hover:not([disabled]) {
+ opacity: 1; } }
+ .dnb-tag--interactive.dnb-button:focus[disabled],
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:focus[disabled] {
+ cursor: not-allowed; }
+ .dnb-tag--interactive.dnb-button:focus:not([disabled]),
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:focus:not([disabled]) {
+ outline: none; }
+ html[data-whatinput='keyboard'] .dnb-tag--interactive.dnb-button:focus:not([disabled]), html[data-whatinput='keyboard']
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:focus:not([disabled]) {
+ color: var(--color-sea-green);
+ background-color: var(--color-black-8); }
+ html[data-whatinput='keyboard'] .dnb-tag--interactive.dnb-button:focus:not([disabled]), html[data-whatinput='keyboard']
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:focus:not([disabled]) {
+ --border-color: var(--color-emerald-green);
+ box-shadow: inset 0 0 0 0.125rem var(--border-color);
+ /* iOS fix - because \\"inset\\" works not fine with border-radius */
+ /* Safari fix - because \\"inset\\" works not fine with border-radius if the user zooms the page */
+ border-color: transparent; }
+ @supports (-webkit-touch-callout: none) {
+ html[data-whatinput='keyboard'] .dnb-tag--interactive.dnb-button:focus:not([disabled]), html[data-whatinput='keyboard']
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:focus:not([disabled]) {
+ box-shadow: 0 0 0 0.125rem var(--border-color); } }
+ @media not all and (min-resolution: 0.001dpcm) {
+ @supports (-webkit-appearance: none) and (stroke-color: transparent) and (not (-webkit-overflow-scrolling: touch)) {
+ html[data-whatinput='keyboard'] .dnb-tag--interactive.dnb-button:focus:not([disabled]), html[data-whatinput='keyboard']
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:focus:not([disabled]) {
+ box-shadow: 0 0 0 0.125rem var(--border-color); } } }
+ @media screen and (-ms-high-contrast: none) {
+ html[data-whatinput='keyboard'] .dnb-tag--interactive.dnb-button:focus:not([disabled]), html[data-whatinput='keyboard']
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:focus:not([disabled]) {
+ box-shadow: inset 0 0 0 0.125rem var(--color-emerald-green); } }
+ .dnb-tag--interactive.dnb-button:active[disabled],
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:active[disabled] {
+ cursor: not-allowed; }
+ .dnb-tag--interactive.dnb-button:active:not([disabled]),
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:active:not([disabled]) {
+ color: var(--color-sea-green);
+ background-color: var(--color-mint-green-50);
+ --border-color: transparent;
+ box-shadow: 0 0 0 0.0625rem var(--border-color);
+ border-color: transparent; }
+ @media screen and (-ms-high-contrast: none) {
+ .dnb-tag--interactive.dnb-button:active:not([disabled]),
+ html:not([data-whatintent='touch']) .dnb-tag--interactive.dnb-button:active:not([disabled]) {
+ box-shadow: 0 0 0 0.0625rem transparent; } }
+ .dnb-tag--interactive.dnb-button[disabled] {
+ color: var(--color-sea-green-30);
+ background-color: var(--color-white);
+ --border-color: var(--color-sea-green-30);
+ box-shadow: 0 0 0 0.0625rem var(--border-color);
+ border-color: transparent; }
+ @media screen and (-ms-high-contrast: none) {
+ .dnb-tag--interactive.dnb-button[disabled] {
+ box-shadow: 0 0 0 0.0625rem var(--color-sea-green-30); } }
+
+.dnb-tag--removable.dnb-button {
+ color: var(--color-white);
+ background-color: var(--color-sea-green); }
+ .dnb-tag--removable.dnb-button svg .dnb-icon-close-circle-path {
+ fill: var(--color-white); }
+ .dnb-tag--removable.dnb-button svg .dnb-icon-close-cross-path {
+ stroke: var(--color-sea-green); }
+ .dnb-tag--removable.dnb-button:focus[disabled],
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:focus[disabled] {
+ cursor: not-allowed; }
+ .dnb-tag--removable.dnb-button:focus:not([disabled]),
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:focus:not([disabled]) {
+ outline: none; }
+ html[data-whatinput='keyboard'] .dnb-tag--removable.dnb-button:focus:not([disabled]), html[data-whatinput='keyboard']
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:focus:not([disabled]) {
+ color: var(--color-sea-green);
+ background-color: var(--color-white); }
+ html[data-whatinput='keyboard'] .dnb-tag--removable.dnb-button:focus:not([disabled]), html[data-whatinput='keyboard']
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:focus:not([disabled]) {
+ --border-color: var(--color-emerald-green);
+ box-shadow: inset 0 0 0 0.125rem var(--border-color);
+ /* iOS fix - because \\"inset\\" works not fine with border-radius */
+ /* Safari fix - because \\"inset\\" works not fine with border-radius if the user zooms the page */
+ border-color: transparent; }
+ @supports (-webkit-touch-callout: none) {
+ html[data-whatinput='keyboard'] .dnb-tag--removable.dnb-button:focus:not([disabled]), html[data-whatinput='keyboard']
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:focus:not([disabled]) {
+ box-shadow: 0 0 0 0.125rem var(--border-color); } }
+ @media not all and (min-resolution: 0.001dpcm) {
+ @supports (-webkit-appearance: none) and (stroke-color: transparent) and (not (-webkit-overflow-scrolling: touch)) {
+ html[data-whatinput='keyboard'] .dnb-tag--removable.dnb-button:focus:not([disabled]), html[data-whatinput='keyboard']
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:focus:not([disabled]) {
+ box-shadow: 0 0 0 0.125rem var(--border-color); } } }
+ @media screen and (-ms-high-contrast: none) {
+ html[data-whatinput='keyboard'] .dnb-tag--removable.dnb-button:focus:not([disabled]), html[data-whatinput='keyboard']
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:focus:not([disabled]) {
+ box-shadow: inset 0 0 0 0.125rem var(--color-emerald-green); } }
+ .dnb-tag--removable.dnb-button:focus:not([disabled]) svg .dnb-icon-close-circle-path,
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:focus:not([disabled]) svg .dnb-icon-close-circle-path {
+ fill: var(--color-sea-green); }
+ .dnb-tag--removable.dnb-button:focus:not([disabled]) svg .dnb-icon-close-cross-path,
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:focus:not([disabled]) svg .dnb-icon-close-cross-path {
+ stroke: var(--color-white); }
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:hover[disabled] {
+ cursor: not-allowed; }
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:hover:not([disabled]) {
+ color: var(--color-sea-green);
+ background-color: var(--color-white);
+ --border-color: var(--color-emerald-green);
+ box-shadow: 0 0 0 0.125rem var(--border-color);
+ border-color: transparent; }
+ @media screen and (-ms-high-contrast: none) {
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:hover:not([disabled]) {
+ box-shadow: 0 0 0 0.125rem var(--color-emerald-green); } }
+ @media screen and (-ms-high-contrast: none) {
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:hover:not([disabled]) {
+ opacity: 1; } }
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:hover:not([disabled]) svg .dnb-icon-close-circle-path {
+ fill: var(--color-sea-green); }
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:hover:not([disabled]) svg .dnb-icon-close-cross-path {
+ stroke: var(--color-white); }
+ .dnb-tag--removable.dnb-button:active[disabled],
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:active[disabled] {
+ cursor: not-allowed; }
+ .dnb-tag--removable.dnb-button:active:not([disabled]),
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:active:not([disabled]) {
+ color: var(--color-sea-green);
+ background-color: var(--color-mint-green-50);
+ --border-color: transparent;
+ box-shadow: 0 0 0 0.0625rem var(--border-color);
+ border-color: transparent; }
+ @media screen and (-ms-high-contrast: none) {
+ .dnb-tag--removable.dnb-button:active:not([disabled]),
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:active:not([disabled]) {
+ box-shadow: 0 0 0 0.0625rem transparent; } }
+ .dnb-tag--removable.dnb-button:active:not([disabled]) svg .dnb-icon-close-circle-path,
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:active:not([disabled]) svg .dnb-icon-close-circle-path {
+ fill: var(--color-sea-green); }
+ .dnb-tag--removable.dnb-button:active:not([disabled]) svg .dnb-icon-close-cross-path,
+ html:not([data-whatintent='touch']) .dnb-tag--removable.dnb-button:active:not([disabled]) svg .dnb-icon-close-cross-path {
+ stroke: var(--color-white); }
+ .dnb-tag--removable.dnb-button[disabled] svg .dnb-icon-close-circle-path {
+ fill: var(--color-mint-green-50); }
+ .dnb-tag--removable.dnb-button[disabled] svg .dnb-icon-close-cross-path {
+ stroke: var(--color-sea-green-30); }
+ .dnb-tag--removable.dnb-button .dnb-button__text {
+ padding-left: 0.5rem; }
+
+.dnb-tag--removable.dnb-button--size-small.dnb-button--has-icon {
+ padding-right: 0; }
"
`;
diff --git a/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-have-to-match-a-removable-tag-1-3387d.snap.png b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-have-to-match-a-removable-tag-1-3387d.snap.png
new file mode 100644
index 00000000000..937a486b822
Binary files /dev/null and b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-have-to-match-a-removable-tag-1-3387d.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-have-to-match-a-removable-tag-list-1-f49cc.snap.png b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-have-to-match-a-removable-tag-list-1-f49cc.snap.png
new file mode 100644
index 00000000000..db9a817deae
Binary files /dev/null and b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-have-to-match-a-removable-tag-list-1-f49cc.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-a-clickable-tag-1-425c7.snap.png b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-a-clickable-tag-1-425c7.snap.png
new file mode 100644
index 00000000000..e21c053b59f
Binary files /dev/null and b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-a-clickable-tag-1-425c7.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-tag-default-1-58f81.snap.png b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-tag-default-1-58f81.snap.png
index c8b52a3f116..885505a0a53 100644
Binary files a/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-tag-default-1-58f81.snap.png and b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-tag-default-1-58f81.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-tag-with-icon-1-25cd8.snap.png b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-tag-with-icon-1-25cd8.snap.png
new file mode 100644
index 00000000000..8e28b6a84fd
Binary files /dev/null and b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-tag-with-icon-1-25cd8.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-tag-with-primary-icon-1-73553.snap.png b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-tag-with-primary-icon-1-73553.snap.png
deleted file mode 100644
index a982c445541..00000000000
Binary files a/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-tag-with-primary-icon-1-73553.snap.png and /dev/null differ
diff --git a/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-tag-with-secondary-icon-1-c0b56.snap.png b/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-tag-with-secondary-icon-1-c0b56.snap.png
deleted file mode 100644
index 30986445d85..00000000000
Binary files a/packages/dnb-eufemia/src/components/tag/__tests__/__snapshots__/tag-screenshot-test-js-tag-screenshot-have-to-match-tag-with-secondary-icon-1-c0b56.snap.png and /dev/null differ
diff --git a/packages/dnb-eufemia/src/components/tag/style/_tag.scss b/packages/dnb-eufemia/src/components/tag/style/_tag.scss
index 071f3c99af3..c14aa87defd 100644
--- a/packages/dnb-eufemia/src/components/tag/style/_tag.scss
+++ b/packages/dnb-eufemia/src/components/tag/style/_tag.scss
@@ -3,24 +3,88 @@
*
*/
-:root {
- --tag-border-radius: 2.5rem;
-}
+@import '../../button/style/themes/_button-mixins.scss';
+@import './themes/_tag-mixins.scss';
.dnb-tag {
- display: inline-flex;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- padding: var(--spacing-xx-small) var(--spacing-x-small);
- font-size: var(--font-size-x-small);
- background-color: var(--color-black-8);
- border-radius: var(--tag-border-radius);
- &__icon {
- display: flex;
- align-items: center;
+ &.dnb-button {
+ appearance: none;
+ background-color: var(--color-black-8);
+
+ &.dnb-button--size-small {
+ padding-left: 0.5rem;
+ padding-right: 0.5rem;
+ }
+
+ &.dnb-button--size-small.dnb-button--has-icon {
+ padding-left: 0;
+ }
+
+ .dnb-button__text {
+ font-size: var(--font-size-x-small);
+ transform: none;
+ }
}
- &__text {
- color: var(--color-black-80);
+
+ &:not(#{&}--interactive) {
+ user-select: unset;
+ cursor: unset;
+ .dnb-button__text {
+ cursor: text;
+ }
+ }
+
+ &--interactive {
+ &.dnb-button {
+ color: var(--color-sea-green);
+
+ @include fakeBorder(var(--color-sea-green), 0.0625rem, inset, true);
+ @include buttonHover(var(--color-sea-green), var(--color-black-8));
+ @include buttonFocus(var(--color-sea-green), var(--color-black-8));
+ @include buttonActive(
+ var(--color-sea-green),
+ var(--color-mint-green-50)
+ );
+ &[disabled] {
+ color: var(--color-sea-green-30);
+ background-color: var(--color-white);
+ @include fakeBorder(var(--color-sea-green-30));
+ }
+ }
+ }
+
+ &--removable {
+ &.dnb-button {
+ color: var(--color-white);
+ background-color: var(--color-sea-green);
+ @include deleteIcon(var(--color-white), var(--color-sea-green));
+
+ @include buttonFocus(var(--color-sea-green), var(--color-white)) {
+ @include deleteIcon();
+ }
+ @include buttonHover(var(--color-sea-green), var(--color-white)) {
+ @include deleteIcon();
+ }
+ @include buttonActive(
+ var(--color-sea-green),
+ var(--color-mint-green-50)
+ ) {
+ @include deleteIcon();
+ }
+ &[disabled] {
+ @include deleteIcon(
+ var(--color-mint-green-50),
+ var(--color-sea-green-30)
+ );
+ }
+
+ .dnb-button__text {
+ padding-left: 0.5rem;
+ }
+ }
+
+ &.dnb-button--size-small.dnb-button--has-icon {
+ padding-right: 0;
+ }
}
}
diff --git a/packages/dnb-eufemia/src/components/tag/style/dnb-tag.scss b/packages/dnb-eufemia/src/components/tag/style/dnb-tag.scss
index e5c6e3fa759..c8bc5a44876 100644
--- a/packages/dnb-eufemia/src/components/tag/style/dnb-tag.scss
+++ b/packages/dnb-eufemia/src/components/tag/style/dnb-tag.scss
@@ -5,6 +5,9 @@
@import '../../../style/components/imports.scss';
+// import dependencies
+@import '../../button/style/dnb-button.scss';
+
.dnb-tag {
@include componentReset();
}
diff --git a/packages/dnb-eufemia/src/components/tag/style/themes/_tag-mixins.scss b/packages/dnb-eufemia/src/components/tag/style/themes/_tag-mixins.scss
new file mode 100644
index 00000000000..12cbe343709
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/tag/style/themes/_tag-mixins.scss
@@ -0,0 +1,18 @@
+/*
+* Tag mixins
+*
+*/
+
+@mixin deleteIcon(
+ $fillColor: var(--color-sea-green),
+ $strokeColor: var(--color-white)
+) {
+ svg {
+ .dnb-icon-close-circle-path {
+ fill: $fillColor;
+ }
+ .dnb-icon-close-cross-path {
+ stroke: $strokeColor;
+ }
+ }
+}
diff --git a/packages/dnb-eufemia/src/components/timeline/Timeline.tsx b/packages/dnb-eufemia/src/components/timeline/Timeline.tsx
new file mode 100644
index 00000000000..46ca416210e
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/timeline/Timeline.tsx
@@ -0,0 +1,91 @@
+import React from 'react'
+import classnames from 'classnames'
+
+// Components
+import { createSpacingClasses } from '../space/SpacingHelper'
+
+// Shared
+import Context from '../../shared/Context'
+import { ISpacingProps, SkeletonTypes } from '../../shared/interfaces'
+import { extendPropsWithContext } from '../../shared/component-helper'
+
+// Internal
+import TimelineItem, { TimelineItemProps } from './TimelineItem'
+
+export * from './TimelineItem'
+
+export interface TimelineProps {
+ /**
+ * Custom className on the component root
+ * Default: null
+ */
+ className?: string
+
+ /**
+ * Skeleton should be applied when loading content
+ * Default: null
+ */
+ skeleton?: SkeletonTypes
+
+ /**
+ * Pass in a list of your events as objects of timelineitem, to render them as timelineitems.
+ * Default: null
+ */
+ data?: TimelineItemProps[]
+
+ /**
+ * The content of the component. Can be used instead of prop "data".
+ * Default: null
+ */
+ children?: TimelineItemProps[]
+}
+
+export const defaultProps = {
+ className: null,
+ skeleton: false,
+ data: null,
+ children: null,
+}
+
+function Timeline(localProps: TimelineProps & ISpacingProps) {
+ // Every component should have a context
+ const context = React.useContext(Context)
+ // Extract additional props from global context
+ const {
+ className,
+ skeleton,
+ data,
+ children: childrenItems,
+ ...props
+ } = extendPropsWithContext(
+ { ...defaultProps, ...localProps },
+ defaultProps,
+ context?.Timeline
+ )
+
+ const spacingClasses = createSpacingClasses(props)
+
+ return (
+
+ {data?.map((timelineItem: TimelineItemProps) => (
+
+ ))}
+
+ {childrenItems}
+
+ )
+}
+
+Timeline.Item = TimelineItem
+
+export { TimelineItem }
+
+export default Timeline
diff --git a/packages/dnb-eufemia/src/components/timeline/TimelineItem.tsx b/packages/dnb-eufemia/src/components/timeline/TimelineItem.tsx
new file mode 100644
index 00000000000..d4969aec517
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/timeline/TimelineItem.tsx
@@ -0,0 +1,193 @@
+import React from 'react'
+import classnames from 'classnames'
+
+// Components
+import FormStatus from '../form-status/FormStatus'
+import Icon, { IconPrimaryIcon } from '../icon-primary/IconPrimary'
+import { createSkeletonClass } from '../skeleton/SkeletonHelper'
+
+// Icons
+import checkIcon from '../../icons/check'
+import calendarIcon from '../../icons/calendar'
+import pinIcon from '../../icons/pin'
+
+// Shared
+import Context from '../../shared/Context'
+import { SkeletonTypes } from '../../shared/interfaces'
+import { extendPropsWithContext } from '../../shared/component-helper'
+
+export interface TimelineItemProps {
+ /**
+ * Icon displaying on the left side.
+ * Default: `check` for state `completed`, `pin` for state `current`, and `calendar` for state `upcoming` .
+ */
+ icon?: IconPrimaryIcon
+
+ /**
+ * Text displaying the title of the item's corresponding page.
+ * Default: translations based on the icon.
+ */
+ iconAlt?: string
+
+ /**
+ * Text displaying the name of the timeline item.
+ */
+ name: React.ReactNode
+
+ /**
+ * Text displaying the date of the timeline item.
+ */
+ date?: React.ReactNode
+
+ /**
+ * Text displaying info message of the timeline item.
+ */
+ infoMessage?: React.ReactNode
+
+ /**
+ * The component state. State 'completed' or 'current' or 'upcoming'.
+ * Default: null
+ */
+ state: 'completed' | 'current' | 'upcoming'
+
+ /**
+ * Skeleton should be applied when loading content
+ * Default: null
+ */
+ skeleton?: SkeletonTypes
+}
+
+const defaultProps = {
+ icon: null,
+ iconAlt: null,
+ name: null,
+ date: null,
+ infoMessage: null,
+ state: null,
+ skeleton: false,
+}
+
+export default function TimelineItem(localProps: TimelineItemProps) {
+ // Every component should have a context
+ const context = React.useContext(Context)
+ const {
+ translation: {
+ TimelineItem: {
+ alt_label_completed,
+ alt_label_current,
+ alt_label_upcoming,
+ },
+ },
+ } = context
+
+ // Extract additional props from global context
+ const {
+ icon,
+ iconAlt,
+ name,
+ date,
+ infoMessage,
+ state,
+ skeleton,
+ ...props
+ } = extendPropsWithContext(
+ { ...defaultProps, ...localProps },
+ defaultProps,
+ context?.TimelineItem
+ )
+
+ const stateIsCompleted = state === 'completed'
+ const stateIsCurrent = state === 'current'
+ const stateIsUpcoming = state === 'upcoming'
+
+ const skeletonClasses = createSkeletonClass('font', skeleton, context)
+ const classes = classnames(
+ 'dnb-timeline__item',
+ skeletonClasses,
+ `dnb-timeline__item--${state}`
+ )
+
+ const TimelineItemIcon = () => {
+ const currentIcon =
+ icon ||
+ (stateIsCompleted && checkIcon) ||
+ (stateIsCurrent && pinIcon) ||
+ (stateIsUpcoming && calendarIcon)
+
+ const currentAltLabel =
+ iconAlt ||
+ (stateIsCompleted && alt_label_completed) ||
+ (stateIsCurrent && alt_label_current) ||
+ (stateIsUpcoming && alt_label_upcoming)
+ return (
+
+
+ {!skeleton && currentIcon && (
+
+ )}
+
+ )
+ }
+
+ const TimelineItemName = () => {
+ return (
+
+ {name}
+
+ )
+ }
+
+ const TimelineItemLabel = () => {
+ return (
+
+
+
+
+ )
+ }
+
+ const TimelineItemContent = () => {
+ return (
+
+ {date && (
+
+ {date}
+
+ )}
+ {infoMessage && (
+
+ )}
+
+ )
+ }
+
+ return (
+
+
+
+
+ )
+}
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/Timeline.screenshot.test.js b/packages/dnb-eufemia/src/components/timeline/__tests__/Timeline.screenshot.test.js
new file mode 100644
index 00000000000..8c161c54b7d
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/timeline/__tests__/Timeline.screenshot.test.js
@@ -0,0 +1,112 @@
+/**
+ * Screenshot Test
+ * This file will not run on "test:staged" because we don't require any related files
+ */
+
+import {
+ testPageScreenshot,
+ setupPageScreenshot,
+} from '../../../core/jest/jestSetupScreenshots'
+
+describe('Timeline screenshot', () => {
+ setupPageScreenshot({ url: '/uilib/components/timeline/demos' })
+
+ it('have to match Timeline single completed', async () => {
+ const screenshot = await testPageScreenshot({
+ selector:
+ '[data-visual-test="timeline-single-completed"] .dnb-timeline',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match Timeline single current', async () => {
+ const screenshot = await testPageScreenshot({
+ selector:
+ '[data-visual-test="timeline-single-current"] .dnb-timeline',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match Timeline single upcoming', async () => {
+ const screenshot = await testPageScreenshot({
+ selector:
+ '[data-visual-test="timeline-single-upcoming"] .dnb-timeline',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match Timeline multiple', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="timeline-multiple"] .dnb-timeline',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match Timeline multiple with children', async () => {
+ const screenshot = await testPageScreenshot({
+ selector:
+ '[data-visual-test="timeline-multiple-children"] .dnb-timeline',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match Timeline with multiple completed time ine items', async () => {
+ const screenshot = await testPageScreenshot({
+ selector:
+ '[data-visual-test="timeline-multiple-completed"] .dnb-timeline',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match Timeline with multiple upcoming timeline items', async () => {
+ const screenshot = await testPageScreenshot({
+ selector:
+ '[data-visual-test="timeline-multiple-upcoming"] .dnb-timeline',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match Timeline with multiple current timeline items', async () => {
+ const screenshot = await testPageScreenshot({
+ selector:
+ '[data-visual-test="timeline-multiple-current"] .dnb-timeline',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match Timeline states', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="timeline-states"] .dnb-timeline',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match Timeline icons', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="timeline-icons"] .dnb-timeline',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match Timeline icons', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="timeline-icons"] .dnb-timeline',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match Timeline skeleton', async () => {
+ const screenshot = await testPageScreenshot({
+ selector: '[data-visual-test="timeline-skeleton"] .dnb-timeline',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+
+ it('have to match Timeline item skeleton', async () => {
+ const screenshot = await testPageScreenshot({
+ selector:
+ '[data-visual-test="timeline-item-skeleton"] .dnb-timeline',
+ })
+ expect(screenshot).toMatchImageSnapshot()
+ })
+})
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/Timeline.test.tsx b/packages/dnb-eufemia/src/components/timeline/__tests__/Timeline.test.tsx
new file mode 100644
index 00000000000..2bbea622b62
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/timeline/__tests__/Timeline.test.tsx
@@ -0,0 +1,213 @@
+import React from 'react'
+import { render, screen } from '@testing-library/react'
+import Timeline, { TimelineItem } from '../Timeline'
+
+import IconPrimary from '../../icon-primary/IconPrimary'
+import { loadScss, axeComponent } from '../../../core/jest/jestSetup'
+
+describe('Timeline', () => {
+ it('renders without properties', () => {
+ render( )
+
+ expect(screen.queryByTestId('timeline')).not.toBeNull()
+ })
+
+ it('renders a timeline with multiple items by data prop', () => {
+ render(
+
+ )
+
+ expect(screen.queryAllByTestId('timeline-item')).toHaveLength(3)
+ })
+
+ it('renders a timeline with multiple items by children', () => {
+ render(
+
+
+
+
+
+ )
+
+ expect(screen.queryAllByTestId('timeline-item')).toHaveLength(3)
+ })
+
+ it('current will have aria-current="step', () => {
+ render(
+
+ )
+
+ const lastElem = screen.getAllByTestId('timeline-item').slice(-1)[0]
+ expect(lastElem.getAttribute('aria-current')).toBe('step')
+ })
+
+ describe('TimelineItem', () => {
+ it('renders name', () => {
+ const name = 'Completed'
+ render( )
+ expect(
+ screen.queryByTestId('timeline-item-label-name').textContent
+ ).toBe(name)
+ })
+
+ it('renders date', () => {
+ const date = '10. september 2021'
+ render(
+
+ )
+ expect(
+ screen.queryByTestId('timeline-item-content-date').textContent
+ ).toBe(date)
+ })
+
+ it('renders info message', () => {
+ const infoMessage = 'Info text'
+ render(
+
+ )
+ expect(
+ screen.queryByTestId('timeline-item-content-info').textContent
+ ).toBe(infoMessage)
+ })
+
+ it('renders custom icon', () => {
+ const CustomIcon = (
+
+ )
+ render(
+
+ )
+
+ const element = screen.queryByTestId('timeline-item-custom-icon')
+ expect(element).not.toBeNull()
+ expect(element.getAttribute('data-test-id')).toBe('bell icon')
+ })
+
+ it('renders custom alt label', () => {
+ const iconAlt = 'custom_alt_label'
+ render(
+
+ )
+
+ expect(screen.findByAltText('custom_alt_label')).not.toBeNull()
+ expect(screen.queryByRole('img').getAttribute('alt')).toBe(iconAlt)
+ })
+
+ describe('renders default icon based on state property', () => {
+ it('renders check icon when state is completed', () => {
+ render( )
+ expect(screen.queryByRole('img').getAttribute('aria-label')).toBe(
+ 'check icon'
+ )
+ })
+
+ it('renders pin icon when state is current', () => {
+ render( )
+ expect(screen.queryByRole('img').getAttribute('aria-label')).toBe(
+ 'pin icon'
+ )
+ })
+
+ it('renders calendar icon when state is upcoming', () => {
+ render( )
+ expect(screen.queryByRole('img').getAttribute('aria-label')).toBe(
+ 'calendar icon'
+ )
+ })
+
+ it('renders alt label Utført when state is completed', () => {
+ render( )
+ expect(screen.queryByRole('img').getAttribute('alt')).toBe(
+ 'Utført'
+ )
+ })
+
+ it('renders alt label Nåværende when state is current', () => {
+ render( )
+ expect(screen.queryByRole('img').getAttribute('alt')).toBe(
+ 'Nåværende'
+ )
+ })
+
+ it('renders alt label Kommende when state is upcoming', () => {
+ render( )
+ expect(screen.queryByRole('img').getAttribute('alt')).toBe(
+ 'Kommende'
+ )
+ })
+ })
+ })
+})
+
+describe('Timeline aria', () => {
+ it('should validate', async () => {
+ const Component = render(
+
+ )
+ expect(await axeComponent(Component)).toHaveNoViolations()
+ })
+})
+
+describe('Timeline scss', () => {
+ it('have to match snapshot', () => {
+ const scss = loadScss(require.resolve('../style/dnb-timeline.scss'))
+ expect(scss).toMatchSnapshot()
+ })
+ it('have to match default theme snapshot', () => {
+ const scss = loadScss(
+ require.resolve('../style/themes/dnb-timeline-theme-ui.scss')
+ )
+ expect(scss).toMatchSnapshot()
+ })
+})
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/Timeline.test.tsx.snap b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/Timeline.test.tsx.snap
new file mode 100644
index 00000000000..dc00ea79c40
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/Timeline.test.tsx.snap
@@ -0,0 +1,324 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Timeline scss have to match default theme snapshot 1`] = `
+"/*
+* Timeline theme
+*
+*/
+/**
+ * This file is only used to make themes independent
+ * so that they can get imported individually, without the core styles
+ *
+ */
+/*
+ * Utilities
+ */
+/*
+* Button mixins
+*
+*/
+:root {
+ --timeline-icon-height--small: var(--button-height--small);
+ --timeline-icon-width--small: var(--button-width--small);
+ --timeline-icon-border-radius--small: calc(
+ var(--timeline-icon-height--small) / 2
+ );
+ --timeline-icon-height--medium: var(--button-height--medium);
+ --timeline-icon-width--medium: var(--button-width--medium);
+ --timeline-icon-border-radius--medium: calc(
+ var(--timeline-icon-height--medium) / 2
+ );
+ --timeline-icon-width-diff: calc(
+ (
+ var(--timeline-icon-width--medium) -
+ var(--timeline-icon-width--small)
+ ) / 2
+ );
+ --timeline-border-spacing: var(--spacing-small);
+ --timeline-border-spacing--icon-adjusted: calc(
+ var(--timeline-icon-width-diff) + var(--timeline-border-spacing)
+ ); }
+
+.dnb-timeline__item {
+ margin-left: var(--timeline-icon-width-diff); }
+ .dnb-timeline__item__label__icon {
+ width: var(--timeline-icon-width--small);
+ line-height: var(--timeline-icon-height--small);
+ border-radius: var(--timeline-icon-border-radius--small);
+ color: var(--color-black-80);
+ background-color: var(--color-white);
+ --border-color: var(--color-black-80);
+ box-shadow: inset 0 0 0 0.0625rem var(--border-color);
+ /* iOS fix - because \\"inset\\" works not fine with border-radius */
+ /* Safari fix - because \\"inset\\" works not fine with border-radius if the user zooms the page */
+ border-color: transparent; }
+ @supports (-webkit-touch-callout: none) {
+ .dnb-timeline__item__label__icon {
+ box-shadow: 0 0 0 0.0625rem var(--border-color); } }
+ @media not all and (min-resolution: 0.001dpcm) {
+ @supports (-webkit-appearance: none) and (stroke-color: transparent) and (not (-webkit-overflow-scrolling: touch)) {
+ .dnb-timeline__item__label__icon {
+ box-shadow: 0 0 0 0.0625rem var(--border-color); } } }
+ @media screen and (-ms-high-contrast: none) {
+ .dnb-timeline__item__label__icon {
+ box-shadow: inset 0 0 0 1px var(--color-black-80); } }
+ .dnb-timeline__item__label__name {
+ margin-left: var(--timeline-border-spacing--icon-adjusted);
+ font-size: var(--font-size-small);
+ line-height: var(--line-height-small); }
+ .dnb-timeline__item__content {
+ margin-left: calc(var(--timeline-icon-width--small) / 2);
+ padding-left: calc(var(--timeline-icon-width--small) / 2 + var(--timeline-border-spacing--icon-adjusted));
+ border-left: 1px dashed var(--color-black-55); }
+ .dnb-timeline__item--completed .dnb-timeline__item__content {
+ border-left: 1px solid var(--color-black-80); }
+ .dnb-timeline__item--completed .dnb-timeline__item__label__name {
+ color: var(--color-black-80); }
+ .dnb-timeline__item--current .dnb-timeline__item__content {
+ margin-left: calc(var(--timeline-icon-width--medium) / 2);
+ padding-left: calc(var(--timeline-icon-width--medium) / 2 + var(--timeline-border-spacing)); }
+ .dnb-timeline__item--current .dnb-timeline__item__label__name {
+ margin-left: var(--timeline-border-spacing);
+ font-weight: var(--font-weight-medium);
+ font-size: var(--font-size-basis);
+ line-height: var(--line-height-basis); }
+ .dnb-timeline__item--current .dnb-timeline__item__label__icon {
+ width: var(--timeline-icon-width--medium);
+ line-height: var(--timeline-icon-height--medium);
+ border-radius: var(--timeline-icon-border-radius--medium); }
+ .dnb-timeline__item--current {
+ margin-left: 0; }
+ .dnb-timeline__item--upcoming .dnb-timeline__item__label__name {
+ font-weight: var(--font-weight-basis);
+ color: var(--color-black-55); }
+ .dnb-timeline__item--upcoming:not(.dnb-skeleton) .dnb-timeline__item__label__icon {
+ color: var(--color-black-55);
+ background-color: var(--color-black-3);
+ --border-color: var(--color-black-3);
+ box-shadow: inset 0 0 0 0.0625rem var(--border-color);
+ /* iOS fix - because \\"inset\\" works not fine with border-radius */
+ /* Safari fix - because \\"inset\\" works not fine with border-radius if the user zooms the page */
+ border-color: transparent; }
+ @supports (-webkit-touch-callout: none) {
+ .dnb-timeline__item--upcoming:not(.dnb-skeleton) .dnb-timeline__item__label__icon {
+ box-shadow: 0 0 0 0.0625rem var(--border-color); } }
+ @media not all and (min-resolution: 0.001dpcm) {
+ @supports (-webkit-appearance: none) and (stroke-color: transparent) and (not (-webkit-overflow-scrolling: touch)) {
+ .dnb-timeline__item--upcoming:not(.dnb-skeleton) .dnb-timeline__item__label__icon {
+ box-shadow: 0 0 0 0.0625rem var(--border-color); } } }
+ @media screen and (-ms-high-contrast: none) {
+ .dnb-timeline__item--upcoming:not(.dnb-skeleton) .dnb-timeline__item__label__icon {
+ box-shadow: inset 0 0 0 1px var(--color-black-3); } }
+ .dnb-timeline__item:only-child {
+ margin-left: 0; }
+ .dnb-timeline__item:last-child .dnb-timeline__item__content {
+ border-left: none; }
+"
+`;
+
+exports[`Timeline scss have to match snapshot 1`] = `
+"/*
+* DNB Timeline
+*
+*/
+/**
+ * This file is only used to make components independent
+ * so that they can get imported individually, without the core styles
+ *
+ */
+/*
+ * Utilities
+ */
+/*
+ * Scopes
+ *
+ */
+/*
+ * Document Reset
+ *
+ */
+.dnb-timeline {
+ font-family: var(--font-family-default);
+ font-weight: var(--font-weight-basis);
+ font-size: var(--font-size-small);
+ font-style: normal;
+ line-height: var(--line-height-basis);
+ color: var(--color-black-80, #333);
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-font-smoothing: antialiased;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+ /**
+ * Ensure consistency and use the same as HTML reset -> html {...}
+ * between base and code package
+ */
+ -moz-tab-size: 4;
+ tab-size: 4;
+ -ms-text-size-adjust: 100%;
+ -webkit-text-size-adjust: 100%;
+ word-break: break-word;
+ /**
+ * 1. Remove repeating backgrounds in all browsers (opinionated).
+ * 2. Add border box sizing in all browsers (opinionated).
+ */
+ /**
+ * 1. Add text decoration inheritance in all browsers (opinionated).
+ * 2. Add vertical alignment inheritance in all browsers (opinionated).
+ */
+ margin: 0;
+ padding: 0; }
+ .dnb-timeline *,
+ .dnb-timeline ::before,
+ .dnb-timeline ::after {
+ background-repeat: no-repeat;
+ /* 1 */
+ box-sizing: border-box;
+ /* 2 */ }
+ .dnb-timeline ::before,
+ .dnb-timeline ::after {
+ text-decoration: inherit;
+ /* 1 */
+ vertical-align: inherit;
+ /* 2 */ }
+
+/*
+* Timeline component
+*
+*/
+/*
+* Timeline theme
+*
+*/
+/**
+ * This file is only used to make themes independent
+ * so that they can get imported individually, without the core styles
+ *
+ */
+/*
+* Button mixins
+*
+*/
+:root {
+ --timeline-icon-height--small: var(--button-height--small);
+ --timeline-icon-width--small: var(--button-width--small);
+ --timeline-icon-border-radius--small: calc(
+ var(--timeline-icon-height--small) / 2
+ );
+ --timeline-icon-height--medium: var(--button-height--medium);
+ --timeline-icon-width--medium: var(--button-width--medium);
+ --timeline-icon-border-radius--medium: calc(
+ var(--timeline-icon-height--medium) / 2
+ );
+ --timeline-icon-width-diff: calc(
+ (
+ var(--timeline-icon-width--medium) -
+ var(--timeline-icon-width--small)
+ ) / 2
+ );
+ --timeline-border-spacing: var(--spacing-small);
+ --timeline-border-spacing--icon-adjusted: calc(
+ var(--timeline-icon-width-diff) + var(--timeline-border-spacing)
+ ); }
+
+.dnb-timeline__item {
+ margin-left: var(--timeline-icon-width-diff); }
+ .dnb-timeline__item__label__icon {
+ width: var(--timeline-icon-width--small);
+ line-height: var(--timeline-icon-height--small);
+ border-radius: var(--timeline-icon-border-radius--small);
+ color: var(--color-black-80);
+ background-color: var(--color-white);
+ --border-color: var(--color-black-80);
+ box-shadow: inset 0 0 0 0.0625rem var(--border-color);
+ /* iOS fix - because \\"inset\\" works not fine with border-radius */
+ /* Safari fix - because \\"inset\\" works not fine with border-radius if the user zooms the page */
+ border-color: transparent; }
+ @supports (-webkit-touch-callout: none) {
+ .dnb-timeline__item__label__icon {
+ box-shadow: 0 0 0 0.0625rem var(--border-color); } }
+ @media not all and (min-resolution: 0.001dpcm) {
+ @supports (-webkit-appearance: none) and (stroke-color: transparent) and (not (-webkit-overflow-scrolling: touch)) {
+ .dnb-timeline__item__label__icon {
+ box-shadow: 0 0 0 0.0625rem var(--border-color); } } }
+ @media screen and (-ms-high-contrast: none) {
+ .dnb-timeline__item__label__icon {
+ box-shadow: inset 0 0 0 1px var(--color-black-80); } }
+ .dnb-timeline__item__label__name {
+ margin-left: var(--timeline-border-spacing--icon-adjusted);
+ font-size: var(--font-size-small);
+ line-height: var(--line-height-small); }
+ .dnb-timeline__item__content {
+ margin-left: calc(var(--timeline-icon-width--small) / 2);
+ padding-left: calc(var(--timeline-icon-width--small) / 2 + var(--timeline-border-spacing--icon-adjusted));
+ border-left: 1px dashed var(--color-black-55); }
+ .dnb-timeline__item--completed .dnb-timeline__item__content {
+ border-left: 1px solid var(--color-black-80); }
+ .dnb-timeline__item--completed .dnb-timeline__item__label__name {
+ color: var(--color-black-80); }
+ .dnb-timeline__item--current .dnb-timeline__item__content {
+ margin-left: calc(var(--timeline-icon-width--medium) / 2);
+ padding-left: calc(var(--timeline-icon-width--medium) / 2 + var(--timeline-border-spacing)); }
+ .dnb-timeline__item--current .dnb-timeline__item__label__name {
+ margin-left: var(--timeline-border-spacing);
+ font-weight: var(--font-weight-medium);
+ font-size: var(--font-size-basis);
+ line-height: var(--line-height-basis); }
+ .dnb-timeline__item--current .dnb-timeline__item__label__icon {
+ width: var(--timeline-icon-width--medium);
+ line-height: var(--timeline-icon-height--medium);
+ border-radius: var(--timeline-icon-border-radius--medium); }
+ .dnb-timeline__item--current {
+ margin-left: 0; }
+ .dnb-timeline__item--upcoming .dnb-timeline__item__label__name {
+ font-weight: var(--font-weight-basis);
+ color: var(--color-black-55); }
+ .dnb-timeline__item--upcoming:not(.dnb-skeleton) .dnb-timeline__item__label__icon {
+ color: var(--color-black-55);
+ background-color: var(--color-black-3);
+ --border-color: var(--color-black-3);
+ box-shadow: inset 0 0 0 0.0625rem var(--border-color);
+ /* iOS fix - because \\"inset\\" works not fine with border-radius */
+ /* Safari fix - because \\"inset\\" works not fine with border-radius if the user zooms the page */
+ border-color: transparent; }
+ @supports (-webkit-touch-callout: none) {
+ .dnb-timeline__item--upcoming:not(.dnb-skeleton) .dnb-timeline__item__label__icon {
+ box-shadow: 0 0 0 0.0625rem var(--border-color); } }
+ @media not all and (min-resolution: 0.001dpcm) {
+ @supports (-webkit-appearance: none) and (stroke-color: transparent) and (not (-webkit-overflow-scrolling: touch)) {
+ .dnb-timeline__item--upcoming:not(.dnb-skeleton) .dnb-timeline__item__label__icon {
+ box-shadow: 0 0 0 0.0625rem var(--border-color); } } }
+ @media screen and (-ms-high-contrast: none) {
+ .dnb-timeline__item--upcoming:not(.dnb-skeleton) .dnb-timeline__item__label__icon {
+ box-shadow: inset 0 0 0 1px var(--color-black-3); } }
+ .dnb-timeline__item:only-child {
+ margin-left: 0; }
+ .dnb-timeline__item:last-child .dnb-timeline__item__content {
+ border-left: none; }
+
+.dnb-timeline {
+ display: flex;
+ flex-direction: column; }
+ .dnb-timeline__item__label {
+ display: flex;
+ align-items: center;
+ text-align: left;
+ padding: 0; }
+ .dnb-timeline__item__label__icon {
+ display: flex;
+ flex-shrink: 0;
+ align-items: center;
+ height: auto;
+ justify-content: center;
+ padding: 0; }
+ .dnb-timeline__item__label__name {
+ cursor: text; }
+ .dnb-timeline__item__content {
+ padding-bottom: var(--spacing-small); }
+ .dnb-timeline__item__content__date {
+ cursor: text;
+ font-size: var(--font-size-x-small);
+ font-weight: var(--font-weight-basis);
+ color: var(--color-black-55); }
+ .dnb-timeline__item__content__info {
+ padding-top: var(--spacing-x-small); }
+"
+`;
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-icons-1-e1616.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-icons-1-e1616.snap.png
new file mode 100644
index 00000000000..d98f8033cdc
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-icons-1-e1616.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-icons-2-3e8d7.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-icons-2-3e8d7.snap.png
new file mode 100644
index 00000000000..d98f8033cdc
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-icons-2-3e8d7.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-item-skeleton-1-7e15f.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-item-skeleton-1-7e15f.snap.png
new file mode 100644
index 00000000000..c3c9a93337f
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-item-skeleton-1-7e15f.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-multiple-1-7b364.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-multiple-1-7b364.snap.png
new file mode 100644
index 00000000000..578b2f170bb
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-multiple-1-7b364.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-multiple-with-children-1-b5a74.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-multiple-with-children-1-b5a74.snap.png
new file mode 100644
index 00000000000..578b2f170bb
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-multiple-with-children-1-b5a74.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-single-1-dc5be.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-single-1-dc5be.snap.png
new file mode 100644
index 00000000000..e3cfbc3b4d2
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-single-1-dc5be.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-single-completed-1-adc72.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-single-completed-1-adc72.snap.png
new file mode 100644
index 00000000000..73cdf307d87
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-single-completed-1-adc72.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-single-current-1-ba668.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-single-current-1-ba668.snap.png
new file mode 100644
index 00000000000..b992582c1b1
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-single-current-1-ba668.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-single-upcoming-1-b5850.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-single-upcoming-1-b5850.snap.png
new file mode 100644
index 00000000000..1ab4dc38ece
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-single-upcoming-1-b5850.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-skeleton-1-5cfaa.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-skeleton-1-5cfaa.snap.png
new file mode 100644
index 00000000000..182c448b3a6
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-skeleton-1-5cfaa.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-states-1-e31a0.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-states-1-e31a0.snap.png
new file mode 100644
index 00000000000..9e23db4581f
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-states-1-e31a0.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-with-multiple-completed-time-ine-items-1-b1617.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-with-multiple-completed-time-ine-items-1-b1617.snap.png
new file mode 100644
index 00000000000..d3a5d8bb4ca
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-with-multiple-completed-time-ine-items-1-b1617.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-with-multiple-current-timeline-items-1-22d5d.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-with-multiple-current-timeline-items-1-22d5d.snap.png
new file mode 100644
index 00000000000..fb42e7a9d8e
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-with-multiple-current-timeline-items-1-22d5d.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-with-multiple-upcoming-timeline-items-1-5b819.snap.png b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-with-multiple-upcoming-timeline-items-1-5b819.snap.png
new file mode 100644
index 00000000000..5a07de99998
Binary files /dev/null and b/packages/dnb-eufemia/src/components/timeline/__tests__/__snapshots__/timeline-screenshot-test-js-timeline-screenshot-have-to-match-timeline-with-multiple-upcoming-timeline-items-1-5b819.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/timeline/index.js b/packages/dnb-eufemia/src/components/timeline/index.js
new file mode 100644
index 00000000000..e0dd79317a3
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/timeline/index.js
@@ -0,0 +1,8 @@
+/**
+ * Component Entry
+ *
+ */
+
+import Timeline from './Timeline'
+export default Timeline
+export * from './Timeline'
diff --git a/packages/dnb-eufemia/src/components/timeline/style.js b/packages/dnb-eufemia/src/components/timeline/style.js
new file mode 100644
index 00000000000..7f86160181b
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/timeline/style.js
@@ -0,0 +1,6 @@
+/**
+ * Web Style Import
+ *
+ */
+
+import './style/dnb-timeline.scss'
diff --git a/packages/dnb-eufemia/src/components/timeline/style/_timeline.scss b/packages/dnb-eufemia/src/components/timeline/style/_timeline.scss
new file mode 100644
index 00000000000..ffedbec41d9
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/timeline/style/_timeline.scss
@@ -0,0 +1,42 @@
+/*
+* Timeline component
+*
+*/
+
+@import './themes/dnb-timeline-theme-ui.scss';
+
+.dnb-timeline {
+ display: flex;
+ flex-direction: column;
+ &__item {
+ &__label {
+ display: flex;
+ align-items: center;
+ text-align: left;
+ padding: 0;
+ &__icon {
+ display: flex;
+ flex-shrink: 0;
+ align-items: center;
+ height: auto;
+ justify-content: center;
+ padding: 0;
+ }
+ &__name {
+ cursor: text;
+ }
+ }
+ &__content {
+ padding-bottom: var(--spacing-small);
+ &__date {
+ cursor: text;
+ font-size: var(--font-size-x-small);
+ font-weight: var(--font-weight-basis);
+ color: var(--color-black-55);
+ }
+ &__info {
+ padding-top: var(--spacing-x-small);
+ }
+ }
+ }
+}
diff --git a/packages/dnb-eufemia/src/components/timeline/style/dnb-timeline.scss b/packages/dnb-eufemia/src/components/timeline/style/dnb-timeline.scss
new file mode 100644
index 00000000000..94214d0288e
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/timeline/style/dnb-timeline.scss
@@ -0,0 +1,12 @@
+/*
+* DNB Timeline
+*
+*/
+
+@import '../../../style/components/imports.scss';
+
+.dnb-timeline {
+ @include componentReset();
+}
+
+@import './_timeline.scss';
diff --git a/packages/dnb-eufemia/src/components/timeline/style/index.js b/packages/dnb-eufemia/src/components/timeline/style/index.js
new file mode 100644
index 00000000000..0da91c01402
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/timeline/style/index.js
@@ -0,0 +1,6 @@
+/**
+ * Web Style Import
+ *
+ */
+
+import './dnb-timeline.scss'
diff --git a/packages/dnb-eufemia/src/components/timeline/style/themes/dnb-timeline-theme-ui.scss b/packages/dnb-eufemia/src/components/timeline/style/themes/dnb-timeline-theme-ui.scss
new file mode 100644
index 00000000000..3995e676906
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/timeline/style/themes/dnb-timeline-theme-ui.scss
@@ -0,0 +1,105 @@
+/*
+* Timeline theme
+*
+*/
+
+@import '../../../../style/themes/imports.scss';
+@import '../../../button/style/themes/_button-mixins.scss';
+
+:root {
+ --timeline-icon-height--small: var(--button-height--small);
+ --timeline-icon-width--small: var(--button-width--small);
+ --timeline-icon-border-radius--small: calc(
+ var(--timeline-icon-height--small) / 2
+ );
+ --timeline-icon-height--medium: var(--button-height--medium);
+ --timeline-icon-width--medium: var(--button-width--medium);
+ --timeline-icon-border-radius--medium: calc(
+ var(--timeline-icon-height--medium) / 2
+ );
+ --timeline-icon-width-diff: calc(
+ (
+ var(--timeline-icon-width--medium) -
+ var(--timeline-icon-width--small)
+ ) / 2
+ );
+ --timeline-border-spacing: var(--spacing-small);
+ --timeline-border-spacing--icon-adjusted: calc(
+ var(--timeline-icon-width-diff) + var(--timeline-border-spacing)
+ );
+}
+
+@mixin centerBorder(
+ $width: var(--timeline-icon-width--medium),
+ $spacing: var(--timeline-border-spacing--icon-adjusted)
+) {
+ margin-left: calc(#{$width} / 2);
+ padding-left: calc(#{$width} / 2 + #{$spacing});
+}
+
+.dnb-timeline {
+ &__item {
+ margin-left: var(--timeline-icon-width-diff);
+ &__label {
+ &__icon {
+ width: var(--timeline-icon-width--small);
+ line-height: var(--timeline-icon-height--small);
+ border-radius: var(--timeline-icon-border-radius--small);
+ color: var(--color-black-80);
+ background-color: var(--color-white);
+ @include fakeBorder(var(--color-black-80), 0.0625rem, inset, true);
+ }
+ &__name {
+ margin-left: var(--timeline-border-spacing--icon-adjusted);
+ font-size: var(--font-size-small);
+ line-height: var(--line-height-small);
+ }
+ }
+ &__content {
+ @include centerBorder(var(--timeline-icon-width--small));
+ border-left: 1px dashed var(--color-black-55);
+ }
+
+ &--completed &__content {
+ border-left: 1px solid var(--color-black-80);
+ }
+ &--completed &__label__name {
+ color: var(--color-black-80);
+ }
+ &--current &__content {
+ @include centerBorder(
+ var(--timeline-icon-width--medium),
+ var(--timeline-border-spacing)
+ );
+ }
+ &--current &__label__name {
+ margin-left: var(--timeline-border-spacing);
+ font-weight: var(--font-weight-medium);
+ font-size: var(--font-size-basis);
+ line-height: var(--line-height-basis);
+ }
+ &--current &__label__icon {
+ width: var(--timeline-icon-width--medium);
+ line-height: var(--timeline-icon-height--medium);
+ border-radius: var(--timeline-icon-border-radius--medium);
+ }
+ &--current {
+ margin-left: 0;
+ }
+ &--upcoming &__label__name {
+ font-weight: var(--font-weight-basis);
+ color: var(--color-black-55);
+ }
+ &--upcoming:not(.dnb-skeleton) &__label__icon {
+ color: var(--color-black-55);
+ background-color: var(--color-black-3);
+ @include fakeBorder(var(--color-black-3), 0.0625rem, inset, true);
+ }
+ &:only-child {
+ margin-left: 0;
+ }
+ &:last-child &__content {
+ border-left: none;
+ }
+ }
+}
diff --git a/packages/dnb-eufemia/src/components/timeline/style/themes/ui.js b/packages/dnb-eufemia/src/components/timeline/style/themes/ui.js
new file mode 100644
index 00000000000..6dfa1baad50
--- /dev/null
+++ b/packages/dnb-eufemia/src/components/timeline/style/themes/ui.js
@@ -0,0 +1,6 @@
+/**
+ * Imports the default theme
+ *
+ */
+
+import './dnb-timeline-theme-ui.scss'
diff --git a/packages/dnb-eufemia/src/elements/Img.d.ts b/packages/dnb-eufemia/src/elements/Img.d.ts
new file mode 100644
index 00000000000..1a82eebedd7
--- /dev/null
+++ b/packages/dnb-eufemia/src/elements/Img.d.ts
@@ -0,0 +1,39 @@
+import * as React from 'react';
+export type ImgSpace =
+ | string
+ | number
+ | boolean
+ | {
+ top?: string | number | boolean;
+ right?: string | number | boolean;
+ bottom?: string | number | boolean;
+ left?: string | number | boolean;
+ };
+export type ImgTop = string | number | boolean;
+export type ImgRight = string | number | boolean;
+export type ImgBottom = string | number | boolean;
+export type ImgLeft = string | number | boolean;
+export type ImgSkeleton = string | boolean;
+export type ImgClassName = string | any | any[];
+
+/**
+ * NB: Do not change the docs (comments) in here. The docs are updated during build time by "generateTypes.js" and "fetchPropertiesFromDocs.js".
+ */
+export interface ImgProps extends React.HTMLProps {
+ space?: ImgSpace;
+ top?: ImgTop;
+ right?: ImgRight;
+ bottom?: ImgBottom;
+ left?: ImgLeft;
+ src: string;
+ alt: string;
+ skeleton?: ImgSkeleton;
+ className?: ImgClassName;
+ class?: string;
+ img_class?: string;
+ element?: React.ReactNode;
+ caption?: string;
+ children?: React.ReactNode;
+}
+declare const Img: React.FC;
+export default Img;
diff --git a/packages/dnb-eufemia/src/elements/__tests__/__snapshots__/table-screenshot-test-js-table-screenshot-have-to-match-the-default-choice-of-table-styles-1-b0b7d.snap.png b/packages/dnb-eufemia/src/elements/__tests__/__snapshots__/table-screenshot-test-js-table-screenshot-have-to-match-the-default-choice-of-table-styles-1-b0b7d.snap.png
index 256a15e8435..504e96705d1 100644
Binary files a/packages/dnb-eufemia/src/elements/__tests__/__snapshots__/table-screenshot-test-js-table-screenshot-have-to-match-the-default-choice-of-table-styles-1-b0b7d.snap.png and b/packages/dnb-eufemia/src/elements/__tests__/__snapshots__/table-screenshot-test-js-table-screenshot-have-to-match-the-default-choice-of-table-styles-1-b0b7d.snap.png differ
diff --git a/packages/dnb-eufemia/src/extensions/payment-card/__tests__/__snapshots__/PaymentCard.test.js.snap b/packages/dnb-eufemia/src/extensions/payment-card/__tests__/__snapshots__/PaymentCard.test.js.snap
index cb232e976d6..9fdbd1f061c 100644
--- a/packages/dnb-eufemia/src/extensions/payment-card/__tests__/__snapshots__/PaymentCard.test.js.snap
+++ b/packages/dnb-eufemia/src/extensions/payment-card/__tests__/__snapshots__/PaymentCard.test.js.snap
@@ -437,7 +437,6 @@ exports[`PaymentCard scss have to match snapshot 1`] = `
*
*/
:root {
- --dnb-payment-shadow-figma: 0 8px 16px rgba(51, 51, 51, 0.08);
--dnb-payment-bg-default: linear-gradient(
184.55deg,
var(--color-sea-green) 6.31%,
diff --git a/packages/dnb-eufemia/src/extensions/payment-card/style/_payment-card.scss b/packages/dnb-eufemia/src/extensions/payment-card/style/_payment-card.scss
index 6f6bbc49c34..b7ed2921b19 100644
--- a/packages/dnb-eufemia/src/extensions/payment-card/style/_payment-card.scss
+++ b/packages/dnb-eufemia/src/extensions/payment-card/style/_payment-card.scss
@@ -4,7 +4,6 @@
*/
:root {
- --dnb-payment-shadow-figma: 0 8px 16px rgba(51, 51, 51, 0.08);
--dnb-payment-bg-default: linear-gradient(
184.55deg,
var(--color-sea-green) 6.31%,
diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/DrawerListProvider.js b/packages/dnb-eufemia/src/fragments/drawer-list/DrawerListProvider.js
index 3b16131cebe..cca184bb6ac 100644
--- a/packages/dnb-eufemia/src/fragments/drawer-list/DrawerListProvider.js
+++ b/packages/dnb-eufemia/src/fragments/drawer-list/DrawerListProvider.js
@@ -127,6 +127,7 @@ export default class DrawerListProvider extends React.PureComponent {
clearTimeout(this._scrollTimeout)
clearTimeout(this._ddTimeout)
+ this.setActiveState(false)
this.removeObservers()
}
diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/DrawerList.test.js b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/DrawerList.test.js
index 030fe473e0f..40f12cbee67 100644
--- a/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/DrawerList.test.js
+++ b/packages/dnb-eufemia/src/fragments/drawer-list/__tests__/DrawerList.test.js
@@ -258,6 +258,29 @@ describe('DrawerList component', () => {
).toBe(false)
})
+ it('will unset data-dnb-drawer-list-active on unmount', () => {
+ const Comp = mount(
+ ,
+ {
+ attachTo: attachToBody(),
+ }
+ )
+
+ Comp.setProps({
+ opened: true,
+ })
+
+ expect(
+ document.documentElement.getAttribute('data-dnb-drawer-list-active')
+ ).toBe(props.id)
+
+ Comp.unmount()
+
+ expect(
+ document.documentElement.hasAttribute('data-dnb-drawer-list-active')
+ ).toBe(false)
+ })
+
it('has valid on_change callback', () => {
const on_change = jest.fn()
const on_select = jest.fn()
diff --git a/packages/dnb-eufemia/src/index.js b/packages/dnb-eufemia/src/index.js
index 301802f9b2f..5244c86e2fb 100644
--- a/packages/dnb-eufemia/src/index.js
+++ b/packages/dnb-eufemia/src/index.js
@@ -12,6 +12,7 @@
// import all the available components and extensions
import Accordion from './components/accordion/Accordion'
import Autocomplete from './components/autocomplete/Autocomplete'
+import Avatar from './components/avatar/Avatar'
import Breadcrumb from './components/breadcrumb/Breadcrumb'
import Button from './components/button/Button'
import Checkbox from './components/checkbox/Checkbox'
@@ -27,6 +28,7 @@ import Heading from './components/heading/Heading'
import HelpButton from './components/help-button/HelpButton'
import Icon from './components/icon/Icon'
import IconPrimary from './components/icon-primary/IconPrimary'
+import InfoCard from './components/info-card/InfoCard'
import Input from './components/input/Input'
import InputMasked from './components/input-masked/InputMasked'
import Logo from './components/logo/Logo'
@@ -44,6 +46,7 @@ import Switch from './components/switch/Switch'
import Tabs from './components/tabs/Tabs'
import Tag from './components/tag/Tag'
import Textarea from './components/textarea/Textarea'
+import Timeline from './components/timeline/Timeline'
import ToggleButton from './components/toggle-button/ToggleButton'
import Tooltip from './components/tooltip/Tooltip'
import Anchor from './elements/Anchor'
@@ -80,6 +83,7 @@ import Ul from './elements/Ul'
export {
Accordion,
Autocomplete,
+ Avatar,
Breadcrumb,
Button,
Checkbox,
@@ -95,6 +99,7 @@ export {
HelpButton,
Icon,
IconPrimary,
+ InfoCard,
Input,
InputMasked,
Logo,
@@ -112,6 +117,7 @@ export {
Tabs,
Tag,
Textarea,
+ Timeline,
ToggleButton,
Tooltip,
Anchor,
diff --git a/packages/dnb-eufemia/src/shared/Context.js b/packages/dnb-eufemia/src/shared/Context.js
index 3fb7f7907e5..ac45e2002aa 100644
--- a/packages/dnb-eufemia/src/shared/Context.js
+++ b/packages/dnb-eufemia/src/shared/Context.js
@@ -46,12 +46,18 @@ export const prepareContext = (props = {}) => {
return context.translation
},
locales,
- ...props,
- translation, // make sure we set this after props, since we update this one!
// All eufemia components because of Typescript:
+ Avatar: {},
+ AvatarGroup: {},
Breadcrumb: {},
BreadcrumbItem: {},
+ InfoCard: {},
Tag: {},
+ TagGroup: {},
+ Timeline: {},
+ TimelineItem: {},
+ ...props,
+ translation, // make sure we set this after props, since we update this one!
}
return context
diff --git a/packages/dnb-eufemia/src/shared/locales/en-GB.js b/packages/dnb-eufemia/src/shared/locales/en-GB.js
index 974d8c32e50..305bd4ddec5 100644
--- a/packages/dnb-eufemia/src/shared/locales/en-GB.js
+++ b/packages/dnb-eufemia/src/shared/locales/en-GB.js
@@ -1,5 +1,10 @@
export default {
'en-GB': {
+ TimelineItem: {
+ alt_label_completed: 'Complete',
+ alt_label_current: 'Current',
+ alt_label_upcoming: 'Upcoming',
+ },
Breadcrumb: {
navText: 'Page hierarchy',
goBackText: 'Back',
@@ -39,14 +44,12 @@ export default {
GlobalError: {
404: {
title: "Oops! We can't find the page you're looking for …",
- text:
- 'Did we messed with the links? Try again, or [go back where you came from](/back).',
+ text: 'Did we messed with the links? Try again, or [go back where you came from](/back).',
alt: 'Lady searching in empty box',
},
500: {
title: 'Ohh, a technical error happened!',
- text:
- 'The service is not working properly at the moment, but try again later.',
+ text: 'The service is not working properly at the moment, but try again later.',
alt: 'Man looking for clues',
},
},
diff --git a/packages/dnb-eufemia/src/shared/locales/nb-NO.js b/packages/dnb-eufemia/src/shared/locales/nb-NO.js
index 37b25ff6c7e..646c644d894 100644
--- a/packages/dnb-eufemia/src/shared/locales/nb-NO.js
+++ b/packages/dnb-eufemia/src/shared/locales/nb-NO.js
@@ -1,5 +1,10 @@
export default {
'nb-NO': {
+ TimelineItem: {
+ alt_label_completed: 'Utført',
+ alt_label_current: 'Nåværende',
+ alt_label_upcoming: 'Kommende',
+ },
Breadcrumb: {
navText: 'Sidehierarki',
goBackText: 'Tilbake',
@@ -39,14 +44,12 @@ export default {
GlobalError: {
404: {
title: 'Oisann! Vi finner ikke siden du leter etter …',
- text:
- 'Sikker på at du har skrevet riktig adresse? Eller har vi rotet med lenkene? Prøv på nytt, eller [gå tilbake der du kom fra](/back).',
+ text: 'Sikker på at du har skrevet riktig adresse? Eller har vi rotet med lenkene? Prøv på nytt, eller [gå tilbake der du kom fra](/back).',
alt: 'Dame søker i tom eske',
},
500: {
title: 'Oops, her ble det en teknisk feil!',
- text:
- 'Tjenesten fungerer ikke slik den skal for øyeblikket, men prøv igjen senere.',
+ text: 'Tjenesten fungerer ikke slik den skal for øyeblikket, men prøv igjen senere.',
alt: 'Mann leter etter spor',
},
},
diff --git a/packages/dnb-eufemia/src/style/core/helper-classes/__tests__/__snapshots__/helper-classes-screenshot-test-js-helper-classes-screenshot-have-to-match-core-style-1-5bb35.snap.png b/packages/dnb-eufemia/src/style/core/helper-classes/__tests__/__snapshots__/helper-classes-screenshot-test-js-helper-classes-screenshot-have-to-match-core-style-1-5bb35.snap.png
index 3c64c0ef94f..3442405e94c 100644
Binary files a/packages/dnb-eufemia/src/style/core/helper-classes/__tests__/__snapshots__/helper-classes-screenshot-test-js-helper-classes-screenshot-have-to-match-core-style-1-5bb35.snap.png and b/packages/dnb-eufemia/src/style/core/helper-classes/__tests__/__snapshots__/helper-classes-screenshot-test-js-helper-classes-screenshot-have-to-match-core-style-1-5bb35.snap.png differ
diff --git a/packages/dnb-eufemia/src/style/dnb-ui-components.scss b/packages/dnb-eufemia/src/style/dnb-ui-components.scss
index 35644eb1973..3b0b22089af 100644
--- a/packages/dnb-eufemia/src/style/dnb-ui-components.scss
+++ b/packages/dnb-eufemia/src/style/dnb-ui-components.scss
@@ -8,6 +8,7 @@
@import './dnb-ui-fragments.scss';
@import '../components/accordion/style/_accordion.scss';
@import '../components/autocomplete/style/_autocomplete.scss';
+@import '../components/avatar/style/_avatar.scss';
@import '../components/breadcrumb/style/_breadcrumb.scss';
@import '../components/button/style/_button.scss';
@import '../components/checkbox/style/_checkbox.scss';
@@ -22,6 +23,7 @@
@import '../components/heading/style/_heading.scss';
@import '../components/help-button/style/_help-button.scss';
@import '../components/icon/style/_icon.scss';
+@import '../components/info-card/style/_info-card.scss';
@import '../components/input/style/_input.scss';
@import '../components/input-masked/style/_input-masked.scss';
@import '../components/logo/style/_logo.scss';
@@ -39,5 +41,6 @@
@import '../components/tabs/style/_tabs.scss';
@import '../components/tag/style/_tag.scss';
@import '../components/textarea/style/_textarea.scss';
+@import '../components/timeline/style/_timeline.scss';
@import '../components/toggle-button/style/_toggle-button.scss';
@import '../components/tooltip/style/_tooltip.scss';
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 af506cef2df..a548ae1b7c6 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
@@ -1256,6 +1256,8 @@ thead > tr > th.dnb-table--active .dnb-button:hover:focus .dnb-button__text::aft
margin: -0.25em 0;
padding: 0.25em 0.25em;
/* 2px 4px */
+ overflow: auto;
+ transform: translateY(0.1875rem);
font-size: inherit;
text-decoration: inherit;
line-height: var(--line-height-xx-small--em);
diff --git a/packages/dnb-eufemia/src/style/elements/__tests__/__snapshots__/typography-screenshot-test-js-paragraph-screenshot-have-to-match-the-paragraph-with-small-text-1-eaab6.snap.png b/packages/dnb-eufemia/src/style/elements/__tests__/__snapshots__/typography-screenshot-test-js-paragraph-screenshot-have-to-match-the-paragraph-with-small-text-1-eaab6.snap.png
index b3fc8f95333..dda6018dcfb 100644
Binary files a/packages/dnb-eufemia/src/style/elements/__tests__/__snapshots__/typography-screenshot-test-js-paragraph-screenshot-have-to-match-the-paragraph-with-small-text-1-eaab6.snap.png and b/packages/dnb-eufemia/src/style/elements/__tests__/__snapshots__/typography-screenshot-test-js-paragraph-screenshot-have-to-match-the-paragraph-with-small-text-1-eaab6.snap.png differ
diff --git a/packages/dnb-eufemia/src/style/elements/code.scss b/packages/dnb-eufemia/src/style/elements/code.scss
index 8d21e92b3f7..2d740dadf1d 100644
--- a/packages/dnb-eufemia/src/style/elements/code.scss
+++ b/packages/dnb-eufemia/src/style/elements/code.scss
@@ -8,6 +8,9 @@
margin: -0.25em 0;
padding: 0.25em 0.25em; /* 2px 4px */
+ overflow: auto; // adding auto to overflow, moves the element 3px up
+ transform: translateY(0.1875rem); // therefore we correct it back again
+
font-size: inherit;
text-decoration: inherit;
line-height: var(--line-height-xx-small--em); // 1.5rem - 0.25em - 0.25em
diff --git a/packages/dnb-eufemia/src/style/themes/theme-ui/dnb-theme-ui.scss b/packages/dnb-eufemia/src/style/themes/theme-ui/dnb-theme-ui.scss
index 01186314b0d..be105fc2a05 100644
--- a/packages/dnb-eufemia/src/style/themes/theme-ui/dnb-theme-ui.scss
+++ b/packages/dnb-eufemia/src/style/themes/theme-ui/dnb-theme-ui.scss
@@ -27,6 +27,7 @@
@import '../../../components/accordion/style/themes/dnb-accordion-theme-ui.scss';
@import '../../../components/autocomplete/style/themes/dnb-autocomplete-theme-ui.scss';
+@import '../../../components/avatar/style/themes/dnb-avatar-theme-ui.scss';
@import '../../../components/button/style/themes/dnb-button-theme-ui.scss';
@import '../../../components/checkbox/style/themes/dnb-checkbox-theme-ui.scss';
@import '../../../components/date-picker/style/themes/dnb-date-picker-theme-ui.scss';
@@ -53,6 +54,7 @@
@import '../../../components/switch/style/themes/dnb-switch-theme-ui.scss';
@import '../../../components/tabs/style/themes/dnb-tabs-theme-ui.scss';
@import '../../../components/textarea/style/themes/dnb-textarea-theme-ui.scss';
+@import '../../../components/timeline/style/themes/dnb-timeline-theme-ui.scss';
@import '../../../components/toggle-button/style/themes/dnb-toggle-button-theme-ui.scss';
@import '../../../components/tooltip/style/themes/dnb-tooltip-theme-ui.scss';
@import '../../../extensions/payment-card/style/themes/dnb-payment-card-theme-ui.scss';
diff --git a/yarn.lock b/yarn.lock
index 5f736041663..df132595fbe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1960,6 +1960,7 @@ __metadata:
"@wojtekmaj/enzyme-adapter-react-17": 0.6.5
audit-ci: 5.1.2
babel-jest: 27.3.1
+ babel-plugin-fully-specified: 1.3.0
babel-plugin-optimize-clsx: 2.6.2
babel-plugin-search-and-replace: 1.1.0
babel-plugin-transform-react-remove-prop-types: 0.4.24
@@ -8080,6 +8081,15 @@ __metadata:
languageName: node
linkType: hard
+"babel-plugin-fully-specified@npm:1.3.0":
+ version: 1.3.0
+ resolution: "babel-plugin-fully-specified@npm:1.3.0"
+ peerDependencies:
+ "@babel/core": "*"
+ checksum: a561b33da1f0e976d34721f55c25c4132c24194ad5d089ac7427db229a852dcb3e83044f9ca34e380e0eba31d789cbaf73b2d8efefb95952c7daedbf83ccd4b8
+ languageName: node
+ linkType: hard
+
"babel-plugin-istanbul@npm:^6.0.0":
version: 6.1.1
resolution: "babel-plugin-istanbul@npm:6.1.1"