diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/timeline.md b/packages/dnb-design-system-portal/src/docs/uilib/components/timeline.md
new file mode 100644
index 00000000000..582ced3669e
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/components/timeline.md
@@ -0,0 +1,13 @@
+---
+title: 'Timeline'
+description: 'The Timeline component shows events in chronological order and gives a great overview of the overall process'
+status: 'new'
+order: 19
+showTabs: true
+---
+
+import TimelineInfo from 'Docs/uilib/components/timeline/info'
+import TimelineDemos from 'Docs/uilib/components/timeline/demos'
+
+
+
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/Examples.js b/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/Examples.js
new file mode 100644
index 00000000000..6def3a76001
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/Examples.js
@@ -0,0 +1,342 @@
+/**
+ * UI lib Component Example
+ *
+ */
+
+import ComponentBox from 'dnb-design-system-portal/src/shared/tags/ComponentBox'
+import {
+ card as Card,
+ account_card as AccountCard,
+ confetti as Confetti,
+} from '@dnb/eufemia/src/icons'
+
+export const TimelineSingleCompleted = () => (
+
+ {() => /* jsx */ `
+
+`}
+
+)
+
+export const TimelineSingleCurrent = () => (
+
+ {() => /* jsx */ `
+
+`}
+
+)
+
+export const TimelineSingleUpcoming = () => (
+
+ {() => /* jsx */ `
+
+`}
+
+)
+
+export const TimelineMultipleData = () => (
+
+ {() => /* jsx */ `
+() => {
+ const events = [
+ {
+ name: "Completed event",
+ date: "10. september 2021",
+ state: "completed"
+ },
+ {
+ name: "Current event",
+ infoMessage: "Additional information about this step if needed.",
+ state: "current",
+ },
+ {
+ name: "Upcoming event",
+ state: "upcoming",
+ },
+ ];
+
+ return (
+
+ )
+}
+`}
+
+)
+
+export const TimelineMultipleCompletedData = () => (
+
+ {() => /* jsx */ `
+() => {
+ const events = [
+ {
+ name: "Completed event#1",
+ infoMessage: "Additional information about this step if needed.",
+ date: "10. september 2021",
+ state: "completed"
+ },
+ {
+ name: "Completed event#2",
+ infoMessage: "Additional information about this step if needed.",
+ state: "completed"
+ },
+ {
+ name: "Completed event#3",
+ date: "10. september 2021",
+ state: "completed"
+ },
+ ];
+
+ return (
+
+ )
+}
+`}
+
+)
+
+export const TimelineMultipleUpcomingData = () => (
+
+ {() => /* jsx */ `
+() => {
+ const events = [
+ {
+ name: "Upcoming event#1",
+ infoMessage: "Additional information about this step if needed.",
+ date: "10. september 2021",
+ state: "upcoming"
+ },
+ {
+ name: "Upcoming event#2",
+ infoMessage: "Additional information about this step if needed.",
+ state: "upcoming"
+ },
+ {
+ name: "Upcoming event#3",
+ date: "10. september 2021",
+ state: "upcoming"
+ },
+ ];
+
+ return (
+
+ )
+}
+`}
+
+)
+
+export const TimelineMultipleCurrentData = () => (
+
+ {() => /* jsx */ `
+() => {
+ const events = [
+ {
+ name: "Current event#1",
+ infoMessage: "Additional information about this step if needed.",
+ date: "10. september 2021",
+ state: "current"
+ },
+ {
+ name: "Current event#2",
+ infoMessage: "Additional information about this step if needed.",
+ state: "current"
+ },
+ {
+ name: "Current event#3",
+ date: "10. september 2021",
+ state: "current"
+ },
+ ];
+
+ return (
+
+ )
+}
+`}
+
+)
+
+export const TimelineMultiple = () => (
+
+ {() => /* jsx */ `
+
+
+
+
+
+`}
+
+)
+
+export const TimelineStates = () => (
+
+ {() => /* jsx */ `
+() => {
+ const events = [
+ {
+ name: "Completed event",
+ date: "10. september 2021",
+ infoMessage: "Additional information about this step if needed.",
+ state: "completed"
+ },
+ {
+ name: "Current event",
+ date: "10. september 2021",
+ infoMessage: "Additional information about this step if needed.",
+ state: "current"
+ },
+ {
+ name: "Upcoming event",
+ date: "10. september 2021",
+ infoMessage: "Additional information about this step if needed.",
+ state: "upcoming"
+ },
+ ];
+
+ return (
+
+ )
+}
+`}
+
+)
+
+export const TimelineIcons = () => (
+
+ {() => /* jsx */ `
+() => {
+ const events = [
+ {
+ name: "Completed event",
+ state: "completed",
+ icon: Confetti,
+ iconAlt: "Celebration"
+ },
+ {
+ name: "Current event",
+ state: "current",
+ icon: Card,
+ iconAlt: "Bank card"
+ },
+ {
+ name: "Upcoming event",
+ state: "upcoming",
+ icon: AccountCard,
+ iconAlt: "Money bag & card"
+ },
+ ];
+
+ return (
+
+ )
+}
+`}
+
+)
+
+export const TimelineSkeleton = () => (
+
+ {() => /* jsx */ `
+
+`}
+
+)
+
+export const TimelineItemSkeleton = () => (
+
+ {() => /* jsx */ `
+
+`}
+
+)
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/demos.md b/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/demos.md
new file mode 100644
index 00000000000..02e84f7c9a3
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/demos.md
@@ -0,0 +1,82 @@
+---
+showTabs: true
+---
+
+import {
+TimelineSingleCompleted,
+TimelineSingleCurrent,
+TimelineSingleUpcoming,
+TimelineMultiple,
+TimelineMultipleData,
+TimelineMultipleCompletedData,
+TimelineMultipleCurrentData,
+TimelineMultipleUpcomingData,
+TimelineStates,
+TimelineIcons,
+TimelineSkeleton,
+TimelineItemSkeleton
+} from 'Docs/uilib/components/timeline/Examples'
+
+## Demos
+
+### Timeline with multiple [timeline items](/uilib/components/timeline/properties#timelineitem-properties):
+
+We can pass down a list of [timeline items](/uilib/components/timeline/properties#timelineitem-properties) as a variable to `data`.
+
+It's also possible to pass down [timeline items](/uilib/components/timeline/properties#timelineitem-properties) as [children in Multiple Timeline](/uilib/components/timeline/#multiple-timeline-with-children).
+
+
+
+### Timeline with multiple [timeline items](/uilib/components/timeline/properties#timelineitem-properties) passed down as children:
+
+Passing down [timeline items](/uilib/components/timeline/properties#timelineitem-properties) as children
+
+
+
+### Examples of Timelines with one [timeline item](/uilib/components/timeline/properties#timelineitem-properties):
+
+#### Completed [timeline item](/uilib/components/timeline/properties#timelineitem-properties):
+
+
+
+#### Current [timeline item](/uilib/components/timeline/properties#timelineitem-properties):
+
+
+
+#### Upcoming [timeline item](/uilib/components/timeline/properties#timelineitem-properties):
+
+
+
+### Setting property `state` of [timeline item](/uilib/components/timeline/properties#timelineitem-properties):
+
+Property `state` changes styling of icon, border, and font of the [timeline item](/uilib/components/timeline/properties#timelineitem-properties)
+
+
+
+### Setting property `icon` of [timeline items](/uilib/components/timeline/properties#timelineitem-properties):
+
+Property `icon` is by default set based on the value of `state` property, but can be overwritten by passing a `icon`, see how to [import icons](/uilib/components/icon#importing-icons).
+
+See default icons based on value of `state` property in documentation for `icon` property of the [timeline item](/uilib/components/timeline/properties#timelineitem-properties)
+
+
+
+### Timeline skeleton:
+
+
+
+### [Timeline item](/uilib/components/timeline/properties#timelineitem-properties) skeleton:
+
+
+
+### Timeline with multiple completed [timeline items](/uilib/components/timeline/properties#timelineitem-properties):
+
+
+
+### Timeline with multiple current [timeline items](/uilib/components/timeline/properties#timelineitem-properties):
+
+
+
+### Timeline with multiple upcoming [timeline items](/uilib/components/timeline/properties#timelineitem-properties):
+
+
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/events.md b/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/events.md
new file mode 100644
index 00000000000..875df1bbea9
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/events.md
@@ -0,0 +1,7 @@
+---
+showTabs: true
+---
+
+## Events
+
+No events are supported at the moment.
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/info.md b/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/info.md
new file mode 100644
index 00000000000..8ed5ce5cde2
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/info.md
@@ -0,0 +1,8 @@
+---
+showTabs: true
+---
+
+## Description
+
+A timeline shows events in chronological order and gives a great overview of the overall process.
+The component itself has interchangeable icons, additional messagebox when needed, and a step has three states(completed, current or upcoming). Date formatting can vary to be consistent with the rest of your application.
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/properties.md b/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/properties.md
new file mode 100644
index 00000000000..2b32cc1ec2a
--- /dev/null
+++ b/packages/dnb-design-system-portal/src/docs/uilib/components/timeline/properties.md
@@ -0,0 +1,27 @@
+---
+showTabs: true
+---
+
+## Properties
+
+### `Timeline` properties
+
+| Properties | Description |
+| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `data` | _(optional)_ List of [timeline items](/uilib/components/timeline/properties#timelineitem-properties) to render. Each object in data can include all properties from [Timeline.Item properties](/uilib/components/timeline/properties#timelineitem-properties). |
+| `children` | _(optional)_ Content of the component. Can be used instead of property `data`, by adding [Timeline Item](/uilib/components/timeline/properties#timelineitem-properties) as children ``. |
+| `skeleton` | _(optional)_ Applies loading skeleton. |
+| `className` | _(optional)_ Custom className for the component root. |
+| [Space](/uilib/components/space/properties) | _(optional)_ spacing properties like `top` or `bottom` are supported. |
+
+### `Timeline.Item` properties
+
+| Properties | Description |
+| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `name` | _(mandatory)_ Name/title of the Timeline item. |
+| `state` | _(mandatory)_ The component state. Options: `completed` \| `current` \| `upcoming`. |
+| `date` | _(optional)_ Date of the Timeline item, displayed below the `name`. |
+| `infoMessage` | _(optional)_ Info message, displayed in a [FormStatus of state info](/uilib/components/form-status#formstatus-displaying-info-status), below the `date` if it exists. |
+| `icon` | _(optional)_ Override icon displaying on the left side (Not recommended). Default: `check` for state `completed`, `pin` for state `current`, and `calendar` for state `upcoming` . |
+| `iconAlt` | _(optional)_ Alt label describing the icon provided. |
+| `skeleton` | _(optional)_ Applies loading skeleton. |
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/toggle-button.md b/packages/dnb-design-system-portal/src/docs/uilib/components/toggle-button.md
index 3eee66fbf8a..419eb1fc01e 100644
--- a/packages/dnb-design-system-portal/src/docs/uilib/components/toggle-button.md
+++ b/packages/dnb-design-system-portal/src/docs/uilib/components/toggle-button.md
@@ -1,7 +1,7 @@
---
title: 'ToggleButton'
description: 'The ToggleButton component should be used to toggle on or off a limited number of choices.'
-order: 19
+order: 20
showTabs: true
---
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/tooltip.md b/packages/dnb-design-system-portal/src/docs/uilib/components/tooltip.md
index 3704570bf99..569e58e5745 100644
--- a/packages/dnb-design-system-portal/src/docs/uilib/components/tooltip.md
+++ b/packages/dnb-design-system-portal/src/docs/uilib/components/tooltip.md
@@ -1,7 +1,7 @@
---
title: 'Tooltip'
status: 'new'
-order: 20
+order: 21
showTabs: true
hideTabs:
- title: Events
diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/upload.md b/packages/dnb-design-system-portal/src/docs/uilib/components/upload.md
index e3d020eae97..d274bb9cdfd 100644
--- a/packages/dnb-design-system-portal/src/docs/uilib/components/upload.md
+++ b/packages/dnb-design-system-portal/src/docs/uilib/components/upload.md
@@ -1,7 +1,7 @@
---
title: 'Upload'
status: 'wip'
-order: 21
+order: 22
# showTabs: true
---
diff --git a/packages/dnb-eufemia/src/components/Timeline.js b/packages/dnb-eufemia/src/components/Timeline.js
new file mode 100644
index 00000000000..eca6cca4cab
--- /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 "prepareTimeline"
+ */
+
+import Timeline from './timeline/Timeline'
+export * from './timeline/Timeline'
+export default Timeline
diff --git a/packages/dnb-eufemia/src/components/index.js b/packages/dnb-eufemia/src/components/index.js
index e08742e2d93..4eaa8109580 100644
--- a/packages/dnb-eufemia/src/components/index.js
+++ b/packages/dnb-eufemia/src/components/index.js
@@ -44,6 +44,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'
@@ -83,6 +84,7 @@ export {
Tabs,
Tag,
Textarea,
+ Timeline,
ToggleButton,
Tooltip,
}
diff --git a/packages/dnb-eufemia/src/components/lib.js b/packages/dnb-eufemia/src/components/lib.js
index 64918bb1478..b0350565ee4 100644
--- a/packages/dnb-eufemia/src/components/lib.js
+++ b/packages/dnb-eufemia/src/components/lib.js
@@ -46,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'
@@ -85,6 +86,7 @@ export {
Tabs,
Tag,
Textarea,
+ Timeline,
ToggleButton,
Tooltip,
}
@@ -125,6 +127,7 @@ export const getComponents = () => {
Tabs,
Tag,
Textarea,
+ Timeline,
ToggleButton,
Tooltip,
}
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..96b31e38157
--- /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 '../..'
+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/index.js b/packages/dnb-eufemia/src/index.js
index 301802f9b2f..0a2e177ca19 100644
--- a/packages/dnb-eufemia/src/index.js
+++ b/packages/dnb-eufemia/src/index.js
@@ -44,6 +44,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'
@@ -112,6 +113,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..7f82b079fc0 100644
--- a/packages/dnb-eufemia/src/shared/Context.js
+++ b/packages/dnb-eufemia/src/shared/Context.js
@@ -52,6 +52,8 @@ export const prepareContext = (props = {}) => {
Breadcrumb: {},
BreadcrumbItem: {},
Tag: {},
+ Timeline: {},
+ TimelineItem: {},
}
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/dnb-ui-components.scss b/packages/dnb-eufemia/src/style/dnb-ui-components.scss
index 35644eb1973..cb852b00b80 100644
--- a/packages/dnb-eufemia/src/style/dnb-ui-components.scss
+++ b/packages/dnb-eufemia/src/style/dnb-ui-components.scss
@@ -39,5 +39,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/themes/theme-ui/dnb-theme-ui.scss b/packages/dnb-eufemia/src/style/themes/theme-ui/dnb-theme-ui.scss
index 01186314b0d..d4459f5057e 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
@@ -53,6 +53,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';