Skip to content

Commit 3016936

Browse files
authored
feat(Notifications): static component implementation (DATAUI-1582) (#57)
1 parent b4392ad commit 3016936

25 files changed

+1028
-3
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ node_modules
88
# Artifacts
99
dist
1010
build
11+
.DS_Store

package-lock.json

+6-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@gravity-ui/components",
3-
"version": "1.7.1",
3+
"version": "1.7.2",
44
"description": "",
55
"license": "MIT",
66
"main": "./build/cjs/index.js",
@@ -37,7 +37,8 @@
3737
"@gravity-ui/i18n": "^1.0.0",
3838
"@gravity-ui/icons": "^1.1.0",
3939
"lodash": "^4.17.21",
40-
"resize-observer-polyfill": "^1.5.1"
40+
"resize-observer-polyfill": "^1.5.1",
41+
"tinygesture": "^2.0.0"
4142
},
4243
"devDependencies": {
4344
"@commitlint/cli": "^17.0.0",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
@use '../variables';
2+
3+
$block: '.#{variables.$ns}notification';
4+
5+
$notificationSourceIconSize: 36px;
6+
7+
#{$block} {
8+
display: flex;
9+
padding: 12px;
10+
gap: 12px;
11+
border-radius: 4px;
12+
box-sizing: border-box;
13+
width: 100%;
14+
15+
&:hover {
16+
background: var(--yc-color-base-simple-hover);
17+
}
18+
19+
&__right {
20+
display: flex;
21+
flex-direction: column;
22+
gap: 4px;
23+
flex: 1;
24+
overflow-x: hidden;
25+
}
26+
27+
&__right-top-part {
28+
display: flex;
29+
align-items: center;
30+
width: 100%;
31+
overflow-x: hidden;
32+
}
33+
34+
&__right-meta-and-title {
35+
flex: 1;
36+
min-width: 0;
37+
overflow-x: hidden;
38+
}
39+
40+
&__right-meta,
41+
&__right-title {
42+
overflow: hidden;
43+
text-overflow: ellipsis;
44+
white-space: nowrap;
45+
}
46+
47+
&__right-meta {
48+
display: flex;
49+
gap: 4px;
50+
color: var(--yc-color-text-secondary);
51+
}
52+
53+
&__right-title {
54+
font-weight: 500;
55+
font-size: 13px;
56+
line-height: 18px;
57+
58+
color: var(--yc-color-text-primary);
59+
}
60+
61+
&__right-content {
62+
font-size: 13px;
63+
line-height: 18px;
64+
65+
color: var(--yc-color-text-secondary);
66+
}
67+
68+
&_unread {
69+
background: var(--yc-color-base-selection);
70+
&:hover {
71+
background: var(--yc-color-base-selection-hover);
72+
}
73+
}
74+
75+
&__actions {
76+
display: flex;
77+
align-items: center;
78+
flex-wrap: wrap;
79+
}
80+
81+
&__actions_right-bottom-actions {
82+
margin-top: 8px;
83+
gap: 8px;
84+
}
85+
86+
&__actions_right-side-actions {
87+
opacity: 0;
88+
}
89+
&:hover &__actions_right-side-actions {
90+
opacity: 1;
91+
}
92+
&_mobile &__actions_right-side-actions {
93+
opacity: 1;
94+
}
95+
96+
&__action_icon {
97+
color: var(--yc-color-text-secondary);
98+
}
99+
100+
&_theme_success {
101+
border-left: 4px solid var(--yc-color-line-positive);
102+
}
103+
&_theme_info {
104+
border-left: 4px solid var(--yc-color-line-info);
105+
}
106+
&_theme_warning {
107+
border-left: 4px solid var(--yc-color-line-warning);
108+
}
109+
&_theme_danger {
110+
border-left: 4px solid var(--yc-color-line-danger);
111+
}
112+
113+
&__swipe-wrap {
114+
width: 100%;
115+
overflow: hidden;
116+
}
117+
118+
&__swipe {
119+
width: 200%;
120+
display: flex;
121+
overflow-x: hidden;
122+
align-items: stretch;
123+
}
124+
125+
&__swipe_position_notification#{&}__swipe_has-left {
126+
transform: translateX(-25%);
127+
}
128+
129+
&__notification-wrapper {
130+
width: 50%;
131+
transition: opacity 0.5s;
132+
}
133+
134+
&__swipe-action-container {
135+
display: flex;
136+
align-items: center;
137+
justify-content: center;
138+
width: 25%;
139+
}
140+
141+
&__swipe-action {
142+
display: flex;
143+
gap: 8px;
144+
align-items: center;
145+
justify-content: center;
146+
height: 100%;
147+
flex: 1;
148+
}
149+
150+
&__swipe-action_theme_base {
151+
background: var(--yc-color-base-misc);
152+
}
153+
&__swipe-action_theme_base &__swipe-action-icon {
154+
background: var(--yc-color-text-misc);
155+
}
156+
&__swipe-action_theme_base &__swipe-action-text {
157+
color: var(--yc-color-text-misc);
158+
}
159+
160+
&__swipe-action_theme_warning {
161+
background: var(--yc-color-base-warning);
162+
}
163+
&__swipe-action_theme_warning &__swipe-action-icon {
164+
background: var(--yc-color-base-warning-heavy);
165+
}
166+
&__swipe-action_theme_warning &__swipe-action-text {
167+
color: var(--yc-color-base-warning-heavy);
168+
}
169+
170+
&__swipe-action_theme_danger {
171+
background: var(--yc-color-base-danger);
172+
}
173+
&__swipe-action_theme_danger &__swipe-action-icon {
174+
background: var(--yc-color-base-danger-heavy);
175+
}
176+
&__swipe-action_theme_danger &__swipe-action-text {
177+
color: var(--yc-color-base-danger-heavy);
178+
}
179+
180+
&__swipe-action-icon {
181+
padding: 8px;
182+
border-radius: 100%;
183+
color: var(--yc-color-base-background);
184+
}
185+
186+
&__swipe-action-text {
187+
font-size: 16px;
188+
}
189+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {Icon, Link, useMobile} from '@gravity-ui/uikit';
2+
import React from 'react';
3+
import {CnMods, block} from '../utils/cn';
4+
import './Notification.scss';
5+
import {NotificationProps, NotificationSourceProps} from './definitions';
6+
7+
const b = block('notification');
8+
9+
type Props = {notification: NotificationProps};
10+
11+
export const Notification = React.memo(function Notification(props: Props) {
12+
const [mobile] = useMobile();
13+
const {notification} = props;
14+
const {title, content, formattedDate, source, unread, theme} = notification;
15+
16+
const modifiers: CnMods = {unread, theme, mobile};
17+
18+
return (
19+
<div
20+
className={b(modifiers, notification.className)}
21+
onMouseEnter={notification.onMouseEnter}
22+
onMouseLeave={notification.onMouseLeave}
23+
onClick={notification.onClick}
24+
>
25+
{source ? <div className={b('left')}>{renderSourceIcon(source)}</div> : null}
26+
<div className={b('right')}>
27+
<div className={b('right-top-part')}>
28+
<div className={b('right-meta-and-title')}>
29+
<div className={b('right-meta')}>
30+
{source?.title ? renderSourceTitle(source.title, source.href) : null}
31+
{source?.title && formattedDate ? <span></span> : null}
32+
{formattedDate ? (
33+
<div className={b('right-date')}>{formattedDate}</div>
34+
) : null}
35+
</div>
36+
{title ? <div className={b('right-title')}>{title}</div> : null}
37+
</div>
38+
{props.notification.sideActions ? (
39+
<div className={b('actions', {'right-side-actions': true})}>
40+
{props.notification.sideActions}
41+
</div>
42+
) : null}
43+
</div>
44+
<div className={b('right-content')}>{content}</div>
45+
{props.notification.bottomActions ? (
46+
<div className={b('actions', {'right-bottom-actions': true})}>
47+
{props.notification.bottomActions}
48+
</div>
49+
) : null}
50+
</div>
51+
</div>
52+
);
53+
});
54+
55+
function renderSourceTitle(title: string, href: string | undefined): JSX.Element {
56+
return href ? (
57+
<Link className={b('right-source-title')} href={href} target="_blank" title={title}>
58+
{title}
59+
</Link>
60+
) : (
61+
<div className={b('right-source-title')} title={title}>
62+
{title}
63+
</div>
64+
);
65+
}
66+
67+
function renderSourceIcon(source: NotificationSourceProps): JSX.Element | null {
68+
const iconElement = getIconElement(source);
69+
70+
if (!iconElement) return null;
71+
72+
return source.href ? (
73+
<Link href={source.href} target="_blank">
74+
{iconElement}
75+
</Link>
76+
) : (
77+
iconElement
78+
);
79+
}
80+
81+
function getIconElement(source: NotificationSourceProps): JSX.Element | null {
82+
if ('icon' in source && source.icon) {
83+
return <Icon className={b('source-icon')} size={36} data={source.icon} />;
84+
} else if ('imageSrc' in source && source.imageSrc) {
85+
return <img className={b('source-icon')} src={source.imageSrc} />;
86+
} else {
87+
return null;
88+
}
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import {Button, Icon, Tooltip} from '@gravity-ui/uikit';
2+
import React from 'react';
3+
import {block} from '../utils/cn';
4+
import './Notification.scss';
5+
import {NotificationActionProps} from './definitions';
6+
7+
const b = block('notification');
8+
9+
type Props = {action: NotificationActionProps};
10+
11+
export const NotificationAction = React.memo(function NotificationAction({action}: Props) {
12+
const content = renderContent(action);
13+
14+
const button = (
15+
<Button
16+
className={b('action', {icon: Boolean(action.icon)})}
17+
view={action.view ?? 'flat'}
18+
href={action.href}
19+
target={action.target}
20+
onClick={action.onClick}
21+
>
22+
{content}
23+
</Button>
24+
);
25+
26+
return action.icon ? <Tooltip content={action.text}>{button}</Tooltip> : button;
27+
});
28+
29+
function renderContent(action: NotificationActionProps): React.ReactNode {
30+
return action.icon ? <Icon data={action.icon} /> : action.text;
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {Icon, IconData} from '@gravity-ui/uikit';
2+
import React from 'react';
3+
import {block} from '../utils/cn';
4+
import './Notification.scss';
5+
6+
const b = block('notification');
7+
8+
type Props = {
9+
icon: IconData;
10+
text?: React.ReactNode;
11+
theme?: 'base' | 'warning' | 'danger';
12+
action?: () => void;
13+
};
14+
15+
export const NotificationSwipeAction = React.memo(function NotificationSwipeAction(props: Props) {
16+
const {icon, text, theme = 'base', action} = props;
17+
18+
return (
19+
<div className={b('swipe-action', {theme})} onClick={action}>
20+
<span className={b('swipe-action-icon')}>
21+
<Icon data={icon} size={16} />
22+
</span>
23+
<span className={b('swipe-action-text')}>{text}</span>
24+
</div>
25+
);
26+
});

0 commit comments

Comments
 (0)