Skip to content

Commit

Permalink
feat(ui): add toast component
Browse files Browse the repository at this point in the history
  • Loading branch information
xiejay97 committed Jan 2, 2022
1 parent 16b1fdb commit 95c643f
Show file tree
Hide file tree
Showing 21 changed files with 744 additions and 29 deletions.
3 changes: 2 additions & 1 deletion packages/site/src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useLocation } from 'react-router-dom';

import { DRoot, NotificationService } from '@react-devui/ui';
import { DRoot, NotificationService, ToastService } from '@react-devui/ui';
import { useAsync } from '@react-devui/ui/hooks';

import { environment } from '../environments/environment';
Expand Down Expand Up @@ -59,6 +59,7 @@ export function App() {
const location = useLocation();
useEffect(() => {
NotificationService.closeAll(false);
ToastService.closeAll(false);
}, [location]);

const contextValue = useMemo<AppContextData>(
Expand Down
1 change: 1 addition & 0 deletions packages/site/src/app/styles/_app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,7 @@ h3 {
section[id^='Button'],
section[id^='Dropdown'],
section[id^='Tooltip'],
section[id^='Toast'],
section[id^='Notification'] {
.d-button {
margin-right: 8px;
Expand Down
3 changes: 3 additions & 0 deletions packages/ui/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export { DTag } from './tag';
export type { DTextareaProps } from './textarea';
export { DTextarea } from './textarea';

export type { DToastProps } from './toast';
export { ToastService } from './toast';

export type { DTooltipProps } from './tooltip';
export { DTooltip } from './tooltip';

Expand Down
2 changes: 1 addition & 1 deletion packages/ui/src/components/notification/Notification.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ export function DNotification(props: DNotificationProps & { dVisible: boolean })
'leave-to': {
height: '0',
opacity: '0',
margin: '0',
marginBottom: '0',
transition: 'height 166ms ease-in, opacity 166ms ease-in, margin 166ms ease-in',
},
};
Expand Down
35 changes: 27 additions & 8 deletions packages/ui/src/components/pagination/Pagination.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export function DPagination(props: DPaginationProps) {
}
)}
role="button"
tabIndex={dDisabled ? undefined : 0}
tabIndex={0}
title={t('Previous page')}
aria-disabled={active === 1}
onClick={() => {
Expand Down Expand Up @@ -152,7 +152,7 @@ export function DPagination(props: DPaginationProps) {
[`${dPrefix}pagination__item--border`]: !(dCustomRender && dCustomRender.next),
})}
role="button"
tabIndex={dDisabled ? undefined : 0}
tabIndex={0}
title={t('Next page')}
aria-disabled={active === lastPage}
onClick={() => {
Expand Down Expand Up @@ -180,7 +180,7 @@ export function DPagination(props: DPaginationProps) {
},
nextNode,
];
}, [active, changeActive, dCompose, dCustomRender, dDisabled, dPrefix, lastPage, t]);
}, [active, changeActive, dCompose, dCustomRender, dPrefix, lastPage, t]);

