diff --git a/CHANGELOG.md b/CHANGELOG.md index cb29b2a..dc9ac48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 3.0.0 + +### Changes + +* TS support +* Added full-sized containers for top and bottom +* Changed SASS style to match BEM - *breaking change* +* Limit center containers to 350px +* Allow dynamic expansion of containers based on each notification's width +* Adedd new API call `removeAllNotifications` +* Removed `prop-types` package, props are now defaulted internally + +### Fixes + +* #82, #69, #67, #65, #63, #61 + ## 2.4.1 ### Changes diff --git a/README.md b/README.md index bdebef4..ee352af 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ [![npm version](https://badgen.net/npm/v/react-notifications-component)](https://www.npmjs.com/package/react-notifications-component) [![npm](https://img.shields.io/npm/dm/react-notifications-component.svg)](https://www.npmjs.com/package/react-notifications-component) [![Minified & Gzipped size](https://badgen.net/bundlephobia/minzip/react-notifications-component)](https://bundlephobia.com/result?p=react-notifications-component) # react-notifications-component -![Logo](https://raw.githubusercontent.com/teodosii/react-notifications-component/master/new-logo.png "Logo") - A delightful, easy to use and highly configurable component to help you notify your users out of the box. No messy setup, just beautiful notifications! > :fire: :fire: :fire: Interested in an animation library for React? I just launched `react-tweenful`, check it out here https://github.com/teodosii/react-tweenful diff --git a/new-logo.png b/new-logo.png deleted file mode 100644 index 85a9373..0000000 Binary files a/new-logo.png and /dev/null differ diff --git a/package-lock.json b/package-lock.json index ec3a05d..a3a072f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6289,28 +6289,28 @@ "dev": true }, "copy-webpack-plugin": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.0.3.tgz", - "integrity": "sha512-q5m6Vz4elsuyVEIUXr7wJdIdePWTubsqVbEMvf1WQnHGv0Q+9yPRu7MtYFPt+GBOXRav9lvIINifTQ1vSCs+eA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-6.1.1.tgz", + "integrity": "sha512-4TlkHFYkrZ3WppLA5XkPmBLI5lnEpFsXvpeqxCf5PzkratZiVklNXsvoQkLhUU43q7ZL3AOXtaHAd9jLNJoU0w==", "dev": true, "requires": { - "cacache": "^15.0.4", + "cacache": "^15.0.5", "fast-glob": "^3.2.4", "find-cache-dir": "^3.3.1", "glob-parent": "^5.1.1", "globby": "^11.0.1", "loader-utils": "^2.0.0", "normalize-path": "^3.0.0", - "p-limit": "^3.0.1", - "schema-utils": "^2.7.0", - "serialize-javascript": "^4.0.0", + "p-limit": "^3.0.2", + "schema-utils": "^2.7.1", + "serialize-javascript": "^5.0.1", "webpack-sources": "^1.4.3" }, "dependencies": { "ajv": { - "version": "6.12.4", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", - "integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -6319,6 +6319,12 @@ "uri-js": "^4.2.2" } }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, "array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -6519,14 +6525,14 @@ } }, "schema-utils": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz", - "integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", "dev": true, "requires": { - "@types/json-schema": "^7.0.4", - "ajv": "^6.12.2", - "ajv-keywords": "^3.4.1" + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" } }, "semver": { @@ -6535,6 +6541,15 @@ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", diff --git a/package.json b/package.json index 4164814..33196cb 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "babel-plugin-transform-class-properties": "^6.24.1", "bootstrap": "^4.3.1", "clean-webpack-plugin": "^3.0.0", - "copy-webpack-plugin": "^6.0.3", + "copy-webpack-plugin": "^6.1.1", "css-loader": "^4.2.1", "css-select-base-adapter": "^0.1.1", "eslint": "^7.7.0", @@ -92,8 +92,5 @@ }, "peerDependencies": { "react": "^16.0.0" - }, - "dependencies": { - "prop-types": "^15.6.2" } } diff --git a/samples/js/components/App.tsx b/samples/js/components/App.tsx index f00cbcd..0e25e23 100644 --- a/samples/js/components/App.tsx +++ b/samples/js/components/App.tsx @@ -10,7 +10,7 @@ export default function App() {

Examples

