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

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",
3
+ "version": "1.2.0-beta.1",
4
4
  "scripts": {
5
5
  "dev": "npm run storybook",
6
6
  "storybook": "storybook dev -p 6006 --ci",
@@ -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,163 @@
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: 93vh;
148
+
149
+ .notification-header {
150
+ .notification-header-title {
151
+ border-radius: 0;
152
+ }
153
+ }
154
+
155
+ .notification-list {
156
+ height: 100%;
157
+ background-color: var(--neutral-white);
158
+ max-height: 100vh;
159
+ }
160
+ }
161
+ }
162
+ }
163
+ }
@@ -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
+ }
@@ -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'
@@ -108,6 +109,7 @@ export {
108
109
  Minus,
109
110
  More,
110
111
  NewView,
112
+ Notification,
111
113
  Orders,
112
114
  PDF,
113
115
  Picture,
@@ -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>
@@ -4,6 +4,10 @@ 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
@@ -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>
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