Skip to content

Commit

Permalink
feat(Notifications): static component implementation (DATAUI-1582) (#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ruminat authored Jun 26, 2023
1 parent b4392ad commit 3016936
Show file tree
Hide file tree
Showing 25 changed files with 1,028 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ node_modules
# Artifacts
dist
build
.DS_Store
7 changes: 6 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gravity-ui/components",
"version": "1.7.1",
"version": "1.7.2",
"description": "",
"license": "MIT",
"main": "./build/cjs/index.js",
Expand Down Expand Up @@ -37,7 +37,8 @@
"@gravity-ui/i18n": "^1.0.0",
"@gravity-ui/icons": "^1.1.0",
"lodash": "^4.17.21",
"resize-observer-polyfill": "^1.5.1"
"resize-observer-polyfill": "^1.5.1",
"tinygesture": "^2.0.0"
},
"devDependencies": {
"@commitlint/cli": "^17.0.0",
Expand Down
189 changes: 189 additions & 0 deletions src/components/Notification/Notification.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
@use '../variables';

$block: '.#{variables.$ns}notification';

$notificationSourceIconSize: 36px;

#{$block} {
display: flex;
padding: 12px;
gap: 12px;
border-radius: 4px;
box-sizing: border-box;
width: 100%;

&:hover {
background: var(--yc-color-base-simple-hover);
}

&__right {
display: flex;
flex-direction: column;
gap: 4px;
flex: 1;
overflow-x: hidden;
}

&__right-top-part {
display: flex;
align-items: center;
width: 100%;
overflow-x: hidden;
}

&__right-meta-and-title {
flex: 1;
min-width: 0;
overflow-x: hidden;
}

&__right-meta,
&__right-title {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

&__right-meta {
display: flex;
gap: 4px;
color: var(--yc-color-text-secondary);
}

&__right-title {
font-weight: 500;
font-size: 13px;
line-height: 18px;

color: var(--yc-color-text-primary);
}

&__right-content {
font-size: 13px;
line-height: 18px;

color: var(--yc-color-text-secondary);
}

&_unread {
background: var(--yc-color-base-selection);
&:hover {
background: var(--yc-color-base-selection-hover);
}
}

&__actions {
display: flex;
align-items: center;
flex-wrap: wrap;
}

&__actions_right-bottom-actions {
margin-top: 8px;
gap: 8px;
}

&__actions_right-side-actions {
opacity: 0;
}
&:hover &__actions_right-side-actions {
opacity: 1;
}
&_mobile &__actions_right-side-actions {
opacity: 1;
}

&__action_icon {
color: var(--yc-color-text-secondary);
}

&_theme_success {
border-left: 4px solid var(--yc-color-line-positive);
}
&_theme_info {
border-left: 4px solid var(--yc-color-line-info);
}
&_theme_warning {
border-left: 4px solid var(--yc-color-line-warning);
}
&_theme_danger {
border-left: 4px solid var(--yc-color-line-danger);
}

&__swipe-wrap {
width: 100%;
overflow: hidden;
}

&__swipe {
width: 200%;
display: flex;
overflow-x: hidden;
align-items: stretch;
}

&__swipe_position_notification#{&}__swipe_has-left {
transform: translateX(-25%);
}

&__notification-wrapper {
width: 50%;
transition: opacity 0.5s;
}

&__swipe-action-container {
display: flex;
align-items: center;
justify-content: center;
width: 25%;
}

&__swipe-action {
display: flex;
gap: 8px;
align-items: center;
justify-content: center;
height: 100%;
flex: 1;
}

&__swipe-action_theme_base {
background: var(--yc-color-base-misc);
}
&__swipe-action_theme_base &__swipe-action-icon {
background: var(--yc-color-text-misc);
}
&__swipe-action_theme_base &__swipe-action-text {
color: var(--yc-color-text-misc);
}

&__swipe-action_theme_warning {
background: var(--yc-color-base-warning);
}
&__swipe-action_theme_warning &__swipe-action-icon {
background: var(--yc-color-base-warning-heavy);
}
&__swipe-action_theme_warning &__swipe-action-text {
color: var(--yc-color-base-warning-heavy);
}

&__swipe-action_theme_danger {
background: var(--yc-color-base-danger);
}
&__swipe-action_theme_danger &__swipe-action-icon {
background: var(--yc-color-base-danger-heavy);
}
&__swipe-action_theme_danger &__swipe-action-text {
color: var(--yc-color-base-danger-heavy);
}

&__swipe-action-icon {
padding: 8px;
border-radius: 100%;
color: var(--yc-color-base-background);
}

&__swipe-action-text {
font-size: 16px;
}
}
89 changes: 89 additions & 0 deletions src/components/Notification/Notification.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import {Icon, Link, useMobile} from '@gravity-ui/uikit';
import React from 'react';
import {CnMods, block} from '../utils/cn';
import './Notification.scss';
import {NotificationProps, NotificationSourceProps} from './definitions';

