agroptima-design-system 1.1.1-beta.1 → 1.2.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agroptima-design-system",
3
- "version": "1.1.1-beta.1",
3
+ "version": "1.2.0-beta.0",
4
4
  "scripts": {
5
5
  "dev": "npm run storybook",
6
6
  "storybook": "storybook dev -p 6006 --ci",
@@ -47,7 +47,7 @@
47
47
  cursor: default;
48
48
  }
49
49
 
50
- .checkbox {
50
+ .checkbox-group {
51
51
  margin-top: config.$space-1x;
52
52
  }
53
53
 
@@ -12,7 +12,7 @@ export interface DrawerProps extends React.ComponentPropsWithoutRef<'div'> {
12
12
  id: string
13
13
  variant?: Variant
14
14
  title: string
15
- buttons: ButtonProps[]
15
+ buttons?: ButtonProps[]
16
16
  onClose: () => void
17
17
  }
18
18
 
@@ -21,7 +21,7 @@ export function Drawer({
21
21
  className,
22
22
  variant = 'primary',
23
23
  title,
24
- buttons,
24
+ buttons = [],
25
25
  children,
26
26
  onClose,
27
27
  ...props
@@ -59,13 +59,15 @@ export function Drawer({
59
59
  {children}
60
60
  </div>
61
61
  </div>
62
- <div className="footer">
63
- <Actions>
64
- {buttons.map(({ ...button }) => (
65
- <Button key={button.label} {...button} />
66
- ))}
67
- </Actions>
68
- </div>
62
+ {buttons.length > 0 && (
63
+ <div className="footer">
64
+ <Actions>
65
+ {buttons.map(({ ...button }) => (
66
+ <Button key={button.label} {...button} />
67
+ ))}
68
+ </Actions>
69
+ </div>
70
+ )}
69
71
  </div>
70
72
  </div>
71
73
  )
@@ -48,6 +48,9 @@
48
48
  &.error {
49
49
  @include mixins.svg-color(var(--error-color-1000));
50
50
  }
51
+ &.primary {
52
+ @include mixins.svg-color(var(--primary-color-600));
53
+ }
51
54
 
52
55
  @keyframes rotate {
53
56
  from {
@@ -5,7 +5,7 @@ import { classNames } from '../utils/classNames'
5
5
  export type IconType = keyof typeof icons
6
6
 
7
7
  export type IconSize = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8'
8
- export type Variant = 'info' | 'success' | 'warning' | 'error'
8
+ export type Variant = 'info' | 'success' | 'warning' | 'error' | 'primary'
9
9
 
10
10
  export interface IconProps extends React.SVGAttributes<HTMLOrSVGElement> {
11
11
  name: IconType
@@ -0,0 +1,161 @@
1
+ @use '../../settings/typography/content' as typography;
2
+ @use '../../settings/config';
3
+ @use '../../settings/depth';
4
+ @use '../../settings/breakpoints';
5
+ @use '../../settings/mixins';
6
+
7
+ .notification-center-container {
8
+ .notification-center {
9
+ display: flex;
10
+ flex-direction: column;
11
+ width: 350px;
12
+ box-shadow:
13
+ 0 3px 6px -4px rgba(0, 0, 0, 0.12),
14
+ 0px 6px 16px 0px rgba(0, 0, 0, 0.08),
15
+ 0px 9px 28px 8px rgba(0, 0, 0, 0.05);
16
+
17
+ .notification-header {
18
+ display: flex;
19
+ flex-direction: column;
20
+ width: 100%;
21
+
22
+ .notification-header-title {
23
+ @include typography.body-bold;
24
+ display: flex;
25
+ padding: config.$space-2x config.$space-3x;
26
+ gap: var(--space-1x);
27
+ border-radius: config.$corner-radius-m config.$corner-radius-m 0 0;
28
+ background: var(--neutral-color-900);
29
+ color: var(--neutral-white);
30
+ }
31
+
32
+ .notification-header-content {
33
+ display: flex;
34
+ flex-direction: row;
35
+ justify-content: space-between;
36
+ align-items: center;
37
+ padding: config.$space-2x config.$space-3x;
38
+ gap: config.$space-2x;
39
+ background: var(--neutral-white);
40
+
41
+ > .button:last-child {
42
+ @include typography.footnote-primary;
43
+ color: var(--primary-color-600);
44
+ border: 0;
45
+ background-color: transparent;
46
+ padding: 0;
47
+ }
48
+ }
49
+ }
50
+
51
+ .notification-list {
52
+ max-height: 370px;
53
+ overflow-y: auto;
54
+ }
55
+
56
+ .notification-line {
57
+ display: flex;
58
+ flex-direction: column;
59
+ width: 100%;
60
+ padding: config.$space-2x config.$space-3x config.$space-2x
61
+ config.$space-3x;
62
+ background-color: var(--neutral-white);
63
+
64
+ &:hover {
65
+ background-color: var(--primary-color-50);
66
+ }
67
+
68
+ .notification-line-head {
69
+ display: flex;
70
+ gap: config.$space-2x;
71
+ justify-content: space-between;
72
+ align-items: center;
73
+ cursor: default;
74
+
75
+ .notification-line-title {
76
+ white-space: normal;
77
+ display: flex;
78
+ align-items: baseline;
79
+ @include typography.body-bold;
80
+
81
+ &:has(.icon.success) {
82
+ align-items: center;
83
+ }
84
+
85
+ .icon {
86
+ margin-right: config.$space-1x;
87
+
88
+ &.primary {
89
+ width: config.$icon-size-2x;
90
+ height: config.$icon-size-2x;
91
+ }
92
+ }
93
+ }
94
+ .notification-line-date {
95
+ @include typography.footnote-primary;
96
+ }
97
+ }
98
+
99
+ .notification-line-description {
100
+ display: flex;
101
+ flex-direction: column;
102
+ gap: config.$space-2x;
103
+ cursor: default;
104
+
105
+ &[open] summary::after {
106
+ content: attr(data-open);
107
+ }
108
+
109
+ &:not([open]) summary::after {
110
+ content: attr(data-close);
111
+ }
112
+
113
+ > summary {
114
+ list-style: none;
115
+ color: var(--primary-color-600);
116
+ text-decoration-line: underline;
117
+ }
118
+
119
+ > summary::-webkit-details-marker {
120
+ display: none;
121
+ }
122
+
123
+ > p {
124
+ white-space: normal;
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
130
+
131
+ @media only screen and (max-width: breakpoints.$medium) {
132
+ .popover-container {
133
+ position: relative;
134
+
135
+ .popover-menu-container {
136
+ margin: 0;
137
+ padding: 0;
138
+ width: 100%;
139
+
140
+ .backdrop {
141
+ display: none;
142
+ visibility: hidden;
143
+ }
144
+
145
+ .notification-center {
146
+ width: 100%;
147
+ height: 96vh;
148
+
149
+ .notification-header {
150
+ .notification-header-title {
151
+ border-radius: 0;
152
+ }
153
+ }
154
+
155
+ .notification-list {
156
+ max-height: 100vh;
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
@@ -0,0 +1,43 @@
1
+ 'use client'
2
+ import './NotificationCenter.scss'
3
+ import React from 'react'
4
+ import { classNames } from '../../utils/classNames'
5
+ import { IconButton } from '../Button'
6
+ import type { Variant } from '../Button/IconButton'
7
+ import { Popover, type Position } from '../Popover'
8
+
9
+ export interface NotificationCenterProps {
10
+ position?: Position
11
+ className?: string
12
+ variant?: Variant
13
+ accessibilityLabel?: string
14
+ children: React.ReactNode
15
+ }
16
+
17
+ function NotificationCenter({
18
+ position = 'right',
19
+ className,
20
+ variant = 'secondary',
21
+ accessibilityLabel = 'Open notifications center',
22
+ children,
23
+ }: NotificationCenterProps) {
24
+ return (
25
+ <Popover
26
+ position={position}
27
+ className={classNames('notification-center-container', className)}
28
+ renderButton={({ toggle }) => (
29
+ <IconButton
30
+ icon="Notification"
31
+ variant={variant}
32
+ accessibilityLabel={accessibilityLabel}
33
+ onClick={toggle}
34
+ />
35
+ )}
36
+ closeOnClick={false}
37
+ >
38
+ <div className="notification-center">{children}</div>
39
+ </Popover>
40
+ )
41
+ }
42
+
43
+ export { NotificationCenter }
@@ -0,0 +1,31 @@
1
+ 'use client'
2
+ import './NotificationCenter.scss'
3
+ import React from 'react'
4
+ import { classNames } from '../../utils/classNames'
5
+ import { Icon } from '../Icon'
6
+
7
+ export interface NotificationEmptyStateProps {
8
+ title: string
9
+ date: string
10
+ className?: string
11
+ }
12
+
13
+ function NotificationEmptyState({
14
+ title,
15
+ date,
16
+ className,
17
+ }: NotificationEmptyStateProps) {
18
+ return (
19
+ <div className={classNames('notification-line', className)}>
20
+ <div className="notification-line-head">
21
+ <span className="notification-line-title">
22
+ <Icon variant="success" name="Check" />
23
+ {title}
24
+ </span>
25
+ <span className="notification-line-date">{date}</span>
26
+ </div>
27
+ </div>
28
+ )
29
+ }
30
+
31
+ export { NotificationEmptyState }
@@ -0,0 +1,28 @@
1
+ 'use client'
2
+ import './NotificationCenter.scss'
3
+ import React from 'react'
4
+ import { classNames } from '../../utils/classNames'
5
+ import { IconButton } from '../Button'
6
+ import type { Variant } from '../Button/IconButton'
7
+ import { Popover, type Position } from '../Popover'
8
+
9
+ export interface NotificationHeaderProps {
10
+ title: string
11
+ className?: string
12
+ children: React.ReactNode
13
+ }
14
+
15
+ function NotificationHeader({
16
+ title,
17
+ className,
18
+ children,
19
+ }: NotificationHeaderProps) {
20
+ return (
21
+ <div className={classNames('notification-header', className)}>
22
+ <div className="notification-header-title">{title}</div>
23
+ <div className="notification-header-content">{children}</div>
24
+ </div>
25
+ )
26
+ }
27
+
28
+ export { NotificationHeader }
@@ -0,0 +1,45 @@
1
+ 'use client'
2
+ import './NotificationCenter.scss'
3
+ import React from 'react'
4
+ import { classNames } from '../../utils/classNames'
5
+ import { Icon } from '../Icon'
6
+
7
+ export interface NotificationLineProps extends React.ComponentPropsWithoutRef<'div'> {
8
+ title: string
9
+ date: string
10
+ isRead?: boolean
11
+ expandLabel?: string
12
+ collapseLabel?: string
13
+ className?: string
14
+ onClick?: () => void
15
+ children: React.ReactNode
16
+ }
17
+
18
+ function NotificationLine({
19
+ title,
20
+ date,
21
+ isRead = false,
22
+ expandLabel = 'View more',
23
+ collapseLabel = 'View less',
24
+ className,
25
+ onClick = () => {},
26
+ children,
27
+ }: NotificationLineProps) {
28
+ return (
29
+ <div className={classNames('notification-line', className)}>
30
+ <div className="notification-line-head" onClick={onClick}>
31
+ <span className="notification-line-title">
32
+ {!isRead && <Icon variant="primary" name="TimelineCircleSelected" />}
33
+ {title}
34
+ </span>
35
+ <span className="notification-line-date">{date}</span>
36
+ </div>
37
+ <details className="notification-line-description">
38
+ <summary data-open={collapseLabel} data-close={expandLabel}></summary>
39
+ <p>{children}</p>
40
+ </details>
41
+ </div>
42
+ )
43
+ }
44
+
45
+ export { NotificationLine }
@@ -0,0 +1,17 @@
1
+ 'use client'
2
+ import './NotificationCenter.scss'
3
+ import React from 'react'
4
+ import { classNames } from '../../utils/classNames'
5
+
6
+ export interface NotificationListProps {
7
+ className?: string
8
+ children: React.ReactNode
9
+ }
10
+
11
+ function NotificationList({ className, children }: NotificationListProps) {
12
+ return (
13
+ <div className={classNames('notification-list', className)}>{children}</div>
14
+ )
15
+ }
16
+
17
+ export { NotificationList }
@@ -0,0 +1,13 @@
1
+ import { NotificationCenter } from './NotificationCenter'
2
+ import { NotificationEmptyState } from './NotificationEmptyState'
3
+ import { NotificationHeader } from './NotificationHeader'
4
+ import { NotificationLine } from './NotificationLine'
5
+ import { NotificationList } from './NotificationList'
6
+
7
+ export {
8
+ NotificationCenter,
9
+ NotificationEmptyState,
10
+ NotificationHeader,
11
+ NotificationLine,
12
+ NotificationList,
13
+ }
@@ -14,12 +14,13 @@ type Actions = {
14
14
 
15
15
  export type Horizontal = 'left' | 'right' | 'center'
16
16
 
17
- type Position = Horizontal | `top-${Horizontal}`
17
+ export type Position = Horizontal | `top-${Horizontal}`
18
18
 
19
19
  export interface PopoverProps {
20
20
  renderButton: (props: Actions) => React.ReactNode
21
21
  position?: Position
22
22
  className?: string
23
+ closeOnClick?: boolean
23
24
  children: React.ReactNode
24
25
  }
25
26
 
@@ -27,6 +28,7 @@ function Popover({
27
28
  renderButton,
28
29
  position = 'left',
29
30
  className,
31
+ closeOnClick = true,
30
32
  children,
31
33
  }: PopoverProps) {
32
34
  const { isOpen, close, open, toggle } = useOpen()
@@ -44,7 +46,7 @@ function Popover({
44
46
  className={classNames('popover-menu-container', position, {
45
47
  hidden: !isOpen,
46
48
  })}
47
- onClick={close}
49
+ onClick={closeOnClick ? close : () => {}}
48
50
  aria-hidden={!isOpen}
49
51
  >
50
52
  <div className="backdrop"></div>
@@ -1,6 +1,13 @@
1
1
  import { Popover } from './Popover'
2
2
  import { type Horizontal } from './Popover'
3
+ import { type Position } from './Popover'
3
4
  import { PopoverMenu } from './PopoverMenu'
4
5
  import { PopoverMenuOption } from './PopoverMenuOption'
5
6
 
6
- export { type Horizontal, Popover, PopoverMenu, PopoverMenuOption }
7
+ export {
8
+ type Horizontal,
9
+ Popover,
10
+ PopoverMenu,
11
+ PopoverMenuOption,
12
+ type Position,
13
+ }
@@ -0,0 +1,47 @@
1
+ import './Timeline'
2
+ import React from 'react'
3
+ import { classNames } from '../../utils/classNames'
4
+ import { BaseButton, type BaseButtonProps } from '../Button'
5
+ import { Icon, type IconType } from '../Icon'
6
+
7
+ export type Variant = 'primary'
8
+
9
+ export interface CustomProps {
10
+ variant?: Variant
11
+ title: string
12
+ disabled?: boolean
13
+ active?: boolean
14
+ leftIcon?: IconType
15
+ }
16
+
17
+ export type LoadMoreMilestonesButtonProps = CustomProps & BaseButtonProps
18
+
19
+ export function LoadMoreMilestonesButton({
20
+ variant = 'primary',
21
+ className,
22
+ title,
23
+ disabled,
24
+ leftIcon,
25
+ prefetch = false,
26
+ ...props
27
+ }: LoadMoreMilestonesButtonProps): React.JSX.Element {
28
+ const cssClasses = classNames(
29
+ 'load-more-milestones-button',
30
+ variant,
31
+ className,
32
+ {
33
+ disabled,
34
+ },
35
+ )
36
+ return (
37
+ <BaseButton
38
+ className={cssClasses}
39
+ aria-disabled={disabled}
40
+ disabled={disabled}
41
+ {...props}
42
+ >
43
+ {leftIcon && <Icon name={leftIcon} size="6" />}
44
+ <span className="title">{title}</span>
45
+ </BaseButton>
46
+ )
47
+ }
@@ -0,0 +1,44 @@
1
+ import './Timeline.scss'
2
+ import Link from 'next/link'
3
+ import React from 'react'
4
+ import { classNames } from '../../utils/classNames'
5
+ import { Icon } from '../Icon'
6
+
7
+ export interface MilestoneProps extends React.HTMLAttributes<HTMLLIElement> {
8
+ title: string
9
+ description: string
10
+ isSelected?: boolean
11
+ href?: string
12
+ }
13
+
14
+ export function Milestone({
15
+ title,
16
+ description,
17
+ isSelected = false,
18
+ href = '',
19
+ className,
20
+ ...props
21
+ }: MilestoneProps) {
22
+ return (
23
+ <li
24
+ className={classNames('milestone-item', { selected: isSelected })}
25
+ {...props}
26
+ >
27
+ <div className="left-wrapper">
28
+ <Icon
29
+ name={isSelected ? 'TimelineCircleSelected' : 'TimelineCircleDefault'}
30
+ />
31
+ </div>
32
+ <div className="right-wrapper">
33
+ {href === '' ? (
34
+ <span className="milestone-title">{title}</span>
35
+ ) : (
36
+ <Link href={href} className={classNames('milestone-title-link')}>
37
+ <span className="milestone-title">{title}</span>
38
+ </Link>
39
+ )}
40
+ <p className="milestone-description">{description}</p>
41
+ </div>
42
+ </li>
43
+ )
44
+ }
@@ -0,0 +1,156 @@
1
+ @use '../../settings/typography/content' as typography;
2
+ @use '../../settings/config';
3
+ @use '../../settings/mixins';
4
+ @use '../../settings/depth';
5
+
6
+ .timeline-container {
7
+ .timeline-rail {
8
+ position: relative;
9
+
10
+ &::after {
11
+ content: '';
12
+ position: absolute;
13
+ width: config.$space-halfx;
14
+ top: 0;
15
+ bottom: 0;
16
+ left: 11px;
17
+ z-index: depth.$z-timeline-rail;
18
+ }
19
+ }
20
+
21
+ .milestones-list {
22
+ list-style: none;
23
+ padding: 0;
24
+ margin: config.$space-2x 0px config.$space-2x 6px;
25
+
26
+ .milestone-item {
27
+ display: flex;
28
+ justify-content: flex-start;
29
+ align-items: flex-start;
30
+ gap: config.$space-3x;
31
+
32
+ .left-wrapper {
33
+ display: flex;
34
+ flex-direction: column;
35
+ justify-content: center;
36
+ align-items: center;
37
+ margin-top: config.$space-2x;
38
+
39
+ .icon {
40
+ width: config.$icon-size-3x;
41
+ height: config.$icon-size-3x;
42
+ z-index: depth.$z-timeline-circle;
43
+ }
44
+ }
45
+
46
+ .milestone-title {
47
+ @include typography.body-regular-primary;
48
+ }
49
+ .milestone-description {
50
+ @include typography.footnote-primary;
51
+ }
52
+
53
+ &.selected {
54
+ .milestone-title {
55
+ @include typography.body-bold;
56
+ }
57
+ }
58
+ }
59
+ }
60
+
61
+ &.primary {
62
+ .timeline-rail {
63
+ &::after {
64
+ background-color: var(--neutral-color-200);
65
+ }
66
+ }
67
+ .milestone-item {
68
+ .milestone-description {
69
+ color: var(--neutral-color-600);
70
+ }
71
+
72
+ .milestone-title-link {
73
+ .milestone-title {
74
+ color: var(--primary-color-600);
75
+ }
76
+ }
77
+
78
+ &.selected {
79
+ .milestone-description {
80
+ color: var(--neutral-color-1000);
81
+ }
82
+
83
+ @include mixins.icon-color(var(--primary-color-600));
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ .load-more-milestones-button {
90
+ display: flex;
91
+ justify-content: space-between;
92
+ gap: config.$space-2x;
93
+ border: 0;
94
+ background-color: transparent;
95
+ padding: 0;
96
+
97
+ .icon {
98
+ display: flex;
99
+ justify-content: center;
100
+ align-items: center;
101
+ gap: config.$space-2x;
102
+ border-radius: config.$corner-radius-xxs;
103
+ padding: config.$space-1x;
104
+ }
105
+
106
+ &.primary {
107
+ &:hover {
108
+ .icon {
109
+ &:not(:disabled) {
110
+ border: 1px solid var(--primary-color-600);
111
+ background: var(--primary-color-50);
112
+ color: var(--primary-color-600);
113
+ }
114
+ }
115
+
116
+ .title {
117
+ &:not(:disabled) {
118
+ color: var(--primary-color-800);
119
+ }
120
+ }
121
+ }
122
+
123
+ .icon {
124
+ border: 1px solid var(--neutral-color-600);
125
+ background: var(--neutral-white);
126
+ @include typography.body-regular-primary;
127
+
128
+ > svg {
129
+ fill: var(--primary-color-600);
130
+ path {
131
+ fill: var(--primary-color-600);
132
+ }
133
+ }
134
+
135
+ &:disabled,
136
+ &.disabled {
137
+ pointer-events: none;
138
+ border: 1px solid var(--neutral-color-400);
139
+ background: var(--neutral-color-50);
140
+ color: var(--neutral-color-400);
141
+ > .icon {
142
+ > svg {
143
+ fill: var(--neutral-color-400);
144
+ path {
145
+ fill: var(--neutral-color-400);
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+
152
+ .title {
153
+ color: var(--primary-color-600);
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,26 @@
1
+ import './Timeline.scss'
2
+ import React from 'react'
3
+ import { classNames } from '../../utils/classNames'
4
+
5
+ export type Variant = 'primary'
6
+ export interface TimelineProps extends React.HTMLAttributes<HTMLDivElement> {
7
+ variant?: Variant
8
+ }
9
+
10
+ export function Timeline({
11
+ className,
12
+ variant = 'primary',
13
+ children,
14
+ ...props
15
+ }: TimelineProps) {
16
+ return (
17
+ <div
18
+ className={classNames('timeline-container', variant, className)}
19
+ {...props}
20
+ >
21
+ <div className="timeline-rail">
22
+ <ol className={classNames('milestones-list')}>{children}</ol>
23
+ </div>
24
+ </div>
25
+ )
26
+ }
@@ -0,0 +1,5 @@
1
+ import { Timeline } from './Timeline'
2
+ export type { TimelineProps } from './Timeline'
3
+ import { Milestone } from './Milestone'
4
+
5
+ export { Milestone, Timeline }
@@ -40,6 +40,7 @@ import Logout from './logout.svg'
40
40
  import Minus from './minus.svg'
41
41
  import More from './more.svg'
42
42
  import NewView from './new-view.svg'
43
+ import Notification from './notification.svg'
43
44
  import Orders from './orders.svg'
44
45
  import PDF from './pdf.svg'
45
46
  import Picture from './picture.svg'
@@ -57,6 +58,9 @@ import Show from './show.svg'
57
58
  import ShowOff from './show-off.svg'
58
59
  import Sorter from './sorter.svg'
59
60
  import TaxRule from './tax-rule.svg'
61
+ import TimelineCircleDefault from './timeline-circle-default.svg'
62
+ import TimelineCircleSelected from './timeline-circle-selected.svg'
63
+ import Traceability from './traceability.svg'
60
64
  import Upload from './upload.svg'
61
65
  import UserMenu from './user-menu.svg'
62
66
  import ValidateInvoice from './validate-invoice.svg'
@@ -105,6 +109,7 @@ export {
105
109
  Minus,
106
110
  More,
107
111
  NewView,
112
+ Notification,
108
113
  Orders,
109
114
  PDF,
110
115
  Picture,
@@ -122,6 +127,9 @@ export {
122
127
  ShowOff,
123
128
  Sorter,
124
129
  TaxRule,
130
+ TimelineCircleDefault,
131
+ TimelineCircleSelected,
132
+ Traceability,
125
133
  Upload,
126
134
  UserMenu,
127
135
  ValidateInvoice,
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M2 17.025v-1.983h1.983V8.1c0-1.372.414-2.59 1.24-3.657.826-1.066 1.9-1.764 3.223-2.095v-.694c0-.413.144-.764.434-1.053A1.43 1.43 0 0 1 9.933.167c.414 0 .765.144 1.054.434.29.289.434.64.434 1.053v.694a5.725 5.725 0 0 1 3.223 2.095c.826 1.066 1.24 2.285 1.24 3.657v6.942h1.983v1.983H2ZM9.933 20a1.91 1.91 0 0 1-1.4-.583 1.91 1.91 0 0 1-.583-1.4h3.967a1.91 1.91 0 0 1-.583 1.4 1.91 1.91 0 0 1-1.4.583Z" fill="#161C26"/></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"><path d="M0 5a5 5 0 1 1 10 0A5 5 0 0 1 0 5Z" fill="var(--neutral-white)"/><path d="M8 5a3 3 0 1 0-3 3v2A5 5 0 1 1 5 0a5 5 0 0 1 0 10V8a3 3 0 0 0 3-3Z" fill="var(--neutral-color-800)"/></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 10 10" xmlns="http://www.w3.org/2000/svg"><path d="M0 5a5 5 0 1 1 10 0A5 5 0 0 1 0 5Z" fill="#161C26"/></svg>
@@ -0,0 +1 @@
1
+ <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M17 19c-.65 0-1.233-.188-1.75-.563A2.91 2.91 0 0 1 14.175 17H9c-1.1 0-2.042-.392-2.825-1.175C5.392 15.042 5 14.1 5 13s.392-2.042 1.175-2.825C6.958 9.392 7.9 9 9 9h2c.55 0 1.02-.196 1.412-.588C12.804 8.021 13 7.55 13 7c0-.55-.196-1.02-.588-1.412A1.926 1.926 0 0 0 11 5H5.825a3.09 3.09 0 0 1-1.088 1.438A2.852 2.852 0 0 1 3 7a2.893 2.893 0 0 1-2.125-.875A2.893 2.893 0 0 1 0 4c0-.833.292-1.542.875-2.125A2.893 2.893 0 0 1 3 1c.65 0 1.23.188 1.737.563.509.375.871.854 1.088 1.437H11c1.1 0 2.042.392 2.825 1.175C14.608 4.958 15 5.9 15 7s-.392 2.042-1.175 2.825C13.042 10.608 12.1 11 11 11H9c-.55 0-1.02.196-1.412.588A1.926 1.926 0 0 0 7 13c0 .55.196 1.02.588 1.412.391.392.862.588 1.412.588h5.175a3.09 3.09 0 0 1 1.087-1.438A2.851 2.851 0 0 1 17 13c.833 0 1.542.292 2.125.875S20 15.167 20 16s-.292 1.542-.875 2.125A2.893 2.893 0 0 1 17 19ZM3 5a.97.97 0 0 0 .712-.287A.968.968 0 0 0 4 4a.968.968 0 0 0-.288-.712A.968.968 0 0 0 3 3a.968.968 0 0 0-.712.288A.968.968 0 0 0 2 4c0 .283.096.52.288.713A.968.968 0 0 0 3 5Z" fill="#161C26"/></svg>
@@ -13,4 +13,7 @@ $z-modal: $z-alert-container + $above;
13
13
  $z-aside-desktop: $base + $above;
14
14
  $z-aside-mobile: $z-modal + $above;
15
15
 
16
- $z-header: $base + $above;
16
+ $z-header: $base + $above;
17
+
18
+ $z-timeline-rail: $base;
19
+ $z-timeline-circle: $base + $above;
@@ -4,17 +4,27 @@ import { Meta } from "@storybook/addon-docs/blocks";
4
4
 
5
5
  # Changelog
6
6
 
7
+ ## 1.2.0
8
+
9
+ * Add NotificationCenter components
10
+
7
11
  ## 1.1.1
8
12
 
9
13
  * Update to Storybook 10
10
- * Add custom `composeStories` helper workaround because of Storybook 10 breaking changes
14
+ * Add custom `composeStories` helper workaround for a11y tests because of Storybook 10 breaking changes
11
15
  * Add missing components to a11y tests
12
16
 
17
+ ## 1.1.0
18
+
19
+ * Add Timeline, Milestone and LoadMoreMilestonesButton components
20
+ * On Drawer component, `buttons` prop is optional
21
+ * Add Traceability icon
22
+ * Fix Checkbox vertical displacement on CardsTable component
23
+
13
24
  ## 1.0.9
14
25
 
15
26
  * Add TaxRule icon
16
27
 
17
-
18
28
  ## 1.0.8
19
29
 
20
30
  * CardMenuOption can be a button or a link
@@ -24,12 +34,10 @@ import { Meta } from "@storybook/addon-docs/blocks";
24
34
 
25
35
  * Add link styles to Alert component
26
36
 
27
-
28
37
  ## 1.0.6
29
38
 
30
39
  * Restore select `disabled` styles
31
40
 
32
-
33
41
  ## 1.0.5
34
42
 
35
43
  * Rechecking npm security changes
@@ -1,8 +1,7 @@
1
1
  import type { StoryObj } from '@storybook/nextjs'
2
2
  import React from 'react'
3
- import { Button, IconButton } from '../atoms/Button'
3
+ import { IconButton } from '../atoms/Button'
4
4
  import { Header, type HeaderProps } from '../atoms/Header'
5
- import { Popover, PopoverMenu, PopoverMenuOption } from '../atoms/Popover'
6
5
 
7
6
  const meta = {
8
7
  title: 'Design System/Atoms/Header',
@@ -0,0 +1,169 @@
1
+ import type { StoryObj } from '@storybook/nextjs'
2
+ import React from 'react'
3
+ import { Button, IconButton } from '../atoms/Button'
4
+ import { CheckableTag, CheckableTagGroup } from '../atoms/CheckableTag'
5
+ import { Header } from '../atoms/Header'
6
+ import {
7
+ NotificationCenter,
8
+ NotificationEmptyState,
9
+ NotificationHeader,
10
+ NotificationLine,
11
+ NotificationList,
12
+ } from '../atoms/Notification'
13
+ import type { NotificationCenterProps } from '../atoms/Notification/NotificationCenter'
14
+
15
+ const meta = {
16
+ title: 'Design System/Atoms/Notification',
17
+ component: NotificationCenter,
18
+ parameters: {
19
+ docs: {
20
+ description: {
21
+ component:
22
+ '<h2>Usage guidelines</h2>' +
23
+ '<p>The <code>Header</code> component is used to provide consistent branding and global navigation across the application.</p>' +
24
+ '<ul>' +
25
+ ' <li>Use it at the top of every page to display the logo, user menu, and language selector</li>' +
26
+ ' <li>Keep the header content minimal and focused on global actions only</li>' +
27
+ ' <li>Position utility items (language selector, user menu) on the right side for better accessibility</li>' +
28
+ ' <li>Maintain consistent height and styling across all pages</li>' +
29
+ '</ul>',
30
+ },
31
+ },
32
+ },
33
+ tags: ['autodocs', 'Feedback components', 'Layout components'],
34
+ argTypes: {
35
+ children: {
36
+ description: 'Header elements are passed as children',
37
+ },
38
+ },
39
+ }
40
+
41
+ export default meta
42
+ type Story = StoryObj<typeof meta>
43
+
44
+ export const Primary: Story = {
45
+ render: (props: NotificationCenterProps) => (
46
+ <Header {...props}>
47
+ <h1>Header Title</h1>
48
+
49
+ <div style={{ display: 'flex', gap: '10px', marginRight: '30px' }}>
50
+ <NotificationCenter {...props}>
51
+ <NotificationHeader title="Notifications">
52
+ <CheckableTagGroup>
53
+ <CheckableTag
54
+ variant="primary"
55
+ label="All"
56
+ aria-label="All notifications"
57
+ onSelect={() => alert('click')}
58
+ isChecked={true}
59
+ />
60
+ <CheckableTag
61
+ variant="primary"
62
+ label="Errors"
63
+ aria-label="Notification errors"
64
+ onSelect={() => alert('click')}
65
+ isChecked={false}
66
+ />
67
+ <CheckableTag
68
+ variant="primary"
69
+ label="Updates"
70
+ aria-label="Notification updates"
71
+ onSelect={() => alert('click')}
72
+ isChecked={false}
73
+ />
74
+ </CheckableTagGroup>
75
+ <Button
76
+ onClick={() => alert('mark all as read')}
77
+ label="Mark all as read"
78
+ />
79
+ </NotificationHeader>
80
+ <NotificationList>
81
+ <NotificationLine
82
+ title="Trophy: Parting on Good Terms"
83
+ date="Jan 04"
84
+ onClick={() => alert('Notification clicked')}
85
+ >
86
+ Good terms? No. I&apos;m here to burn bridges.
87
+ </NotificationLine>
88
+ <NotificationLine title="New operating system update" date="Jan 02">
89
+ Enjoy the new UI and performance improvements. <br /> Contact{' '}
90
+ <a href="support@fakemail.com">support@fakemail.com</a> for help.
91
+ </NotificationLine>
92
+ <NotificationLine title="Trophy: Shell of the Past" date="Dec 30">
93
+ What exactly have we learned?
94
+ </NotificationLine>
95
+ <NotificationLine
96
+ title="New SHf game patch update"
97
+ date="Dec 29"
98
+ isRead={true}
99
+ >
100
+ Fixes a bug when saving the game in certain conditions.
101
+ </NotificationLine>
102
+ <NotificationLine
103
+ title="Trophy: On the Way to School"
104
+ date="Dec 28"
105
+ isRead={true}
106
+ onClick={() => alert('Notification clicked')}
107
+ >
108
+ The little sparrow escapes with its friends, wings flapping
109
+ nervously...
110
+ </NotificationLine>
111
+ </NotificationList>
112
+ </NotificationCenter>
113
+ <IconButton
114
+ icon="UserMenu"
115
+ accessibilityLabel="User menu button"
116
+ variant="secondary"
117
+ />
118
+ </div>
119
+ </Header>
120
+ ),
121
+ } as unknown as Story
122
+
123
+ export const EmptyState: Story = {
124
+ render: (props: NotificationCenterProps) => (
125
+ <Header {...props}>
126
+ <h1>Header Title</h1>
127
+
128
+ <div style={{ display: 'flex', gap: '10px', marginRight: '30px' }}>
129
+ <NotificationCenter {...props}>
130
+ <NotificationHeader title="Notifications">
131
+ <CheckableTagGroup>
132
+ <CheckableTag
133
+ variant="primary"
134
+ label="All"
135
+ aria-label="All notifications"
136
+ onSelect={() => alert('click')}
137
+ isChecked={true}
138
+ />
139
+ <CheckableTag
140
+ variant="primary"
141
+ label="Errors"
142
+ aria-label="Notification errors"
143
+ onSelect={() => alert('click')}
144
+ isChecked={false}
145
+ />
146
+ <CheckableTag
147
+ variant="primary"
148
+ label="Updates"
149
+ aria-label="Notification updates"
150
+ onSelect={() => alert('click')}
151
+ isChecked={false}
152
+ />
153
+ </CheckableTagGroup>
154
+ <Button
155
+ onClick={() => alert('mark all as read')}
156
+ label="Mark all as read"
157
+ />
158
+ </NotificationHeader>
159
+ <NotificationEmptyState title="No new notifications" date="Jan 21" />
160
+ </NotificationCenter>
161
+ <IconButton
162
+ icon="UserMenu"
163
+ accessibilityLabel="User menu button"
164
+ variant="secondary"
165
+ />
166
+ </div>
167
+ </Header>
168
+ ),
169
+ } as unknown as Story
@@ -0,0 +1,155 @@
1
+ import type { StoryObj } from '@storybook/nextjs'
2
+ import React from 'react'
3
+ import { Drawer } from '../atoms/Drawer'
4
+ import { Milestone, Timeline } from '../atoms/Timeline'
5
+ import { LoadMoreMilestonesButton } from '../atoms/Timeline/LoadMoreMilestonesButton'
6
+
7
+ const figmaPrimaryDesign = {
8
+ design: {
9
+ type: 'figma',
10
+ url: 'https://www.figma.com/design/DN2ova21vWqCRvPspBXgI1/Design-System?node-id=4786-71&m=dev',
11
+ },
12
+ }
13
+
14
+ const meta = {
15
+ title: 'Design System/Atoms/Timeline',
16
+ component: Timeline,
17
+ tags: ['autodocs', 'Data display components'],
18
+ argTypes: {
19
+ variant: {
20
+ description: 'Component variant used',
21
+ },
22
+ },
23
+ parameters: {
24
+ docs: {
25
+ description: {
26
+ component:
27
+ '<h2>Usage guidelines</h2>' +
28
+ '<p>The vertical display Timeline component shows progress through a workflow or chronological events in a linear order.</p>' +
29
+ '<ul>' +
30
+ ' <li>Use it when a series of information needs to be ordered by time (ascending or descending).</li>' +
31
+ ' <li>Use it when you need a timeline to make a visual connection.</li>' +
32
+ ' <li>The milestone title can either be a button or a link.</li>' +
33
+ '</ul>',
34
+ },
35
+ },
36
+ figmaPrimaryDesign,
37
+ },
38
+ }
39
+
40
+ export default meta
41
+ type Story = StoryObj<typeof meta>
42
+
43
+ export const TimelineDrawer: Story = {
44
+ render: () => (
45
+ <Drawer id="timeline-drawer" title="1986" onClose={() => alert('close')}>
46
+ <div style={{ marginTop: '20px' }}>
47
+ <LoadMoreMilestonesButton
48
+ onClick={() => alert('more milestones')}
49
+ variant="primary"
50
+ title="View previous"
51
+ leftIcon="AngleUp"
52
+ />
53
+ <Timeline>
54
+ <Milestone
55
+ title="1985"
56
+ description="Nintendo
57
+ NES launches with breakout hits like Super
58
+ Mario Bros.
59
+ Sega launches its Master System."
60
+ />
61
+ <Milestone
62
+ isSelected
63
+ title="1986"
64
+ description="Legend of Zelda comes out, spawning a long series of popular games."
65
+ />
66
+ <Milestone
67
+ title="1988"
68
+ description="Sega's 16-bit
69
+ Mega Drive is introduced, becoming the company's most successful system."
70
+ />
71
+ <Milestone
72
+ title="1989"
73
+ description="Nintendo's handheld
74
+ Game Boy hits the market."
75
+ />
76
+ <Milestone
77
+ title="1991"
78
+ description="Sega's hit Sonic the Hedgehog and the 16-bit
79
+ Super Nintendo come out."
80
+ />
81
+ <Milestone
82
+ title="1994"
83
+ description="32-bit consoles like the Sega Saturn, Sony PlayStation, and NEC PC-FX launch."
84
+ />
85
+ </Timeline>
86
+ <LoadMoreMilestonesButton
87
+ onClick={() => alert('more milestones')}
88
+ variant="primary"
89
+ title="View next"
90
+ leftIcon="AngleDown"
91
+ />
92
+ </div>
93
+ </Drawer>
94
+ ),
95
+ } as unknown as Story
96
+
97
+ export const TimelineWithLinksDrawer: Story = {
98
+ render: () => (
99
+ <Drawer id="timeline-drawer" title="1986" onClose={() => alert('close')}>
100
+ <div style={{ marginTop: '20px' }}>
101
+ <LoadMoreMilestonesButton
102
+ onClick={() => alert('more milestones')}
103
+ variant="primary"
104
+ title="View previous"
105
+ leftIcon="AngleUp"
106
+ />
107
+ <Timeline>
108
+ <Milestone
109
+ title="1985"
110
+ description="Nintendo
111
+ NES launches with breakout hits like Super
112
+ Mario Bros.
113
+ Sega launches its Master System."
114
+ href="link.com"
115
+ />
116
+ <Milestone
117
+ isSelected
118
+ title="1986"
119
+ description="Legend of Zelda comes out, spawning a long series of popular games."
120
+ href="link.com"
121
+ />
122
+ <Milestone
123
+ title="1988"
124
+ description="Sega's 16-bit
125
+ Mega Drive is introduced, becoming the company's most successful system."
126
+ href="link.com"
127
+ />
128
+ <Milestone
129
+ title="1989"
130
+ description="Nintendo's handheld
131
+ Game Boy hits the market."
132
+ href="link.com"
133
+ />
134
+ <Milestone
135
+ title="1991"
136
+ description="Sega's hit Sonic the Hedgehog and the 16-bit
137
+ Super Nintendo come out."
138
+ href="link.com"
139
+ />
140
+ <Milestone
141
+ title="1994"
142
+ description="32-bit consoles like the Sega Saturn, Sony PlayStation, and NEC PC-FX launch."
143
+ href="link.com"
144
+ />
145
+ </Timeline>
146
+ <LoadMoreMilestonesButton
147
+ onClick={() => alert('more milestones')}
148
+ variant="primary"
149
+ title="View next"
150
+ leftIcon="AngleDown"
151
+ />
152
+ </div>
153
+ </Drawer>
154
+ ),
155
+ } as unknown as Story
@@ -0,0 +1,53 @@
1
+ import { render } from '@testing-library/react'
2
+ import React from 'react'
3
+ import { Milestone, Timeline } from '../src/atoms/Timeline'
4
+ import { LoadMoreMilestonesButton } from '../src/atoms/Timeline/LoadMoreMilestonesButton'
5
+
6
+ describe('Timeline', () => {
7
+ it('renders with expected content and buttons', () => {
8
+ const { getByText, getAllByRole } = render(
9
+ <>
10
+ <LoadMoreMilestonesButton
11
+ onClick={() => jest.fn()}
12
+ variant="primary"
13
+ title="View previous"
14
+ leftIcon="AngleUp"
15
+ />
16
+ <Timeline>
17
+ <Milestone
18
+ title="1985"
19
+ description="Nintendo
20
+ NES launches with breakout hits like Super
21
+ Mario Bros.
22
+ Sega launches its Master System."
23
+ />
24
+ <Milestone
25
+ isSelected
26
+ title="1986"
27
+ description="Legend of Zelda comes out, spawning a long series of popular games."
28
+ />
29
+ </Timeline>
30
+ <LoadMoreMilestonesButton
31
+ onClick={() => jest.fn()}
32
+ variant="primary"
33
+ title="View next"
34
+ leftIcon="AngleDown"
35
+ />
36
+ </>,
37
+ )
38
+ expect(getByText('1985')).toBeInTheDocument()
39
+ expect(getByText(/Nintendo NES launches with/i)).toBeInTheDocument()
40
+ expect(getByText('1986')).toBeInTheDocument()
41
+ expect(getByText(/Legend of Zelda comes out/i)).toBeInTheDocument()
42
+ expect(getAllByRole('listitem')[0]).toHaveClass('milestone-item')
43
+ expect(getAllByRole('listitem')[1]).toHaveClass('milestone-item selected')
44
+ expect(getAllByRole('button')[0]).toBeInTheDocument()
45
+ expect(getAllByRole('button')[1]).toBeInTheDocument()
46
+ expect(getAllByRole('button')[0]).toHaveClass(
47
+ 'load-more-milestones-button primary',
48
+ )
49
+ expect(getAllByRole('button')[1]).toHaveClass(
50
+ 'load-more-milestones-button primary',
51
+ )
52
+ })
53
+ })
package/tests/library.ts CHANGED
@@ -36,3 +36,4 @@ export * as Select from '../src/stories/Select.stories'
36
36
  export * as Switch from '../src/stories/Switch.stories'
37
37
  export * as TabMenu from '../src/stories/TabMenu.stories'
38
38
  export * as TextArea from '../src/stories/TextArea.stories'
39
+ export * as Timeline from '../src/stories/Timeline.stories'