+
All notifications have been set to be automatically dismissed after{' '} diff --git a/samples/js/components/examples/ContainerExample.tsx b/samples/js/components/examples/ContainerExample.tsx index 9c4ef79..c6e6394 100644 --- a/samples/js/components/examples/ContainerExample.tsx +++ b/samples/js/components/examples/ContainerExample.tsx @@ -25,52 +25,59 @@ export default function ContainerExample() { onClick={() => add('top-left')} > Top Left - - {' '} + {' '} - {' '} + {' '} - {' '} + {' '} - {' '} + {' '} - {' '} + {' '} - {' '} + {' '} {' '} + {' '} + - {' '}
@@ -81,10 +88,12 @@ function add(container: string): string { const type = getType(); const object: iNotification = {}; - return store.addNotification(Object.assign(object, notification, { - title: getTitle(type), - message: getMessage(type), - container, - type - })); + return store.addNotification( + Object.assign(object, notification, { + title: getTitle(type), + message: getMessage(type), + container, + type + }) + ); } diff --git a/samples/js/components/examples/CustomContentExample.tsx b/samples/js/components/examples/CustomContentExample.tsx index 4339777..847ec06 100644 --- a/samples/js/components/examples/CustomContentExample.tsx +++ b/samples/js/components/examples/CustomContentExample.tsx @@ -84,12 +84,12 @@ function addCustomIcon(type: string, iconClassName: string): void { width: 275, container: getContainer(), content: ( -
-
+
+
-
-

{message}

+
+

{message}

) diff --git a/samples/js/components/examples/TypeExample.tsx b/samples/js/components/examples/TypeExample.tsx index 913ecc6..5ecb1b3 100644 --- a/samples/js/components/examples/TypeExample.tsx +++ b/samples/js/components/examples/TypeExample.tsx @@ -43,7 +43,7 @@ export default function TypeExample() { {' '} -
diff --git a/samples/js/helpers/notification.ts b/samples/js/helpers/notification.ts index 058970c..033570e 100644 --- a/samples/js/helpers/notification.ts +++ b/samples/js/helpers/notification.ts @@ -40,7 +40,7 @@ export default { }, dismiss: { - duration: 5000, + duration: 0, onScreen: false, pauseOnHover: true, waitForAnimation: false, diff --git a/samples/styles/_customTypes.scss b/samples/styles/_customTypes.scss index 3e3dfc5..2b08a99 100644 --- a/samples/styles/_customTypes.scss +++ b/samples/styles/_customTypes.scss @@ -1,4 +1,4 @@ -.notification-custom-icon { +.notification__custom-icon { flex-basis: 20%; position: relative; padding: 8px 8px 8px 12px; @@ -12,58 +12,58 @@ } } -.notification-custom-content { +.notification__custom { flex-basis: 80%; padding: 12px 12px 12px 8px; display: inline-block; } -.notification-custom-success { +.notification__custom--success { width: 100%; display: flex; background-color: #28a745; - .notification-custom-icon { + .notification__custom-icon { border-left: 8px solid darken(#28a745, 15%); } } -.notification-custom-default { +.notification__custom--default { width: 100%; display: flex; background-color: #007bff; - .notification-custom-icon { + .notification__custom-icon { border-left: 8px solid darken(#007bff, 15%); } } -.notification-custom-danger { +.notification__custom--danger { width: 100%; display: flex; background-color: #dc3545; - .notification-custom-icon { + .notification__custom-icon { border-left: 8px solid darken(#dc3545, 15%); } } -.notification-custom-info { +.notification__custom--info { width: 100%; display: flex; background-color: #17a2b8; - .notification-custom-icon { + .notification__custom-icon { border-left: 8px solid darken(#17a2b8, 15%); } } -.notification-custom-warning { +.notification__custom--warning { width: 100%; display: flex; background-color: #eab000; - .notification-custom-icon { + .notification__custom-icon { border-left: 8px solid darken(#eab000, 15%); } } diff --git a/samples/styles/stylesheet.scss b/samples/styles/stylesheet.scss index dbe0e1a..83eb9b8 100644 --- a/samples/styles/stylesheet.scss +++ b/samples/styles/stylesheet.scss @@ -13,10 +13,10 @@ body { html, body, -.notification-parent, -.notification-title, -.notification-message, -.notification-item { +.notification, +.notification__title, +.notification__message, +.notification__item { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol' !important; } diff --git a/src/components/Container.tsx b/src/components/Container.tsx index 60db4e4..c41e182 100644 --- a/src/components/Container.tsx +++ b/src/components/Container.tsx @@ -3,63 +3,52 @@ import React from 'react'; import ReactNotification from 'src/components/Notification'; import 'src/scss/notification.scss'; import store from 'src/store'; +import { DEFAULT_CONTAINER_VALUES as DCV } from 'src/utils/constants'; import { iNotification, iNotificationCustomType } from 'src/types/Notification'; -import { getNotificationsForEachContainer, getNotificationsForMobileView } from 'src/utils/helpers'; +import { + getNotificationsForEachContainer, + getNotificationsForMobileView, + isNull +} from 'src/utils/helpers'; interface iContainerProps { - isMobile: boolean; - breakpoint: number; - types: iNotificationCustomType[]; + isMobile?: boolean; + breakpoint?: number; + types?: iNotificationCustomType[]; + defaultNotificationWidth?: number; } interface iContainerState { isMobile: boolean; breakpoint: number; notifications: iNotification[]; - width: number; + windowWidth: number; } -const defaultProps = { - isMobile: true, - breakpoint: 768 -}; - -const propTypes = { - isMobile: PropTypes.bool, - breakpoint: PropTypes.number, - types: PropTypes.array -}; - class Container extends React.Component { constructor(props: iContainerProps) { super(props); this.state = { - isMobile: props.isMobile, - breakpoint: props.breakpoint, + isMobile: isNull(props.isMobile) ? DCV.isMobile : props.isMobile, + breakpoint: isNull(props.breakpoint) ? DCV.breakpoint : props.breakpoint, notifications: [], - width: undefined + windowWidth: undefined }; - - this.add = this.add.bind(this); - this.remove = this.remove.bind(this); - this.toggleRemoval = this.toggleRemoval.bind(this); - this.handleResize = this.handleResize.bind(this); } - static propTypes = propTypes; - static defaultProps = defaultProps; - componentDidMount() { - const { types } = this.props; + const { types, defaultNotificationWidth } = this.props; store.register({ - addNotification: this.add, - removeNotification: this.remove, - types - }); - - this.setState({ width: window.innerWidth }); + addNotification: this.add, + removeNotification: this.remove, + removeAllNotifications: this.removeAllNotifications, + defaultNotificationWidth: defaultNotificationWidth || DCV.defaultNotificationWidth, + types + }); + + this.setState({ windowWidth: window.innerWidth }); window.addEventListener('resize', this.handleResize); } @@ -67,51 +56,62 @@ class Container extends React.Component { window.removeEventListener('resize', this.handleResize); } - handleResize() { - this.setState({ width: window.innerWidth }); - } + handleResize = () => { + this.setState({ windowWidth: window.innerWidth }); + }; - add(notification: iNotification) { + add = (notification: iNotification) => { this.setState(({ notifications }) => ({ notifications: notification.insert === 'top' - ? [notification, ...notifications] - : [...notifications, notification] + ? [notification, ...notifications] + : [...notifications, notification] })); return notification.id; - } + }; - remove(id: string) { + remove = (id: string) => { this.setState(({ notifications }) => ({ notifications: notifications.map((notification) => { if (notification.id === id) { - notification.removed = true; + notification.hasBeenRemoved = true; } return notification; }) })); - } - - toggleRemoval(id: string, callback: () => void) { + }; + + removeAllNotifications = () => { + this.setState({ + notifications: this.state.notifications.map((notification) => ({ + ...notification, + hasBeenRemoved: true + })) + }); + }; + + toggleRemoval = (id: string, callback: () => void) => { this.setState( ({ notifications }) => ({ notifications: notifications.filter(({ id: nId }) => nId !== id) }), callback ); - } + }; - renderNotifications(notifications: iNotification[]) { + renderNotifications(notifications: iNotification[], isMobile: boolean) { return notifications.map((notification) => ( )); } @@ -120,13 +120,13 @@ class Container extends React.Component { const { className, id } = props; const { notifications } = this.state; const mobileNotifications = getNotificationsForMobileView(notifications); - const top = this.renderNotifications(mobileNotifications.top); - const bottom = this.renderNotifications(mobileNotifications.bottom); + const top = this.renderNotifications(mobileNotifications.top, true); + const bottom = this.renderNotifications(mobileNotifications.bottom, true); return ( -
-
{top}
-
{bottom}
+
+
{top}
+
{bottom}
); } @@ -135,34 +135,38 @@ class Container extends React.Component { const { className, id } = props; const { notifications } = this.state; const items = getNotificationsForEachContainer(notifications); - const topLeft = this.renderNotifications(items.topLeft); - const topRight = this.renderNotifications(items.topRight); - const topCenter = this.renderNotifications(items.topCenter); - const bottomLeft = this.renderNotifications(items.bottomLeft); - const bottomRight = this.renderNotifications(items.bottomRight); - const bottomCenter = this.renderNotifications(items.bottomCenter); - const center = this.renderNotifications(items.center); + const topFull = this.renderNotifications(items.topFull, false); + const bottomFull = this.renderNotifications(items.bottomFull, false); + const topLeft = this.renderNotifications(items.topLeft, false); + const topRight = this.renderNotifications(items.topRight, false); + const topCenter = this.renderNotifications(items.topCenter, false); + const bottomLeft = this.renderNotifications(items.bottomLeft, false); + const bottomRight = this.renderNotifications(items.bottomRight, false); + const bottomCenter = this.renderNotifications(items.bottomCenter, false); + const center = this.renderNotifications(items.center, false); return ( -
-
{topLeft}
-
{topRight}
-
{bottomLeft}
-
{bottomRight}
-
{topCenter}
-
-
{center}
+
+
{topFull}
+
{bottomFull}
+
{topLeft}
+
{topRight}
+
{bottomLeft}
+
{bottomRight}
+
{topCenter}
+
+
{center}
-
{bottomCenter}
+
{bottomCenter}
); } render() { const { isMobile } = this.props; - const { width, breakpoint } = this.state; + const { windowWidth, breakpoint } = this.state; - if (isMobile && width <= breakpoint) { + if (isMobile && windowWidth <= breakpoint) { return this.renderMobileNotifications(this.props); } diff --git a/src/components/Notification.tsx b/src/components/Notification.tsx index 93833b2..3a45b2b 100644 --- a/src/components/Notification.tsx +++ b/src/components/Notification.tsx @@ -1,15 +1,21 @@ -import PropTypes from 'prop-types'; import React from 'react'; import { iNotification } from 'src/types/Notification'; -import { REMOVAL } from '../utils/constants'; -import { getHtmlClassesForType, getTransition, hasFullySwiped, shouldNotificationHaveSliding } from '../utils/helpers'; +import { NOTIFICATION_CONTAINER as NC, REMOVAL } from '../utils/constants'; +import { + getHtmlClassesForType, + getTransition, + hasFullySwiped, + shouldNotificationHaveSliding +} from '../utils/helpers'; import Timer from '../utils/timer'; class iNotificationProps { id: string; notification: iNotification; - count: number; - removed: boolean; + defaultNotificationWidth: number; + notificationsCount: number; + isMobile: boolean; + hasBeenRemoved: boolean; toggleRemoval: (id: string, callback: () => void) => void; } @@ -35,36 +41,30 @@ class Notification extends React.Component; private timer: Timer; - static propTypes = { - toggleRemoval: PropTypes.func.isRequired, - count: PropTypes.number.isRequired, - removed: PropTypes.bool - }; - componentWillUnmount() { if (this.timer) { this.timer.clear(); @@ -72,17 +72,15 @@ class Notification extends React.Component { - if (!duration || onScreen) { - return; - } + if (!duration || onScreen) return; const callback = () => this.removeNotification(REMOVAL.TIMEOUT); this.timer = new Timer(callback, duration); }; @@ -108,8 +106,8 @@ class Notification extends React.Component toggleRemoval(id, () => onRemoval(id, removalFlag)); const parentStyle: iParentStyle = { height: `0px`, + overflow: 'hidden', transition: getTransition(notification.slidingExit, 'height') }; @@ -134,12 +133,12 @@ class Notification extends React.Component { this.setState({ - parentStyle: { - width, - ...parentStyle - }, - onTransitionEnd - }); + parentStyle: { + width, + ...parentStyle + }, + onTransitionEnd + }); } })); } @@ -154,16 +153,16 @@ class Notification extends React.Component { const { notification: { dismiss } } = this.props; if (dismiss.click || dismiss.showIcon) { this.removeNotification(REMOVAL.CLICK); } - } + }; - onTouchStart(event: React.TouchEvent) { + onTouchStart = (event: React.TouchEvent) => { const { pageX } = event.touches[0]; this.setState(({ parentStyle }) => ({ @@ -174,9 +173,9 @@ class Notification extends React.Component { const { pageX } = event.touches[0]; const { startX } = this.state; const { @@ -214,6 +213,7 @@ class Notification extends React.Component { const { notification: { touchRevert } } = this.props; @@ -243,23 +243,23 @@ class Notification extends React.Component { if (this.timer) { this.timer.pause(); } else { this.setState({ animationPlayState: 'paused' }); } - } + }; - onMouseLeave() { + onMouseLeave = () => { if (this.timer) { this.timer.resume(); } else { this.setState({ animationPlayState: 'running' }); } - } + }; renderTimer() { const { @@ -283,8 +283,12 @@ class Notification extends React.Component this.removeNotification(REMOVAL.TIMEOUT); return ( -
-
+
+
); } @@ -303,7 +307,7 @@ class Notification extends React.Component @@ -325,14 +329,14 @@ class Notification extends React.Component -
- {showIcon &&
} - {title &&
{title}
} -
{message}
+
+ {showIcon &&
} + {title &&
{title}
} +
{message}
{this.renderTimer()}
@@ -352,7 +356,7 @@ class Notification extends React.Component void; +interface iRegisterParams { + addNotification: (notification: iNotification) => string; + removeNotification: (id: string) => void; + removeAllNotifications: () => void; types: iNotificationCustomType[]; + defaultNotificationWidth: number; } class Store implements iStore { @@ -20,11 +22,15 @@ class Store implements iStore { this.add = null; } - public counter: number; public removeNotification: (id: string) => void; + public removeAllNotifications: () => void; private types: iNotificationCustomType[]; private add: (notification: iNotification) => string; + private defaultNotificationWidth: number; + private counter: number; + + private incrementCounter = () => (this.counter += 1); public addNotification(notification: iNotification) { if (process.env.NODE_ENV === 'development') { @@ -33,13 +39,31 @@ class Store implements iStore { validators.forEach((validator) => validator(notification, this.types)); } - this.counter += 1; - return this.add(parseNotification(notification, this.types)); + this.incrementCounter(); + + const parsedNotification = parseNotification( + notification, + this.types, + this.defaultNotificationWidth + ); + return this.add(parsedNotification); } - public register({ addNotification, removeNotification, types }) { + public getCounter = () => this.counter; + + public register(parameters: iRegisterParams) { + const { + addNotification, + removeNotification, + removeAllNotifications, + types, + defaultNotificationWidth + } = parameters; + this.add = addNotification; this.removeNotification = removeNotification; + this.removeAllNotifications = removeAllNotifications; + this.defaultNotificationWidth = defaultNotificationWidth; this.types = types; } } diff --git a/src/types/Notification.ts b/src/types/Notification.ts index afeb598..287c3b0 100644 --- a/src/types/Notification.ts +++ b/src/types/Notification.ts @@ -18,7 +18,7 @@ export interface iNotification { touchSlidingExit?: iTouchTransition; userDefinedTypes?: iNotificationCustomType[]; width?: number; - removed?: boolean + hasBeenRemoved?: boolean } export type NotificationTitleMessage = string | React.ReactNode | React.FunctionComponent; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index cc03f2e..e70850c 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1,4 +1,10 @@ -export const NOTIFICATION_BASE_CLASS = 'notification-item'; +export const DEFAULT_CONTAINER_VALUES = { + isMobile: true, + breakpoint: 768, + defaultNotificationWidth: 325 +}; + +export const NOTIFICATION_BASE_CLASS = 'notification__item'; export enum NOTIFICATION_CONTAINER { BOTTOM_LEFT = 'bottom-left', @@ -7,13 +13,15 @@ export enum NOTIFICATION_CONTAINER { TOP_LEFT = 'top-left', TOP_RIGHT = 'top-right', TOP_CENTER = 'top-center', - CENTER = 'center' -}; + CENTER = 'center', + TOP_FULL = 'top-full', + BOTTOM_FULL = 'bottom-full' +} export enum INSERTION { TOP = 'top', BOTTOM = 'bottom' -}; +} export enum NOTIFICATION_TYPE { SUCCESS = 'success', @@ -21,14 +29,14 @@ export enum NOTIFICATION_TYPE { INFO = 'info', DEFAULT = 'default', WARNING = 'warning' -}; +} export enum REMOVAL { TIMEOUT = 'timeout', CLICK = 'click', TOUCH = 'touch', MANUAL = 'manual' -}; +} export const ERROR = { ANIMATION_IN: 'Validation error. `animationIn` option must be an array', diff --git a/src/utils/helpers.ts b/src/utils/helpers.ts index 8bcdd8e..3abe2c7 100644 --- a/src/utils/helpers.ts +++ b/src/utils/helpers.ts @@ -1,5 +1,11 @@ import store from 'src/store'; -import { iDismiss, iNotification, iNotificationCustomType, iTouchTransition, iTransition } from 'src/types/Notification'; +import { + iDismiss, + iNotification, + iNotificationCustomType, + iTouchTransition, + iTransition +} from 'src/types/Notification'; import { INSERTION, NOTIFICATION_BASE_CLASS, @@ -8,10 +14,11 @@ import { NOTIFICATION_TYPE as NT } from 'src/utils/constants'; -const isNull = (object: any) => object === null || object === undefined; +export const isNull = (object: any) => object === null || object === undefined; export function isBottomContainer(container: string) { return ( + container === NOTIFICATION_CONTAINER.BOTTOM_FULL || container === NOTIFICATION_CONTAINER.BOTTOM_LEFT || container === NOTIFICATION_CONTAINER.BOTTOM_RIGHT || container === NOTIFICATION_CONTAINER.BOTTOM_CENTER @@ -20,6 +27,7 @@ export function isBottomContainer(container: string) { export function isTopContainer(container: string) { return ( + container === NOTIFICATION_CONTAINER.TOP_FULL || container === NOTIFICATION_CONTAINER.TOP_LEFT || container === NOTIFICATION_CONTAINER.TOP_RIGHT || container === NOTIFICATION_CONTAINER.TOP_CENTER @@ -40,23 +48,23 @@ export function shouldNotificationHaveSliding(notification: iNotification, count return ( count > 1 && ((notification.insert === INSERTION.TOP && isTopContainer(notification.container)) || - (notification.insert === INSERTION.BOTTOM && isBottomContainer(notification.container)) || - notification.container === NOTIFICATION_CONTAINER.CENTER) + (notification.insert === INSERTION.BOTTOM && isBottomContainer(notification.container)) || + notification.container === NOTIFICATION_CONTAINER.CENTER) ); } export function htmlClassesForExistingType(type: NOTIFICATION_TYPE) { switch (type) { case NT.DEFAULT: - return [NOTIFICATION_BASE_CLASS, 'notification-default']; + return [NOTIFICATION_BASE_CLASS, 'notification__item--default']; case NT.SUCCESS: - return [NOTIFICATION_BASE_CLASS, 'notification-success']; + return [NOTIFICATION_BASE_CLASS, 'notification__item--success']; case NT.DANGER: - return [NOTIFICATION_BASE_CLASS, 'notification-danger']; + return [NOTIFICATION_BASE_CLASS, 'notification__item--danger']; case NT.WARNING: - return [NOTIFICATION_BASE_CLASS, 'notification-warning']; + return [NOTIFICATION_BASE_CLASS, 'notification__item--warning']; case NT.INFO: - return [NOTIFICATION_BASE_CLASS, 'notification-info']; + return [NOTIFICATION_BASE_CLASS, 'notification__item--info']; default: return [NOTIFICATION_BASE_CLASS]; } @@ -78,8 +86,8 @@ export function getHtmlClassesForType(notification: iNotification) { } export function getNotificationsForMobileView(notifications: iNotification[]) { - const top = []; - const bottom = []; + const top: iNotification[] = []; + const bottom: iNotification[] = []; notifications.forEach((notification) => { const { container } = notification; @@ -96,17 +104,23 @@ export function getNotificationsForMobileView(notifications: iNotification[]) { } export function getNotificationsForEachContainer(notifications: iNotification[]) { - const topLeft = []; - const topRight = []; - const topCenter = []; - const bottomLeft = []; - const bottomRight = []; - const bottomCenter = []; - const center = []; + const topLeft: iNotification[] = []; + const topRight: iNotification[] = []; + const topCenter: iNotification[] = []; + const bottomLeft: iNotification[] = []; + const bottomRight: iNotification[] = []; + const bottomCenter: iNotification[] = []; + const center: iNotification[] = []; + const topFull: iNotification[] = []; + const bottomFull: iNotification[] = []; notifications.forEach((notification) => { const { container } = notification; - if (container === NOTIFICATION_CONTAINER.TOP_LEFT) { + if (container === NOTIFICATION_CONTAINER.TOP_FULL) { + topFull.push(notification); + } else if (container === NOTIFICATION_CONTAINER.BOTTOM_FULL) { + bottomFull.push(notification); + } else if (container === NOTIFICATION_CONTAINER.TOP_LEFT) { topLeft.push(notification); } else if (container === NOTIFICATION_CONTAINER.TOP_RIGHT) { topRight.push(notification); @@ -124,6 +138,8 @@ export function getNotificationsForEachContainer(notifications: iNotification[]) }); return { + topFull, + bottomFull, topLeft, topRight, topCenter, @@ -138,8 +154,11 @@ export function getTransition({ duration, timingFunction, delay }: iTransition, return `${duration}ms ${property} ${timingFunction} ${delay}ms`; } -function defaultTransition(transition: iTransition, { duration, timingFunction, delay }: iTransition) { - const transitionOptions = transition || {} as iTransition; +function defaultTransition( + transition: iTransition, + { duration, timingFunction, delay }: iTransition +) { + const transitionOptions = transition || ({} as iTransition); if (isNull(transitionOptions.duration)) { transitionOptions.duration = duration; @@ -181,7 +200,10 @@ function defaultDismiss(dismiss: iDismiss): iDismiss { return option; } -function defaultUserDefinedTypes(notification: iNotification, definedTypes: iNotificationCustomType[]) { +function defaultUserDefinedTypes( + notification: iNotification, + definedTypes: iNotificationCustomType[] +) { const { content, type } = notification; if (content) { return undefined; @@ -201,7 +223,11 @@ function defaultUserDefinedTypes(notification: iNotification, definedTypes: iNot return definedTypes; } -export function parseNotification(options: iNotification, userDefinedTypes: any): iNotification { +export function parseNotification( + options: iNotification, + userDefinedTypes: iNotificationCustomType[], + defaultNotificationWidth: number +): iNotification { const notification = options; const { id, @@ -220,17 +246,14 @@ export function parseNotification(options: iNotification, userDefinedTypes: any) onRemoval } = notification; - notification.id = id || store.counter.toString(); - notification.type = content ? null : type.toLowerCase() as NOTIFICATION_TYPE; + notification.id = id || store.getCounter().toString(); + notification.type = content ? null : (type.toLowerCase() as NOTIFICATION_TYPE); if (userDefinedTypes && !content) { notification.userDefinedTypes = defaultUserDefinedTypes(notification, userDefinedTypes); } - if (!isNull(width)) { - notification.width = width; - } - + notification.width = isNull(width) ? defaultNotificationWidth : width; notification.container = container.toLowerCase() as NOTIFICATION_CONTAINER; notification.insert = (insert || 'top').toLowerCase() as INSERTION; notification.dismiss = defaultDismiss(dismiss); @@ -248,9 +271,9 @@ export function parseNotification(options: iNotification, userDefinedTypes: any) notification.slidingExit = defaultTransition(slidingExit, t(600, 'linear', 0)); notification.touchRevert = defaultTransition(touchRevert, t(600, 'linear', 0)); - const touchExit = touchSlidingExit || {} as iTouchTransition; - const swipe = touchExit.swipe || {} as iTransition; - const fade = touchExit.fade || {} as iTransition; + const touchExit = touchSlidingExit || ({} as iTouchTransition); + const swipe = touchExit.swipe || ({} as iTransition); + const fade = touchExit.fade || ({} as iTransition); notification.touchSlidingExit = touchExit; notification.touchSlidingExit.swipe = defaultTransition(swipe, t(600, 'linear', 0)); notification.touchSlidingExit.fade = defaultTransition(fade, t(300, 'linear', 0)); diff --git a/src/utils/validators.ts b/src/utils/validators.ts index c9fd187..81aafa1 100644 --- a/src/utils/validators.ts +++ b/src/utils/validators.ts @@ -10,7 +10,7 @@ const isFunction = (object: any) => !!(object && object.constructor && object.ca const isArray = (object: any) => !isNull(object) && object.constructor === Array; function isClassComponent(component: any) { - return typeof component === 'function' && component.prototype && !!component.prototype.isReactComponent; + return typeof component === 'function' && component.prototype && !!component.prototype.render; } function isFunctionComponent(component: any) { @@ -36,17 +36,11 @@ export function validateTransition(notification: iNotification, transition: stri export const validators = [ function title({ content, title }: iNotification) { - if (content) { - return; - } - if (isNull(title)) { - return; - } + if (content) return; + if (isNull(title)) return; const isReactEl = isReactElement(title); - if (isReactEl || typeof title === 'string') { - return; - } + if (isReactEl || typeof title === 'string') return; if (!isReactEl) { throw new Error(ERROR.TITLE_ELEMENT); } @@ -56,9 +50,7 @@ export const validators = [ }, function message({ content, message }: iNotification) { - if (content) { - return; - } + if (content) return; if (!message) { throw new Error(ERROR.MESSAGE_REQUIRED); @@ -77,9 +69,7 @@ export const validators = [ }, function type({ content, type }: iNotification, userDefinedTypes: iNotificationCustomType[]) { - if (content) { - return; - } + if (content) return; if (!type) { throw new Error(ERROR.TYPE_REQUIRED); } @@ -109,18 +99,14 @@ export const validators = [ }, function insert({ insert }: iNotification) { - if (isNull(insert)) { - return; - } + if (isNull(insert)) return; if (!isString(insert)) { throw new Error(ERROR.INSERT_STRING); } }, function width({ width }: iNotification) { - if (isNull(width)) { - return; - } + if (isNull(width)) return; if (!isNumber(width)) { throw new Error(ERROR.WIDTH_NUMBER); } @@ -130,9 +116,7 @@ export const validators = [ { type, content }: iNotification, userDefinedTypes: iNotificationCustomType[] ) { - if (content) { - return; - } + if (content) return; if ( type === NT.SUCCESS || @@ -151,9 +135,7 @@ export const validators = [ }, function content({ content }: iNotification) { - if (!content) { - return; - } + if (!content) return; const isClass = isClassComponent(content); const isFunction = isFunctionComponent(content); const isElem = React.isValidElement(content); @@ -163,36 +145,28 @@ export const validators = [ }, function animationIn({ animationIn }: iNotification) { - if (isNull(animationIn)) { - return; - } + if (isNull(animationIn)) return; if (!isArray(animationIn)) { throw new Error(ERROR.ANIMATION_IN); } }, function animationOut({ animationOut }: iNotification) { - if (isNull(animationOut)) { - return; - } + if (isNull(animationOut)) return; if (!isArray(animationOut)) { throw new Error(ERROR.ANIMATION_OUT); } }, function onRemoval({ onRemoval }: iNotification) { - if (!onRemoval) { - return; - } + if (!onRemoval) return; if (!isFunction(onRemoval)) { throw new Error(ERROR.REMOVAL_FUNC); } }, function dismiss({ dismiss }: iNotification) { - if (!dismiss) { - return; - } + if (!dismiss) return; const { duration, diff --git a/tsconfig.json b/tsconfig.json index 7e93a2f..8be2fde 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,7 +10,7 @@ "src/*": ["src/*"] }, - "declaration": false, + "declaration": true, "noImplicitReturns": false, "alwaysStrict": true, "noImplicitThis": false, @@ -28,7 +28,9 @@ "preserveConstEnums": true, "experimentalDecorators": true, "forceConsistentCasingInFileNames": true, - "target": "es2015" + "target": "es2015", + + "outDir": "dist/js" }, "include": [ diff --git a/webpack.prod.js b/webpack.prod.js index dcafe3a..cae965b 100644 --- a/webpack.prod.js +++ b/webpack.prod.js @@ -24,7 +24,7 @@ module.exports = { new CopyPlugin({ patterns: [ { from: 'src/scss', to: 'scss' }, - { from: 'build/index.ts', to: 'index.js' } + { from: 'build/index.js', to: 'index.js' } ], }) ]