const b = block('notification');

type Props = {notification: NotificationProps};

export const Notification = React.memo(function Notification(props: Props) {
const [mobile] = useMobile();
const {notification} = props;
const {title, content, formattedDate, source, unread, theme} = notification;

const modifiers: CnMods = {unread, theme, mobile};

return (
<div
className={b(modifiers, notification.className)}
onMouseEnter={notification.onMouseEnter}
onMouseLeave={notification.onMouseLeave}
onClick={notification.onClick}
>
{source ? <div className={b('left')}>{renderSourceIcon(source)}</div> : null}
<div className={b('right')}>
<div className={b('right-top-part')}>
<div className={b('right-meta-and-title')}>
<div className={b('right-meta')}>
{source?.title ? renderSourceTitle(source.title, source.href) : null}
{source?.title && formattedDate ? <span></span> : null}
{formattedDate ? (
<div className={b('right-date')}>{formattedDate}</div>
) : null}
</div>
{title ? <div className={b('right-title')}>{title}</div> : null}
</div>
{props.notification.sideActions ? (
<div className={b('actions', {'right-side-actions': true})}>
{props.notification.sideActions}
</div>
) : null}
</div>
<div className={b('right-content')}>{content}</div>
{props.notification.bottomActions ? (
<div className={b('actions', {'right-bottom-actions': true})}>
{props.notification.bottomActions}
</div>
) : null}
</div>
</div>
);
});

function renderSourceTitle(title: string, href: string | undefined): JSX.Element {
return href ? (
<Link className={b('right-source-title')} href={href} target="_blank" title={title}>
{title}
</Link>
) : (
<div className={b('right-source-title')} title={title}>
{title}
</div>
);
}

function renderSourceIcon(source: NotificationSourceProps): JSX.Element | null {
const iconElement = getIconElement(source);

if (!iconElement) return null;

return source.href ? (
<Link href={source.href} target="_blank">
{iconElement}
</Link>
) : (
iconElement
);
}

function getIconElement(source: NotificationSourceProps): JSX.Element | null {
if ('icon' in source && source.icon) {
return <Icon className={b('source-icon')} size={36} data={source.icon} />;
} else if ('imageSrc' in source && source.imageSrc) {
return <img className={b('source-icon')} src={source.imageSrc} />;
} else {
return null;
}
}
31 changes: 31 additions & 0 deletions src/components/Notification/NotificationAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {Button, Icon, Tooltip} from '@gravity-ui/uikit';
import React from 'react';
import {block} from '../utils/cn';
import './Notification.scss';
import {NotificationActionProps} from './definitions';

const b = block('notification');

type Props = {action: NotificationActionProps};

export const NotificationAction = React.memo(function NotificationAction({action}: Props) {
const content = renderContent(action);

const button = (
<Button
className={b('action', {icon: Boolean(action.icon)})}
view={action.view ?? 'flat'}
href={action.href}
target={action.target}
onClick={action.onClick}
>
{content}
</Button>
);

return action.icon ? <Tooltip content={action.text}>{button}</Tooltip> : button;
});

function renderContent(action: NotificationActionProps): React.ReactNode {
return action.icon ? <Icon data={action.icon} /> : action.text;
}
26 changes: 26 additions & 0 deletions src/components/Notification/NotificationSwipeAction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {Icon, IconData} from '@gravity-ui/uikit';
import React from 'react';
import {block} from '../utils/cn';
import './Notification.scss';

const b = block('notification');

type Props = {
icon: IconData;
text?: React.ReactNode;
theme?: 'base' | 'warning' | 'danger';
action?: () => void;
};

export const NotificationSwipeAction = React.memo(function NotificationSwipeAction(props: Props) {
const {icon, text, theme = 'base', action} = props;

return (
<div className={b('swipe-action', {theme})} onClick={action}>
<span className={b('swipe-action-icon')}>
<Icon data={icon} size={16} />
</span>
<span className={b('swipe-action-text')}>{text}</span>
</div>
);
});
Loading

0 comments on commit 3016936

Please sign in to comment.