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';