const sizeNode = useMemo(() => {
const options = dPageSizeOptions.map((size) => ({
Expand Down Expand Up @@ -251,6 +251,25 @@ export function DPagination(props: DPaginationProps) {
return null;
}, [changeActive, dCompose, dCustomRender, dDisabled, dMini, dPrefix, jumpValue, lastPage, t]);

const handleMouseDown = useCallback<React.MouseEventHandler<HTMLElement>>(
(e) => {
if (dDisabled) {
e.preventDefault();
e.stopPropagation();
}
},
[dDisabled]
);
const handleClick = useCallback<React.MouseEventHandler<HTMLElement>>(
(e) => {
if (dDisabled) {
e.preventDefault();
e.stopPropagation();
}
},
[dDisabled]
);

return (
<nav
{...restProps}
Expand All @@ -259,7 +278,7 @@ export function DPagination(props: DPaginationProps) {
'is-disabled': dDisabled,
'is-change': isChange,
})}
tabIndex={dDisabled ? undefined : -1}
tabIndex={-1}
role="navigation"
aria-label="Pagination Navigation"
>
Expand Down Expand Up @@ -303,7 +322,7 @@ export function DPagination(props: DPaginationProps) {
}

return (
<ul key="pages" className={`${dPrefix}pagination__list`}>
<ul key="pages" className={`${dPrefix}pagination__list`} onMouseDownCapture={handleMouseDown} onClickCapture={handleClick}>
{prevNode}
{pages.map((n) => {
if (n === 'prev5') {
Expand All @@ -316,7 +335,7 @@ export function DPagination(props: DPaginationProps) {
`${dPrefix}pagination__item--jump5`
)}
role="button"
tabIndex={dDisabled ? undefined : 0}
tabIndex={0}
title={t('5 pages forward')}
onClick={() => {
changeActive(Math.max(active - 5, 1));
Expand Down Expand Up @@ -344,7 +363,7 @@ export function DPagination(props: DPaginationProps) {
`${dPrefix}pagination__item--jump5`
)}
role="button"
tabIndex={dDisabled ? undefined : 0}
tabIndex={0}
title={t('5 pages backward')}
onClick={() => {
changeActive(Math.min(active + 5, lastPage));
Expand Down Expand Up @@ -374,7 +393,7 @@ export function DPagination(props: DPaginationProps) {
'is-active': active === n,
}
)}
tabIndex={dDisabled ? undefined : 0}
tabIndex={0}
onClick={() => {
changeActive(n);
}}
Expand Down
2 changes: 2 additions & 0 deletions packages/ui/src/components/root/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useEffect } from 'react';

import { DConfigContext } from '../../hooks/d-config';
import { Notification } from './Notification';
import { Toast } from './Toast';

export interface DRootProps extends DConfigContextData {
children: React.ReactNode;
Expand All @@ -26,6 +27,7 @@ export function DRoot(props: DRootProps) {
<DConfigContext.Provider value={restProps}>
{children}
<Notification></Notification>
<Toast></Toast>
</DConfigContext.Provider>
);
}
118 changes: 118 additions & 0 deletions packages/ui/src/components/root/Toast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Subscription } from 'rxjs';

import { useEffect, useMemo } from 'react';
import ReactDOM from 'react-dom';

import { useImmer, usePrefixConfig } from '../../hooks';
import { DToast, ToastService, toastSubject } from '../toast';

export function Toast() {
//#region Context
const dPrefix = usePrefixConfig();
//#endregion

const [toasts, setToasts] = useImmer(new Map<number, any>());

useEffect(() => {
const obs: Subscription[] = [];
const mergeProps = (uniqueId: number, props: any) => {
return {
onClose: () => {
props.onClose?.();

ToastService.close(uniqueId);
},
afterVisibleChange: (visible: boolean) => {
props.afterVisibleChange?.(visible);

if (!visible) {
setToasts((draft) => {
draft.delete(uniqueId);
});
}
},
};
};
obs.push(
toastSubject.open.subscribe({
next: ({ uniqueId, props }) => {
setToasts((draft) => {
draft.set(uniqueId, { ...props, dVisible: true, ...mergeProps(uniqueId, props) });
});
},
}),
toastSubject.close.subscribe({
next: (uniqueId) => {
setToasts((draft) => {
const props = draft.get(uniqueId);
if (props) {
draft.set(uniqueId, { ...props, dVisible: false });
}
});
},
}),
toastSubject.rerender.subscribe({
next: ({ uniqueId, props: newProps }) => {
setToasts((draft) => {
const props = draft.get(uniqueId);
if (props) {
draft.set(uniqueId, { ...newProps, dVisible: props.dVisible, ...mergeProps(uniqueId, newProps) });
}
});
},
}),
toastSubject.closeAll.subscribe({
next: (animation) => {
setToasts((draft) => {
if (animation) {
for (const props of draft.values()) {
props.dVisible = false;
}
} else {
draft.clear();
}
});
},
})
);
}, [setToasts]);

const [toastTRoot, toastBRoot] = useMemo(() => {
const getRoot = (id: string) => {
let root = document.getElementById(`${dPrefix}toast-root`);
if (!root) {
root = document.createElement('div');
root.id = `${dPrefix}toast-root`;
document.body.appendChild(root);
}

let el = document.getElementById(id);
if (!el) {
el = document.createElement('div');
el.id = id;
root.appendChild(el);
}
return el;
};

return [getRoot(`${dPrefix}toast-t-root`), getRoot(`${dPrefix}toast-b-root`)];
}, [dPrefix]);

return (
<>
{ReactDOM.createPortal(
Array.from(toasts.entries())
.filter(([uniqueId, toastProps]) => (toastProps.dPlacement ?? 'top') === 'top')
.map(([uniqueId, toastProps]) => <DToast key={uniqueId} {...toastProps}></DToast>),
toastTRoot
)}
{ReactDOM.createPortal(
Array.from(toasts.entries())
.filter(([uniqueId, toastProps]) => toastProps.dPlacement === 'bottom')
.map(([uniqueId, toastProps]) => <DToast key={uniqueId} {...toastProps}></DToast>),
toastBRoot
)}
</>
);
}
64 changes: 64 additions & 0 deletions packages/ui/src/components/toast/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
group: Feedback
title: Toast
---

Toast.

## When To Use

Global display operation feedback information.

## API

### DToastProps

Extend `React.HTMLAttributes<HTMLDivElement>`.

<!-- prettier-ignore-start -->
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| dType | Toast type | 'success' \| 'warning' \| 'error' \| 'info' | - |
| dIcon | Custom toast icon | React.ReactNode | - |
| dContent | Content | React.ReactNode | - |
| dDuration | Display duration, will not be closed automatically when it is 0 | number | 9.6 |
| dPlacement | Toast pop-up direction | 'top' \| 'bottom' | 'top' |
| onClose | Callback when the toast is closed | `() => void` | - |
| afterVisibleChange | Callback to the end of the opening/closing animation | `(visible: boolean) => void` | - |
<!-- prettier-ignore-end -->

### ToastService

```tsx
class ToastService {
// Toast list
static readonly toasts: Toast[];

// Open toast
static open(props: DToastProps): Toast;

// Close toast
static close(uniqueId: number): void;

// Update toast
static rerender(uniqueId: number, props: DToastProps): void;

// Close all toasts
static closeAll(animation = true): void;
}
```

### Toast

```tsx
class Toast {
// Uniquely identifies
readonly uniqueId: number;

// Close toast
close(): void;

// Update toast
rerender(props: DToastProps): void;
}
```
Loading

0 comments on commit 95c643f

Please sign in to comment.