diff --git a/packages/notifications-panel/CHANGELOG.md b/packages/notifications-panel/CHANGELOG.md
new file mode 100644
index 0000000000..8d5710bc45
--- /dev/null
+++ b/packages/notifications-panel/CHANGELOG.md
@@ -0,0 +1,6 @@
+### Features
+
+* this component leverages @hig/notifications-flyout@3.2.0 ([9b8069e](https://github.com/DynamoDS/hig/pull/1/commits/9b8069ec26b66cfad929b88edd20eb0c1a145676))
+* Addition of the 'Mark all as read' feature
+
+# [@hig/notifications-panel-v0.1.0](https://github.com/DynamoDS/hig/pull/1) (2022-08-04)
\ No newline at end of file
diff --git a/packages/notifications-panel/README.md b/packages/notifications-panel/README.md
new file mode 100644
index 0000000000..02ddf8e947
--- /dev/null
+++ b/packages/notifications-panel/README.md
@@ -0,0 +1,50 @@
+# Notifications Panel
+
+The notifications panel provides information and warnings that products may recover from without user involvement. It is meant to be displayed in a flyout as a list of notifications.
+
+## Getting started
+
+```bash
+yarn add @hig/notifications-panel @hig/theme-context @hig/theme-data
+```
+
+## Import the component
+
+```js
+import NotificationsPanel, { Notification } from "@hig/notifications-panel";
+```
+
+## Basic usage
+
+```jsx
+
+
+ Your subscription expires May 5
+
+
+```
+
+## Advanced usage
+
+```jsx
+import NotificationsPanel, { anchorPoints } from "@hig/notifications-panel";
+import Timestamp from "@hig/timestamp";
+
+ ,
+ content:
Something happened
+ },
+ {
+ content: "Hello world"
+ }
+ ]}
+/>
+```
diff --git a/packages/notifications-panel/__mocks__/@hig/progress-ring.js b/packages/notifications-panel/__mocks__/@hig/progress-ring.js
new file mode 100644
index 0000000000..e68f41e4e4
--- /dev/null
+++ b/packages/notifications-panel/__mocks__/@hig/progress-ring.js
@@ -0,0 +1,8 @@
+/** @todo Remove when `@hig/progress-ring` is migrated from vanilla */
+import React from "react";
+
+function ProgressRingMock(props) {
+ return
;
+}
+
+module.exports = jest.fn(ProgressRingMock);
diff --git a/packages/notifications-panel/package.json b/packages/notifications-panel/package.json
new file mode 100644
index 0000000000..1bd88dfbec
--- /dev/null
+++ b/packages/notifications-panel/package.json
@@ -0,0 +1,71 @@
+{
+ "name": "@hig/notifications-panel",
+ "version": "0.0.1",
+ "description": "HIG NotificationsPanel",
+ "author": "Autodesk Inc.",
+ "license": "Apache-2.0",
+ "homepage": "https://hig.autodesk.com",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/DynamoDS/hig.git"
+ },
+ "publishConfig": {
+ "access": "public"
+ },
+ "main": "build/index.js",
+ "module": "build/index.es.js",
+ "files": [
+ "build/*"
+ ],
+ "dependencies": {
+ "@hig/behaviors": "^2.1.0",
+ "@hig/icon-button": "^3.1.0",
+ "@hig/icons": "^4.1.0",
+ "@hig/progress-ring": "^2.2.0",
+ "@hig/rich-text": "^2.1.0",
+ "@hig/typography": "^2.1.0",
+ "@hig/utils": "^0.4.1",
+ "emotion": "^10.0.0",
+ "prop-types": "^15.7.1",
+ "react-transition-group": "^4.4.2"
+ },
+ "devDependencies": {
+ "@hig/avatar": "^2.1.0",
+ "@hig/babel-preset": "^0.1.1",
+ "@hig/eslint-config": "^0.1.0",
+ "@hig/jest-preset": "^0.1.0",
+ "@hig/scripts": "^0.1.2",
+ "@hig/semantic-release-config": "^0.1.0",
+ "@hig/text-link": "^2.1.0"
+ },
+ "peerDependencies": {
+ "@hig/theme-context": "^4.2.0",
+ "@hig/theme-data": "^3.0.0",
+ "react": "^17.0.0"
+ },
+ "scripts": {
+ "build": "hig-scripts-build",
+ "lint": "hig-scripts-lint",
+ "test": "hig-scripts-test",
+ "release": "hig-scripts-release",
+ "publish:npm": "rm -rf dist && mkdir dist && babel src/component -d dist --copy-files"
+ },
+ "eslintConfig": {
+ "extends": "@hig"
+ },
+ "jest": {
+ "preset": "@hig/jest-preset"
+ },
+ "release": {
+ "extends": "@hig/semantic-release-config"
+ },
+ "babel": {
+ "env": {
+ "test": {
+ "presets": [
+ "@hig/babel-preset/test"
+ ]
+ }
+ }
+ }
+}
diff --git a/packages/notifications-panel/src/Notification.js b/packages/notifications-panel/src/Notification.js
new file mode 100644
index 0000000000..79b721ddc6
--- /dev/null
+++ b/packages/notifications-panel/src/Notification.js
@@ -0,0 +1,127 @@
+import PropTypes from "prop-types";
+import React from "react";
+import { ControlBehavior } from "@hig/behaviors";
+
+import NotificationBehavior from "./behaviors/NotificationBehavior";
+import NotificationPresenter from "./presenters/NotificationPresenter";
+
+const Notification = (props) => {
+ /**
+ * @param {NotificationShape} shape
+ * @returns {import("react").ReactElement}
+ */
+ const renderChildren = () => {
+ const { children, hideFlyout, onDismiss: dismiss } = props;
+
+ if (typeof children !== "function") {
+ return children;
+ }
+
+ return children({ dismiss, hideFlyout });
+ };
+
+ const {
+ dismissButtonTitle,
+ featured,
+ image,
+ onDismiss,
+ onNotificationClick,
+ onMouseEnter,
+ onMouseLeave,
+ // Featured notifications show the dismiss button by default
+ showDismissButton = featured,
+ stylesheet,
+ timestamp,
+ type,
+ unread,
+ ...otherProps
+ } = props;
+ const { className } = otherProps;
+
+ return (
+
+ {({ handleDismissButtonClick, height, innerRef, transitionStatus }) => (
+
+ {({
+ hasHover,
+ onMouseEnter: handleMouseEnter,
+ onMouseLeave: handleMouseLeave,
+ }) => (
+
+ {renderChildren()}
+
+ )}
+
+ )}
+
+ );
+};
+
+Notification.displayName = "Notification";
+
+Notification.propTypes = {
+ /** Notification content */
+ children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
+ /** Title HTML attribute for the dismiss button */
+ dismissButtonTitle: PropTypes.string,
+ /** Determines whether the notification is featured */
+ featured: PropTypes.bool,
+ /** An action provided hide the flyout. This is provided to the `children` render prop */
+ hideFlyout: PropTypes.func,
+ /** An image to display such as an avatar or and icon */
+ image: PropTypes.node,
+ /** A callback called when user dismisses a featured notification */
+ onDismiss: PropTypes.func,
+ /** A callback when the user clicks anywhere within the notification */
+ onNotificationClick: PropTypes.func,
+ /**
+ * Triggers when the user's mouse is over the notification
+ */
+ onMouseEnter: PropTypes.func,
+ /**
+ * Triggers when the user's mouse is no longer over the notification
+ */
+ onMouseLeave: PropTypes.func,
+ /** Determines whether the dismiss button is shown */
+ showDismissButton: PropTypes.bool,
+ /** Function to modify the component's styles */
+ stylesheet: PropTypes.func,
+ /** Timestamp component */
+ timestamp: PropTypes.node,
+ /** Determines notification variant */
+ type: PropTypes.string,
+ /** Determines whether notification has not been read */
+ unread: PropTypes.bool,
+};
+
+Notification.defaultProps = {
+ /**
+ * This is an action that's provided to the consumer,
+ * as a result a value must always be available.
+ */
+ hideFlyout: () => {},
+ onNotificationClick: () => {},
+};
+
+export default Notification;
diff --git a/packages/notifications-panel/src/NotificationsPanel.js b/packages/notifications-panel/src/NotificationsPanel.js
new file mode 100644
index 0000000000..58007c3d8f
--- /dev/null
+++ b/packages/notifications-panel/src/NotificationsPanel.js
@@ -0,0 +1,152 @@
+import React from "react";
+import Panel from "./Panel";
+import Notification from "./Notification";
+import EmptyStatePresenter from "./presenters/EmptyStatePresenter";
+import PropTypes from "prop-types";
+import NotificationFlyoutBehavior from "./behaviors/NotificationFlyoutBehavior";
+import { combineEventHandlers } from "@hig/utils";
+
+/** @typedef {import("./behaviors/parseNotifications").ParsedNotification} ParsedNotification */
+
+/**
+ * @param {Object} payload
+ * @returns {function(ParsedNotification): JSX}
+ */
+function CreateNotificationRenderer({ dismissNotification }) {
+ /* eslint-disable-next-line react/prop-types */
+ return function renderNotification(notification) {
+ const {
+ content,
+ dismissButtonTitle,
+ featured,
+ id,
+ image,
+ key,
+ onDismiss,
+ onNotificationClick,
+ showDismissButton,
+ stylesheet,
+ timestamp,
+ type,
+ unread,
+ ...otherProps
+ } = notification;
+ const { className } = otherProps;
+
+ const handleDismiss = combineEventHandlers(onDismiss, () =>
+ dismissNotification(id)
+ );
+
+ return (
+
+ {content}
+
+ );
+ };
+}
+
+export default function NotificationsPanel(props) {
+
+ const {
+ alterCoordinates,
+ anchorPoint,
+ children,
+ emptyMessage,
+ heading,
+ indicatorTitle,
+ loading,
+ onClick,
+ onClickOutside,
+ onScroll,
+ open,
+ markAllAsReadTitle,
+ onClickMarkAllAsRead,
+ notifications: notificationsInput = children,
+ unreadCount: controlledUnreadCount,
+ stylesheet,
+ ...otherProps
+ } = props;
+ const { className } = otherProps;
+
+ return (
+
+ {({
+ dismissNotification,
+ handleClose,
+ notifications,
+ showUnreadCount,
+ unreadCount
+ }) => (
+ { }}
+ markAllAsReadTitle={markAllAsReadTitle}
+ onClickMarkAllAsRead={onClickMarkAllAsRead}
+ heading={heading}>
+ {notifications.length == 0 ? (
+
+ ) : (
+ notifications.map(
+ CreateNotificationRenderer({ dismissNotification })
+ )
+ )}
+
+ )}
+
+ )
+}
+
+NotificationsPanel.propTypes = {
+ /** Manipulate flyout coordinates before each render */
+ alterCoordinates: PropTypes.func,
+ /** Rendered notifications. It can contain one or more components. */
+ children: PropTypes.node,
+ /** The message displayed when there are no notifications */
+ emptyMessage: PropTypes.string,
+ /** Flyout panel heading */
+ heading: PropTypes.string,
+ /** Indicator button title */
+ indicatorTitle: PropTypes.string,
+ /** Determines whether the loading animation is shown */
+ loading: PropTypes.bool,
+ /**
+ * Rendered notifications.
+ * It takes precedent over `children`, and accepts an array
+ * containing any combination of components
+ * and Notification models
+ */
+ notifications: PropTypes.arrayOf(
+ PropTypes.oneOfType([PropTypes.node, PropTypes.object])
+ ),
+ /** Function called when the flyout is opened */
+ onClick: PropTypes.func,
+ /** Function called when the flyout is open, and a click event occurs outside the flyout */
+ onClickOutside: PropTypes.func,
+ /** Function called when the flyout panel is scrolled */
+ onScroll: PropTypes.func,
+ /** When provided, it overrides the flyout's open state */
+ open: PropTypes.bool,
+ /** Function to modify the component's styles */
+ stylesheet: PropTypes.func,
+ /** When provided, it overrides the derived unread notification count */
+ unreadCount: PropTypes.number,
+ /** The title related to the 'Mark all as read' button */
+ markAllAsReadTitle: PropTypes.string,
+ /** Function called when the 'Mark all as read' button */
+ onClickMarkAllAsRead: PropTypes.func
+};
\ No newline at end of file
diff --git a/packages/notifications-panel/src/Panel.js b/packages/notifications-panel/src/Panel.js
new file mode 100644
index 0000000000..c1d5fb91f6
--- /dev/null
+++ b/packages/notifications-panel/src/Panel.js
@@ -0,0 +1,51 @@
+import React from "react";
+import PropTypes from "prop-types";
+
+import PanelBehavior from "./behaviors/PanelBehavior";
+import PanelPresenter from "./presenters/PanelPresenter";
+
+export default function Panel(props) {
+ const {
+ children,
+ heading,
+ innerRef,
+ loading,
+ onScroll,
+ transitionStatus,
+ stylesheet,
+ markAllAsReadTitle,
+ onClickMarkAllAsRead
+ } = props;
+
+ return (
+
+ {({ listMaxHeight, loadingTransitionState, refListWrapper }) => (
+
+ {children}
+
+ )}
+
+ );
+}
+
+Panel.propTypes = {
+ children: PropTypes.node,
+ markAllAsReadTitle: PropTypes.string,
+ heading: PropTypes.string,
+ innerRef: PropTypes.func.isRequired,
+ loading: PropTypes.bool,
+ onScroll: PropTypes.func,
+ onClickMarkAllAsRead: PropTypes.func,
+ stylesheet: PropTypes.func,
+ transitionStatus: PropTypes.string,
+};
diff --git a/packages/notifications-panel/src/__stories__/NotificationsPanel.new-stories.js b/packages/notifications-panel/src/__stories__/NotificationsPanel.new-stories.js
new file mode 100644
index 0000000000..6bdd8573d0
--- /dev/null
+++ b/packages/notifications-panel/src/__stories__/NotificationsPanel.new-stories.js
@@ -0,0 +1,24 @@
+import React from "react";
+import NotificationsPanel from "../NotificationsPanel";
+
+export default {
+ title: "NotificationsPanel",
+ component: NotificationsPanel
+}
+
+const clickButton = () => { console.log("button clicked") }
+
+export const NotsPanel = () => ,
+ content: Something happened
+ },
+ {
+ id: 2,
+ featured: true,
+ content: "Hello world"
+ }
+]}/>
\ No newline at end of file
diff --git a/packages/notifications-panel/src/behaviors/NotificationBehavior.js b/packages/notifications-panel/src/behaviors/NotificationBehavior.js
new file mode 100644
index 0000000000..9fda23d90a
--- /dev/null
+++ b/packages/notifications-panel/src/behaviors/NotificationBehavior.js
@@ -0,0 +1,93 @@
+import React, { useState, useRef } from "react";
+import Transition from "react-transition-group/Transition";
+import PropTypes from "prop-types";
+
+const TRANSITION_DURATION = 300;
+
+/**
+ * @typedef {Object} Payload
+ * @property {string} transitionStatus
+ * @property {function(MouseEvent): void} handleDismissButtonClick
+ */
+
+const NotificationBehavior = (props) => {
+ const [height, setHeight] = useState("");
+ const [isVisible, setIsVisible] = useState(true);
+ const containerRef = useRef(null);
+
+ /**
+ * @param {HTMLDivElement} containerRef
+ */
+ const refContainer = (containerRefParams) => {
+ containerRef.current = containerRefParams;
+ };
+
+ const handleExit = () => {
+ const { onDismiss } = props;
+
+ if (onDismiss) onDismiss();
+ };
+
+ /**
+ * Sets the current height of the notification
+ * so that the height transition can occur
+ */
+ const prepareHideTransition = () =>
+ new Promise((resolve) => {
+ setHeight(`${containerRef.current.clientHeight}px`);
+ window.requestAnimationFrame(resolve);
+ });
+
+ /**
+ * Begins the hide transition by setting the `isVisible` prop
+ * The height is then controlled via CSS in the `NotificationPresenter`
+ *
+ * This method calls `requestAnimationFrame` again to ensure that a repaint occurs.
+ * @see https://stackoverflow.com/a/42302185
+ */
+ const startHideTransition = () => {
+ window.requestAnimationFrame(() => {
+ setHeight("");
+ setIsVisible(false);
+ });
+ };
+
+ const hide = () => {
+ prepareHideTransition().then(() => startHideTransition());
+ };
+
+ const handleDismissButtonClick = () => {
+ hide();
+ };
+
+ const innerRef = refContainer;
+ const { children } = props;
+
+ return (
+
+ {(transitionStatus) =>
+ children({
+ handleDismissButtonClick,
+ height,
+ innerRef,
+ transitionStatus,
+ })
+ }
+
+ );
+};
+
+NotificationBehavior.displayName = "NotificationBehavior";
+
+NotificationBehavior.propTypes = {
+ /** Notification content */
+ children: PropTypes.func.isRequired,
+ /** A callback called when user dismisses a featured notification */
+ onDismiss: PropTypes.func,
+};
+
+export default NotificationBehavior;
diff --git a/packages/notifications-panel/src/behaviors/NotificationFlyoutBehavior.js b/packages/notifications-panel/src/behaviors/NotificationFlyoutBehavior.js
new file mode 100644
index 0000000000..c1574deaa1
--- /dev/null
+++ b/packages/notifications-panel/src/behaviors/NotificationFlyoutBehavior.js
@@ -0,0 +1,156 @@
+/* eslint-disable react/no-unused-prop-types */
+
+import { Component } from "react";
+import PropTypes from "prop-types";
+
+import parseNotifications from "./parseNotifications";
+
+/** @typedef {import("./parseNotifications").Input} NotificationsInput */
+/** @typedef {import("./parseNotifications").ParsedNotification} ParsedNotification */
+/** @typedef {import("./NotificationBehavior").Payload} NotificationBehaviorPayload */
+
+/**
+ * @typedef {Object} Payload
+ * @property {Function} dismissNotification
+ * @property {ParsedNotification[]} notifications
+ * @property {boolean} showUnreadCount
+ * @property {number} unreadCount
+ */
+
+/**
+ * @typedef {Object} Props
+ * @property {function(Payload): import("react").ReactElement} [children]
+ * @property {NotificationsInput} [notifications]
+ * @property {number} [unreadCount]
+ */
+
+/**
+ * @typedef {Object} State
+ * @property {string[]} dismissedNotifications An array of notification IDs that have been dismissed
+ * @property {ParsedNotification[]} notifications
+ * @property {string[]} readNotifications An array of notification IDs that have been read
+ */
+
+export default class NotificationFlyoutBehavior extends Component {
+ /** @type {Props} */
+ props;
+
+ // eslint-disable-next-line react/static-property-placement
+ static propTypes = {
+ /** Render prop */
+ children: PropTypes.func.isRequired,
+ /** Rendered notifications */
+ notifications: PropTypes.oneOfType([
+ PropTypes.node,
+ PropTypes.arrayOf(
+ PropTypes.oneOfType([PropTypes.node, PropTypes.shape({})])
+ ),
+ ]),
+ /** When provided, it overrides the derived unread notification count */
+ unreadCount: PropTypes.number,
+ };
+
+ /** @type {State} */
+ // eslint-disable-next-line react/state-in-constructor
+ state = {
+ dismissedNotifications: [],
+ notifications: [],
+ readNotifications: [],
+ };
+
+ /**
+ * @param {Props} nextProps
+ * @returns {State | null}
+ */
+ static getDerivedStateFromProps(nextProps) {
+ return {
+ notifications: parseNotifications(nextProps.notifications),
+ };
+ }
+
+ /**
+ * @returns {ParsedNotification[]}
+ */
+ getNotifications() {
+ const { dismissedNotifications, notifications, readNotifications } =
+ this.state;
+
+ const updateReadStatus = ({ id, unread, ...otherProps }) => ({
+ id,
+ unread: unread && !readNotifications.includes(id),
+ ...otherProps,
+ });
+ const isNotDismissed = ({ id }) => !dismissedNotifications.includes(id);
+
+ return notifications.map(updateReadStatus).filter(isNotDismissed);
+ }
+
+ /** @returns {number} */
+ getUnreadCount() {
+ const { unreadCount: controlledUnreadCount } = this.props;
+
+ return controlledUnreadCount !== undefined
+ ? controlledUnreadCount
+ : this.deriveUnreadCount();
+ }
+
+ /**
+ * Action to dismiss a notification
+ * @param {string} id
+ */
+ dismissNotification = (id) => {
+ this.setState({
+ // eslint-disable-next-line react/no-access-state-in-setstate
+ dismissedNotifications: this.state.dismissedNotifications.concat(id),
+ });
+ };
+
+ /**
+ * Handler for when the flyout opens
+ */
+ handleClose = () => {
+ this.markAllNotificationsRead();
+ };
+
+ /**
+ * @param {ParsedNotification[]} notifications
+ */
+ deriveUnreadCount() {
+ return this.getNotifications().reduce(
+ (count, { unread }) => (unread ? count + 1 : count),
+ 0
+ );
+ }
+
+ markAllNotificationsRead() {
+ const notifications = this.getNotifications();
+ const { readNotifications } = this.state;
+ const nextRead = notifications.reduce((result, notification) => {
+ const { id } = notification;
+
+ if (!result.includes(id)) result.push(id);
+
+ return result;
+ }, readNotifications.slice());
+
+ this.setState({ readNotifications: nextRead });
+ }
+
+ /**
+ * @returns {import("react").ReactElement}
+ */
+ render() {
+ const { dismissNotification, handleClose } = this;
+ const notifications = this.getNotifications();
+ const unreadCount = this.getUnreadCount();
+ const showUnreadCount = unreadCount > 0;
+
+ return this.props.children({
+ dismissNotification,
+ handleClose,
+ notifications,
+ showUnreadCount,
+ unreadCount,
+ });
+ }
+}
diff --git a/packages/notifications-panel/src/behaviors/PanelBehavior.js b/packages/notifications-panel/src/behaviors/PanelBehavior.js
new file mode 100644
index 0000000000..049e8c6fc6
--- /dev/null
+++ b/packages/notifications-panel/src/behaviors/PanelBehavior.js
@@ -0,0 +1,97 @@
+import React, { useState, useEffect, useRef } from "react";
+import PropTypes from "prop-types";
+import Transition from "react-transition-group/Transition";
+import { transitionStatuses, AVAILABLE_TRANSITION_STATUSES } from "@hig/flyout";
+
+/** 50px per the design spec plus 30px for the height of the title */
+const BOTTOM_SPACING = 80;
+const TRANSITION_DURATION = 300;
+
+/**
+ * @param {HTMLDivElement} listWrapper
+ * @returns {string}
+ */
+function calculateListMaxHeight(listWrapper) {
+ if (!listWrapper) return "";
+
+ const { top: listWrapperTop } = listWrapper.getBoundingClientRect();
+
+ // Distance between the top of the list wrapper and the spacing at the bottom of the screen
+ const height = window.innerHeight - BOTTOM_SPACING - listWrapperTop;
+
+ return `${height}px`;
+}
+
+const PanelBehavior = (props) => {
+ const [listMaxHeight, setListMaxHeight] = useState("");
+ const listWrapperRef = useRef(null);
+
+ const updateMaxHeight = () => {
+ setListMaxHeight(calculateListMaxHeight(listWrapperRef.current));
+ };
+
+ const handleResize = () => {
+ updateMaxHeight();
+ };
+
+ const bindResize = () => {
+ window.addEventListener("resize", handleResize);
+ };
+
+ const unbindResize = () => {
+ window.removeEventListener("resize", handleResize);
+ };
+
+ /**
+ * @param {HTMLDivElement} listWrapperRef
+ */
+ const refListWrapper = (value) => {
+ listWrapperRef.current = value;
+ };
+
+ const { children, loading } = props;
+
+ useEffect(() => {
+ bindResize();
+ updateMaxHeight();
+
+ return () => {
+ unbindResize();
+ };
+ }, []);
+
+ useEffect(() => {
+ if (
+ props.transitionStatus === transitionStatuses.HIDDEN ||
+ props.transitionStatus === transitionStatuses.EXITED
+ ) {
+ window.requestAnimationFrame(() => {
+ updateMaxHeight();
+ });
+ }
+ }, [props]);
+
+ return (
+
+ {(loadingTransitionState) =>
+ children({
+ loadingTransitionState,
+ listMaxHeight,
+ refListWrapper,
+ })
+ }
+
+ );
+};
+
+PanelBehavior.propTypes = {
+ children: PropTypes.func.isRequired,
+ loading: PropTypes.bool,
+ transitionStatus: PropTypes.oneOf(AVAILABLE_TRANSITION_STATUSES),
+};
+
+PanelBehavior.defaultProps = {
+ loading: false,
+};
+
+export default PanelBehavior;
diff --git a/packages/notifications-panel/src/behaviors/__snapshots__/teste.NotificationFlyoutBehavior.test.js.snap b/packages/notifications-panel/src/behaviors/__snapshots__/teste.NotificationFlyoutBehavior.test.js.snap
new file mode 100644
index 0000000000..0117e6f7d7
--- /dev/null
+++ b/packages/notifications-panel/src/behaviors/__snapshots__/teste.NotificationFlyoutBehavior.test.js.snap
@@ -0,0 +1,61 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`notification-flyout/behaviors/NotificationFlyoutBehavior render prop payload normalizes notifications 1`] = `
+Array [
+ Object {
+ "content": "Featured",
+ "featured": true,
+ "hideFlyout": [Function],
+ "id": "1",
+ "key": "0",
+ "onNotificationClick": [Function],
+ "unread": true,
+ },
+ Object {
+ "content": "Foo",
+ "hideFlyout": [Function],
+ "id": "2",
+ "key": "1",
+ "onNotificationClick": [Function],
+ "unread": true,
+ },
+ Object {
+ "content": "Bar",
+ "hideFlyout": [Function],
+ "id": "3",
+ "key": "2",
+ "onNotificationClick": [Function],
+ "unread": true,
+ },
+]
+`;
+
+exports[`notification-flyout/behaviors/NotificationFlyoutBehavior render prop payload provides a payload to the \`children\` render prop 1`] = `
+Array [
+ Object {
+ "content": "Featured",
+ "featured": true,
+ "hideFlyout": [Function],
+ "id": "1",
+ "key": "0",
+ "onNotificationClick": [Function],
+ "unread": true,
+ },
+ Object {
+ "content": "Foo",
+ "hideFlyout": [Function],
+ "id": "2",
+ "key": "1",
+ "onNotificationClick": [Function],
+ "unread": true,
+ },
+ Object {
+ "content": "Bar",
+ "hideFlyout": [Function],
+ "id": "3",
+ "key": "2",
+ "onNotificationClick": [Function],
+ "unread": true,
+ },
+]
+`;
diff --git a/packages/notifications-panel/src/behaviors/parseNotifications.js b/packages/notifications-panel/src/behaviors/parseNotifications.js
new file mode 100644
index 0000000000..27ca55f292
--- /dev/null
+++ b/packages/notifications-panel/src/behaviors/parseNotifications.js
@@ -0,0 +1,72 @@
+import { Children } from "react";
+import Notification from "../Notification";
+
+/** @typedef {null|undefined|NotificationElements} Input */
+
+/**
+ * @typedef {Object} ParsedNotification
+ * @property {string} id
+ * @property {string} key
+ * @property {NotificationContent} [content]
+ * @property {boolean} [featured]
+ * @property {function(): void} [onDismiss]
+ * @property {boolean} [showDismissButton]
+ * @property {import("react").ReactElement} [timestamp]
+ * @property {string} [type]
+ * @property {boolean} unread
+ */
+
+/**
+ * Converts the given notifications input into an array
+ * @param {Input} input
+ * @returns {Object[]}
+ */
+function normalizeInput(input) {
+ if (input == null) {
+ return [];
+ }
+
+ if (Array.isArray(input)) {
+ return input;
+ }
+
+ const element = Children.only(input);
+
+ if (element.type === Notification) {
+ return [element];
+ }
+
+ throw new Error("Invalid notifications value.");
+}
+
+/**
+ * @param {Object} value
+ * @param {number} index
+ * @returns {ParsedNotification}
+ */
+function parseNotification(value, index) {
+ const { key = `notification-${index}`, ...otherValues } = value;
+ const {
+ children,
+ content = children,
+ id = index.toString(),
+ unread = true,
+ ...props
+ } = otherValues.props || otherValues;
+
+ return {
+ id,
+ key,
+ content,
+ unread,
+ ...props,
+ };
+}
+
+/**
+ * @param {Input} input
+ * @returns {ParsedNotification[]}
+ */
+export default function parseNotifications(input) {
+ return normalizeInput(input).map(parseNotification);
+}
diff --git a/packages/notifications-panel/src/index.js b/packages/notifications-panel/src/index.js
new file mode 100644
index 0000000000..00c229b0b2
--- /dev/null
+++ b/packages/notifications-panel/src/index.js
@@ -0,0 +1,9 @@
+import Image from "./presenters/ImagePresenter";
+import NotificationsPanel from "./NotificationsPanel";
+import Notification from "./Notification";
+
+NotificationsPanel.Image = Image;
+NotificationsPanel.Notification = Notification;
+
+export { NotificationsPanel as default, Image, Notification };
+export { types, AVAILABLE_TYPES } from "./types";
diff --git a/packages/notifications-panel/src/presenters/Bell.svg b/packages/notifications-panel/src/presenters/Bell.svg
new file mode 100644
index 0000000000..57d8c55467
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/Bell.svg
@@ -0,0 +1,40 @@
+
+
+
+ empty-illustration
+ Created with Sketch.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/notifications-panel/src/presenters/DismissButtonPresenter.js b/packages/notifications-panel/src/presenters/DismissButtonPresenter.js
new file mode 100644
index 0000000000..547257267b
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/DismissButtonPresenter.js
@@ -0,0 +1,39 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { css, cx } from "emotion";
+import IconButton from "@hig/icon-button";
+import { CloseSUI } from "@hig/icons";
+import { createCustomClassNames } from "@hig/utils";
+
+import stylesheet from "./stylesheet";
+
+export default function DismissButtonPresenter({
+ hasHover,
+ onClick,
+ stylesheet: customStylesheet,
+ title,
+ ...otherProps
+}) {
+ const { className } = otherProps;
+ const dismissButtonClassName = createCustomClassNames(
+ className,
+ "dismiss-button"
+ );
+ const styles = stylesheet({ hasHover, stylesheet: customStylesheet }, {});
+ return (
+
+ } title={title} />
+
+ );
+}
+
+DismissButtonPresenter.defaultProps = {
+ title: "Dismiss featured notification",
+};
+
+DismissButtonPresenter.propTypes = {
+ hasHover: PropTypes.bool,
+ onClick: PropTypes.func,
+ stylesheet: PropTypes.func,
+ title: PropTypes.string,
+};
diff --git a/packages/notifications-panel/src/presenters/DismissButtonPresenter.test.js b/packages/notifications-panel/src/presenters/DismissButtonPresenter.test.js
new file mode 100644
index 0000000000..99386fe036
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/DismissButtonPresenter.test.js
@@ -0,0 +1,19 @@
+import { takeSnapshotsOf } from "@hig/jest-preset/helpers";
+
+import DismissButtonPresenter from "./DismissButtonPresenter";
+
+describe("notifications-flyout/presenters/DismissButtonPresenter", () => {
+ takeSnapshotsOf(DismissButtonPresenter, [
+ {
+ desc: "renders without props",
+ props: {},
+ },
+ {
+ desc: "renders with all props",
+ props: {
+ onClick: () => {},
+ title: "HELLO",
+ },
+ },
+ ]);
+});
diff --git a/packages/notifications-panel/src/presenters/EmptyStatePresenter.js b/packages/notifications-panel/src/presenters/EmptyStatePresenter.js
new file mode 100644
index 0000000000..b43a22800f
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/EmptyStatePresenter.js
@@ -0,0 +1,29 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { css } from "emotion";
+import Typography from "@hig/typography";
+
+import Bell from "./Bell.svg";
+import stylesheet from "./stylesheet";
+
+export default function EmptyStatePresenter({
+ message,
+ stylesheet: customStylesheet,
+}) {
+ const styles = stylesheet({ stylesheet: customStylesheet }, {});
+ return (
+
+
+ {message}
+
+ );
+}
+
+EmptyStatePresenter.defaultProps = {
+ message: "You currently have no notifications",
+};
+
+EmptyStatePresenter.propTypes = {
+ message: PropTypes.string,
+ stylesheet: PropTypes.func,
+};
diff --git a/packages/notifications-panel/src/presenters/EmptyStatePresenter.test.js b/packages/notifications-panel/src/presenters/EmptyStatePresenter.test.js
new file mode 100644
index 0000000000..6be6551a90
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/EmptyStatePresenter.test.js
@@ -0,0 +1,18 @@
+import { takeSnapshotsOf } from "@hig/jest-preset/helpers";
+
+import EmptyStatePresenter from "./EmptyStatePresenter";
+
+describe("notifications-flyout/presenters/EmptyStatePresenter", () => {
+ takeSnapshotsOf(EmptyStatePresenter, [
+ {
+ desc: "renders without props",
+ props: {},
+ },
+ {
+ desc: "renders with all props",
+ props: {
+ message: "hello world",
+ },
+ },
+ ]);
+});
diff --git a/packages/notifications-panel/src/presenters/ImagePresenter.js b/packages/notifications-panel/src/presenters/ImagePresenter.js
new file mode 100644
index 0000000000..e51a64662a
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/ImagePresenter.js
@@ -0,0 +1,9 @@
+/* eslint-disable jsx-a11y/alt-text */
+import React from "react";
+import { css } from "emotion";
+
+import stylesheet from "./stylesheet";
+
+export default function ImagePresenter(props) {
+ return ;
+}
diff --git a/packages/notifications-panel/src/presenters/ImagePresenter.test.js b/packages/notifications-panel/src/presenters/ImagePresenter.test.js
new file mode 100644
index 0000000000..37aa034ce7
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/ImagePresenter.test.js
@@ -0,0 +1,16 @@
+import { takeSnapshotsOf } from "@hig/jest-preset/helpers";
+
+import ImagePresenter from "./ImagePresenter";
+
+describe("notifications-flyout/presenters/ImagePresenter", () => {
+ takeSnapshotsOf(ImagePresenter, [
+ {
+ desc: "renders with img props",
+ props: {
+ alt: "hello",
+ src: "//example.com/random.png",
+ "data-something": "anything",
+ },
+ },
+ ]);
+});
diff --git a/packages/notifications-panel/src/presenters/IndicatorPresenter.js b/packages/notifications-panel/src/presenters/IndicatorPresenter.js
new file mode 100644
index 0000000000..8df977d9a9
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/IndicatorPresenter.js
@@ -0,0 +1,58 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { css, cx } from "emotion";
+import IconButton from "@hig/icon-button";
+import { Notification16, Notification24 } from "@hig/icons";
+import ThemeContext from "@hig/theme-context";
+import { createCustomClassNames } from "@hig/utils";
+
+import stylesheet from "./stylesheet";
+
+export default function IndicatorPresenter(props) {
+ const { count, onClick, title, ...otherProps } = props;
+ const { className } = otherProps;
+ const indicatorClassName = createCustomClassNames(className, "indicator");
+ const indicatorCountClassName = createCustomClassNames(
+ className,
+ "indicator-count"
+ );
+
+ return (
+
+ {({ resolvedRoles, metadata }) => {
+ const styles = stylesheet(props, resolvedRoles);
+ const NotificationIcon =
+ metadata.densityId === "high-density"
+ ? Notification16
+ : Notification24;
+ return (
+
+
}
+ title={title}
+ />
+
+ {count}
+
+
+ );
+ }}
+
+ );
+}
+
+IndicatorPresenter.defaultProps = {
+ title: "View notifications",
+};
+
+IndicatorPresenter.propTypes = {
+ count: PropTypes.number,
+ onClick: PropTypes.func,
+ title: PropTypes.string,
+};
diff --git a/packages/notifications-panel/src/presenters/IndicatorPresenter.test.js b/packages/notifications-panel/src/presenters/IndicatorPresenter.test.js
new file mode 100644
index 0000000000..185a6078a0
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/IndicatorPresenter.test.js
@@ -0,0 +1,21 @@
+import { takeSnapshotsOf } from "@hig/jest-preset/helpers";
+
+import IndicatorPresenter from "./IndicatorPresenter";
+
+describe("notifications-flyout/presenters/IndicatorPresenter", () => {
+ takeSnapshotsOf(IndicatorPresenter, [
+ {
+ desc: "renders without props",
+ props: {},
+ },
+ {
+ desc: "renders with all props",
+ props: {
+ count: 3,
+ onClick: () => {},
+ showCount: true,
+ title: "hello world",
+ },
+ },
+ ]);
+});
diff --git a/packages/notifications-panel/src/presenters/NotificationPresenter.js b/packages/notifications-panel/src/presenters/NotificationPresenter.js
new file mode 100644
index 0000000000..e5494f9c6c
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/NotificationPresenter.js
@@ -0,0 +1,114 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { css, cx } from "emotion";
+import RichText from "@hig/rich-text";
+import ThemeContext from "@hig/theme-context";
+import { createCustomClassNames } from "@hig/utils";
+
+import DismissButtonPresenter from "./DismissButtonPresenter";
+import stylesheet from "./stylesheet";
+
+export default function NotificationPresenter(props) {
+ const {
+ children,
+ dismissButtonTitle,
+ hasHover,
+ height,
+ image,
+ innerRef,
+ onDismissButtonClick,
+ onNotificationClick,
+ onMouseEnter,
+ onMouseLeave,
+ showDismissButton,
+ stylesheet: customStylesheet,
+ timestamp,
+ ...otherProps
+ } = props;
+ const { className } = otherProps;
+ const notificationContentClassName = createCustomClassNames(
+ className,
+ "notification-content"
+ );
+ const notificationContentImageClassName = createCustomClassNames(
+ className,
+ "notification-content-image"
+ );
+ const notificationContentTextClassName = createCustomClassNames(
+ className,
+ "notification-content-text"
+ );
+
+ return (
+ /* eslint-disable jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions */
+
+ {({ resolvedRoles }) => {
+ const styles = stylesheet(props, resolvedRoles);
+ return (
+
+
+ {image ? (
+
+ {image}
+
+ ) : null}
+
+ {children}
+ {timestamp}
+ {showDismissButton ? (
+
+ ) : null}
+
+
+
+ );
+ }}
+
+ /* eslint-enable jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions */
+ );
+}
+
+NotificationPresenter.propTypes = {
+ children: PropTypes.node,
+ dismissButtonTitle: PropTypes.string,
+ hasHover: PropTypes.bool,
+ height: PropTypes.string,
+ image: PropTypes.node,
+ innerRef: PropTypes.func,
+ onDismissButtonClick: PropTypes.func,
+ onNotificationClick: PropTypes.func,
+ onMouseEnter: PropTypes.func,
+ onMouseLeave: PropTypes.func,
+ showDismissButton: PropTypes.bool,
+ stylesheet: PropTypes.func,
+ timestamp: PropTypes.node,
+};
diff --git a/packages/notifications-panel/src/presenters/NotificationPresenter.test.js b/packages/notifications-panel/src/presenters/NotificationPresenter.test.js
new file mode 100644
index 0000000000..97c07facb6
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/NotificationPresenter.test.js
@@ -0,0 +1,30 @@
+import { ENTERED } from "react-transition-group/Transition";
+import { takeSnapshotsOf } from "@hig/jest-preset/helpers";
+
+import NotificationPresenter from "./NotificationPresenter";
+import { types } from "../types";
+
+describe("notifications-flyout/presenters/NotificationPresenter", () => {
+ takeSnapshotsOf(NotificationPresenter, [
+ {
+ desc: "renders without props",
+ props: {},
+ },
+ {
+ desc: "renders with all props",
+ props: {
+ children: "foobar",
+ dismissButtonTitle: "hello world",
+ featured: true,
+ height: "3000px",
+ innerRef: () => {},
+ onDismissButtonClick: () => {},
+ showDismissButton: true,
+ timestamp: "2018-08-17",
+ transitionStatus: ENTERED,
+ type: types.SUCCESS,
+ unread: true,
+ },
+ },
+ ]);
+});
diff --git a/packages/notifications-panel/src/presenters/PanelPresenter.js b/packages/notifications-panel/src/presenters/PanelPresenter.js
new file mode 100644
index 0000000000..bef661f64f
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/PanelPresenter.js
@@ -0,0 +1,95 @@
+import React from "react";
+import PropTypes from "prop-types";
+import { css } from "emotion";
+import { Panel } from "@hig/flyout";
+import ProgressRing from "@hig/progress-ring";
+import ThemeContext from "@hig/theme-context";
+import Typography from "@hig/typography";
+import {
+ UNMOUNTED,
+ EXITED,
+ ENTERING,
+ ENTERED,
+ EXITING,
+} from "react-transition-group/Transition";
+import Button from "@hig/button"
+
+import stylesheet from "./stylesheet";
+
+export default function PanelPresenter({
+ children,
+ heading,
+ innerRef,
+ listMaxHeight,
+ loadingTransitionState,
+ onScroll,
+ refListWrapper,
+ stylesheet: customStylesheet,
+ markAllAsReadTitle,
+ onClickMarkAllAsRead
+}) {
+ return (
+
+ {({ resolvedRoles }) => {
+ const styles = stylesheet(
+ {
+ transitionState: null,
+ loadingTransitionState,
+ stylesheet: customStylesheet,
+ },
+ resolvedRoles
+ );
+ return (
+
+
+ {heading}
+
+
+
+
+ );
+ }}
+
+ );
+}
+
+PanelPresenter.defaultProps = {
+ heading: "Notifications",
+ markAllAsReadTitle: "Mark all as read"
+};
+
+PanelPresenter.propTypes = {
+ children: PropTypes.node,
+ heading: PropTypes.node,
+ innerRef: PropTypes.func.isRequired,
+ listMaxHeight: PropTypes.string,
+ markAllAsReadTitle: PropTypes.string,
+ onClickMarkAllAsRead: PropTypes.func,
+ stylesheet: PropTypes.func,
+ loadingTransitionState: PropTypes.oneOf([
+ UNMOUNTED,
+ EXITED,
+ ENTERING,
+ ENTERED,
+ EXITING,
+ ]),
+ onScroll: PropTypes.func,
+ refListWrapper: PropTypes.func,
+ stylesheet: PropTypes.func,
+};
diff --git a/packages/notifications-panel/src/presenters/PanelPresenter.test.js b/packages/notifications-panel/src/presenters/PanelPresenter.test.js
new file mode 100644
index 0000000000..b8d62f7b07
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/PanelPresenter.test.js
@@ -0,0 +1,27 @@
+import { ENTERED } from "react-transition-group/Transition";
+import { takeSnapshotsOf } from "@hig/jest-preset/helpers";
+
+const PanelPresenter = require("./PanelPresenter").default;
+
+describe("notifications-flyout/presenters/PanelPresenter", () => {
+ takeSnapshotsOf(PanelPresenter, [
+ {
+ desc: "renders without props",
+ props: {
+ innerRef: () => {},
+ },
+ },
+ {
+ desc: "renders with all props",
+ props: {
+ children: "foobar",
+ heading: "Heading",
+ innerRef: () => {},
+ listMaxHeight: "3000px",
+ loadingTransitionState: ENTERED,
+ onScroll: () => {},
+ refListWrapper: () => {},
+ },
+ },
+ ]);
+});
diff --git a/packages/notifications-panel/src/presenters/__snapshots__/DismissButtonPresenter.test.js.snap b/packages/notifications-panel/src/presenters/__snapshots__/DismissButtonPresenter.test.js.snap
new file mode 100644
index 0000000000..8973a48077
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/__snapshots__/DismissButtonPresenter.test.js.snap
@@ -0,0 +1,168 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`notifications-flyout/presenters/DismissButtonPresenter renders with all props 1`] = `
+.emotion-2 {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+
+.emotion-1 {
+ background-color: transparent;
+ border-color: transparent;
+ border-style: solid;
+ border-width: 1px;
+ display: inline-block;
+ position: relative;
+ cursor: pointer;
+ box-sizing: border-box;
+ border-radius: 2px;
+ padding: 0;
+ height: calc(20px + (8px * 2));
+ line-height: calc(20px + (8px * 2));
+ width: calc(20px + (8px * 2));
+ outline: 0;
+ -webkit-transition-property: box-shadow,background-color;
+ transition-property: box-shadow,background-color;
+ -webkit-transition-duration: 0.3s,0.3s;
+ transition-duration: 0.3s,0.3s;
+ -webkit-transition-timing-function: cubic-bezier(0.4,0,0.2,1),cubic-bezier(0.4,0,0.2,1);
+ transition-timing-function: cubic-bezier(0.4,0,0.2,1),cubic-bezier(0.4,0,0.2,1);
+}
+
+.emotion-1 svg * {
+ fill: #808080;
+ -webkit-transition-duration: 0.3s;
+ transition-duration: 0.3s;
+ -webkit-transition-property: fill;
+ transition-property: fill;
+}
+
+.emotion-0 {
+ fill: #808080;
+ position: absolute;
+ left: 0;
+ right: 0;
+ margin: 0 auto;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ pointer-events: none;
+}
+
+.emotion-0 > * {
+ fill: #808080;
+}
+
+
+`;
+
+exports[`notifications-flyout/presenters/DismissButtonPresenter renders without props 1`] = `
+.emotion-2 {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+
+.emotion-1 {
+ background-color: transparent;
+ border-color: transparent;
+ border-style: solid;
+ border-width: 1px;
+ display: inline-block;
+ position: relative;
+ cursor: pointer;
+ box-sizing: border-box;
+ border-radius: 2px;
+ padding: 0;
+ height: calc(20px + (8px * 2));
+ line-height: calc(20px + (8px * 2));
+ width: calc(20px + (8px * 2));
+ outline: 0;
+ -webkit-transition-property: box-shadow,background-color;
+ transition-property: box-shadow,background-color;
+ -webkit-transition-duration: 0.3s,0.3s;
+ transition-duration: 0.3s,0.3s;
+ -webkit-transition-timing-function: cubic-bezier(0.4,0,0.2,1),cubic-bezier(0.4,0,0.2,1);
+ transition-timing-function: cubic-bezier(0.4,0,0.2,1),cubic-bezier(0.4,0,0.2,1);
+}
+
+.emotion-1 svg * {
+ fill: #808080;
+ -webkit-transition-duration: 0.3s;
+ transition-duration: 0.3s;
+ -webkit-transition-property: fill;
+ transition-property: fill;
+}
+
+.emotion-0 {
+ fill: #808080;
+ position: absolute;
+ left: 0;
+ right: 0;
+ margin: 0 auto;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ pointer-events: none;
+}
+
+.emotion-0 > * {
+ fill: #808080;
+}
+
+
+`;
diff --git a/packages/notifications-panel/src/presenters/__snapshots__/EmptyStatePresenter.test.js.snap b/packages/notifications-panel/src/presenters/__snapshots__/EmptyStatePresenter.test.js.snap
new file mode 100644
index 0000000000..cb82cf626c
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/__snapshots__/EmptyStatePresenter.test.js.snap
@@ -0,0 +1,101 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`notifications-flyout/presenters/EmptyStatePresenter renders with all props 1`] = `
+.emotion-2 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+}
+
+.emotion-0 {
+ margin-top: 20px;
+}
+
+.emotion-1 {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.428571429;
+ margin: 0;
+ text-align: initial;
+}
+
+
+`;
+
+exports[`notifications-flyout/presenters/EmptyStatePresenter renders without props 1`] = `
+.emotion-2 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+}
+
+.emotion-0 {
+ margin-top: 20px;
+}
+
+.emotion-1 {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.428571429;
+ margin: 0;
+ text-align: initial;
+}
+
+
+
+
+ You currently have no notifications
+
+
+`;
diff --git a/packages/notifications-panel/src/presenters/__snapshots__/ImagePresenter.test.js.snap b/packages/notifications-panel/src/presenters/__snapshots__/ImagePresenter.test.js.snap
new file mode 100644
index 0000000000..0982b99b6c
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/__snapshots__/ImagePresenter.test.js.snap
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`notifications-flyout/presenters/ImagePresenter renders with img props 1`] = `
+.emotion-0 {
+ height: 48px;
+ width: 48px;
+ overflow: hidden;
+ border-radius: 4px;
+}
+
+
+`;
diff --git a/packages/notifications-panel/src/presenters/__snapshots__/IndicatorPresenter.test.js.snap b/packages/notifications-panel/src/presenters/__snapshots__/IndicatorPresenter.test.js.snap
new file mode 100644
index 0000000000..6658868f4b
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/__snapshots__/IndicatorPresenter.test.js.snap
@@ -0,0 +1,216 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`notifications-flyout/presenters/IndicatorPresenter renders with all props 1`] = `
+.emotion-3 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ position: relative;
+}
+
+.emotion-1 {
+ background-color: transparent;
+ border-color: transparent;
+ border-style: solid;
+ border-width: 1px;
+ display: inline-block;
+ position: relative;
+ cursor: pointer;
+ box-sizing: border-box;
+ border-radius: 2px;
+ padding: 0;
+ height: calc(20px + (8px * 2));
+ line-height: calc(20px + (8px * 2));
+ width: calc(20px + (8px * 2));
+ outline: 0;
+ -webkit-transition-property: box-shadow,background-color;
+ transition-property: box-shadow,background-color;
+ -webkit-transition-duration: 0.3s,0.3s;
+ transition-duration: 0.3s,0.3s;
+ -webkit-transition-timing-function: cubic-bezier(0.4,0,0.2,1),cubic-bezier(0.4,0,0.2,1);
+ transition-timing-function: cubic-bezier(0.4,0,0.2,1),cubic-bezier(0.4,0,0.2,1);
+}
+
+.emotion-1 svg * {
+ fill: #808080;
+ -webkit-transition-duration: 0.3s;
+ transition-duration: 0.3s;
+ -webkit-transition-property: fill;
+ transition-property: fill;
+}
+
+.emotion-0 {
+ fill: #808080;
+ position: absolute;
+ left: 0;
+ right: 0;
+ margin: 0 auto;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ pointer-events: none;
+}
+
+.emotion-0 > * {
+ fill: #808080;
+}
+
+.emotion-2 {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ height: 13px;
+ padding: 0 3px;
+ font-size: 11px;
+ line-height: 13px;
+ border: 1px solid #ffffff;
+ color: #ffffff;
+ background-color: #0696d7;
+ border-radius: 4px;
+ pointer-events: none;
+ font-weight: 400;
+ font-family: ArtifaktElement,sans-serif;
+ margin: 0;
+ display: block;
+}
+
+
+`;
+
+exports[`notifications-flyout/presenters/IndicatorPresenter renders without props 1`] = `
+.emotion-3 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ position: relative;
+}
+
+.emotion-2 {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ height: 13px;
+ padding: 0 3px;
+ font-size: 11px;
+ line-height: 13px;
+ border: 1px solid #ffffff;
+ color: #ffffff;
+ background-color: #0696d7;
+ border-radius: 4px;
+ pointer-events: none;
+ font-weight: 400;
+ font-family: ArtifaktElement,sans-serif;
+ margin: 0;
+ display: none;
+}
+
+.emotion-1 {
+ background-color: transparent;
+ border-color: transparent;
+ border-style: solid;
+ border-width: 1px;
+ display: inline-block;
+ position: relative;
+ cursor: pointer;
+ box-sizing: border-box;
+ border-radius: 2px;
+ padding: 0;
+ height: calc(20px + (8px * 2));
+ line-height: calc(20px + (8px * 2));
+ width: calc(20px + (8px * 2));
+ outline: 0;
+ -webkit-transition-property: box-shadow,background-color;
+ transition-property: box-shadow,background-color;
+ -webkit-transition-duration: 0.3s,0.3s;
+ transition-duration: 0.3s,0.3s;
+ -webkit-transition-timing-function: cubic-bezier(0.4,0,0.2,1),cubic-bezier(0.4,0,0.2,1);
+ transition-timing-function: cubic-bezier(0.4,0,0.2,1),cubic-bezier(0.4,0,0.2,1);
+}
+
+.emotion-1 svg * {
+ fill: #808080;
+ -webkit-transition-duration: 0.3s;
+ transition-duration: 0.3s;
+ -webkit-transition-property: fill;
+ transition-property: fill;
+}
+
+.emotion-0 {
+ fill: #808080;
+ position: absolute;
+ left: 0;
+ right: 0;
+ margin: 0 auto;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ pointer-events: none;
+}
+
+.emotion-0 > * {
+ fill: #808080;
+}
+
+
+`;
diff --git a/packages/notifications-panel/src/presenters/__snapshots__/NotificationPresenter.test.js.snap b/packages/notifications-panel/src/presenters/__snapshots__/NotificationPresenter.test.js.snap
new file mode 100644
index 0000000000..3686d15fdf
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/__snapshots__/NotificationPresenter.test.js.snap
@@ -0,0 +1,417 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`notifications-flyout/presenters/NotificationPresenter renders with all props 1`] = `
+.emotion-6 {
+ position: relative;
+ overflow: hidden;
+ -webkit-transition-property: height,opacity;
+ transition-property: height,opacity;
+ -webkit-transition-duration: 300ms;
+ transition-duration: 300ms;
+ -webkit-transition-timing-function: ease;
+ transition-timing-function: ease;
+}
+
+.emotion-6:last-child {
+ border-bottom: none;
+}
+
+.emotion-4 {
+ margin: 12px;
+ word-wrap: break-word;
+ overflow: hidden;
+}
+
+.emotion-0 {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.428571429;
+ margin: 0;
+ text-align: initial;
+}
+
+.emotion-0 ul,
+.emotion-0 ol {
+ padding-left: 24px;
+}
+
+.emotion-0 ul li {
+ list-style: none;
+}
+
+.emotion-0 ul li:before {
+ content: '\\B7';
+ vertical-align: middle;
+ font-size: 20px;
+ padding-right: 12px;
+ margin-left: -14px;
+}
+
+.emotion-0 h1 {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 28px;
+ font-weight: 400;
+ line-height: 1.285714286;
+ margin: 0;
+ text-align: initial;
+}
+
+.emotion-0 h2 {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 24px;
+ font-weight: 400;
+ line-height: 1.25;
+ margin: 0;
+ text-align: initial;
+}
+
+.emotion-0 h3 {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 20px;
+ font-weight: 600;
+ line-height: 1.3;
+ margin: 0;
+ text-align: initial;
+}
+
+.emotion-0 a {
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ cursor: pointer;
+ color: #006eaf;
+ outline: none;
+}
+
+.emotion-0 a:hover {
+ color: #006eaf;
+ -webkit-text-decoration: underline;
+ text-decoration: underline;
+ -webkit-text-decoration-color: #006eaf;
+ text-decoration-color: #006eaf;
+}
+
+.emotion-0 a:focus {
+ color: #006eaf;
+ outline: solid 2px rgba(6,150,215,0.35);
+}
+
+.emotion-0 p {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.428571429;
+ margin: 0 0 12px 0;
+ text-align: initial;
+}
+
+.emotion-0 h1 + p,
+.emotion-0 h2 + p,
+.emotion-0 h3 + p {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.428571429;
+ margin: 16px 0 12px 0;
+ text-align: initial;
+}
+
+.emotion-0 b,
+.emotion-0 strong {
+ font-weight: 700;
+}
+
+.emotion-5 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ border-bottom: 1px solid rgba(60,60,60,0.25);
+ border-left: 3px solid #bec8d2;
+ border-left-color: #6a9728;
+}
+
+.emotion-3 {
+ display: none;
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+
+.emotion-2 {
+ background-color: transparent;
+ border-color: transparent;
+ border-style: solid;
+ border-width: 1px;
+ display: inline-block;
+ position: relative;
+ cursor: pointer;
+ box-sizing: border-box;
+ border-radius: 2px;
+ padding: 0;
+ height: calc(20px + (8px * 2));
+ line-height: calc(20px + (8px * 2));
+ width: calc(20px + (8px * 2));
+ outline: 0;
+ -webkit-transition-property: box-shadow,background-color;
+ transition-property: box-shadow,background-color;
+ -webkit-transition-duration: 0.3s,0.3s;
+ transition-duration: 0.3s,0.3s;
+ -webkit-transition-timing-function: cubic-bezier(0.4,0,0.2,1),cubic-bezier(0.4,0,0.2,1);
+ transition-timing-function: cubic-bezier(0.4,0,0.2,1),cubic-bezier(0.4,0,0.2,1);
+}
+
+.emotion-2 svg * {
+ fill: #808080;
+ -webkit-transition-duration: 0.3s;
+ transition-duration: 0.3s;
+ -webkit-transition-property: fill;
+ transition-property: fill;
+}
+
+.emotion-1 {
+ fill: #808080;
+ position: absolute;
+ left: 0;
+ right: 0;
+ margin: 0 auto;
+ top: 50%;
+ -webkit-transform: translateY(-50%);
+ -ms-transform: translateY(-50%);
+ transform: translateY(-50%);
+ pointer-events: none;
+}
+
+.emotion-1 > * {
+ fill: #808080;
+}
+
+
+
+
+
+ foobar
+
+ 2018-08-17
+
+
+
+
+`;
+
+exports[`notifications-flyout/presenters/NotificationPresenter renders without props 1`] = `
+.emotion-3 {
+ position: relative;
+ overflow: hidden;
+ -webkit-transition-property: height,opacity;
+ transition-property: height,opacity;
+ -webkit-transition-duration: 300ms;
+ transition-duration: 300ms;
+ -webkit-transition-timing-function: ease;
+ transition-timing-function: ease;
+}
+
+.emotion-3:last-child {
+ border-bottom: none;
+}
+
+.emotion-2 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ border-bottom: 1px solid rgba(60,60,60,0.25);
+ border-left: 3px solid #bec8d2;
+ border-left-color: rgba(128,128,128,0.4);
+}
+
+.emotion-1 {
+ margin: 12px;
+ word-wrap: break-word;
+ overflow: hidden;
+}
+
+.emotion-0 {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.428571429;
+ margin: 0;
+ text-align: initial;
+}
+
+.emotion-0 ul,
+.emotion-0 ol {
+ padding-left: 24px;
+}
+
+.emotion-0 ul li {
+ list-style: none;
+}
+
+.emotion-0 ul li:before {
+ content: '\\B7';
+ vertical-align: middle;
+ font-size: 20px;
+ padding-right: 12px;
+ margin-left: -14px;
+}
+
+.emotion-0 h1 {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 28px;
+ font-weight: 400;
+ line-height: 1.285714286;
+ margin: 0;
+ text-align: initial;
+}
+
+.emotion-0 h2 {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 24px;
+ font-weight: 400;
+ line-height: 1.25;
+ margin: 0;
+ text-align: initial;
+}
+
+.emotion-0 h3 {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 20px;
+ font-weight: 600;
+ line-height: 1.3;
+ margin: 0;
+ text-align: initial;
+}
+
+.emotion-0 a {
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ cursor: pointer;
+ color: #006eaf;
+ outline: none;
+}
+
+.emotion-0 a:hover {
+ color: #006eaf;
+ -webkit-text-decoration: underline;
+ text-decoration: underline;
+ -webkit-text-decoration-color: #006eaf;
+ text-decoration-color: #006eaf;
+}
+
+.emotion-0 a:focus {
+ color: #006eaf;
+ outline: solid 2px rgba(6,150,215,0.35);
+}
+
+.emotion-0 p {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.428571429;
+ margin: 0 0 12px 0;
+ text-align: initial;
+}
+
+.emotion-0 h1 + p,
+.emotion-0 h2 + p,
+.emotion-0 h3 + p {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.428571429;
+ margin: 16px 0 12px 0;
+ text-align: initial;
+}
+
+.emotion-0 b,
+.emotion-0 strong {
+ font-weight: 700;
+}
+
+
+`;
diff --git a/packages/notifications-panel/src/presenters/__snapshots__/PanelPresenter.test.js.snap b/packages/notifications-panel/src/presenters/__snapshots__/PanelPresenter.test.js.snap
new file mode 100644
index 0000000000..7642b52334
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/__snapshots__/PanelPresenter.test.js.snap
@@ -0,0 +1,212 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`notifications-flyout/presenters/PanelPresenter renders with all props 1`] = `
+.emotion-1 {
+ width: 300px;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+.emotion-4 {
+ background-color: #ffffff;
+ border-radius: 4px;
+ border: none;
+ box-shadow: 0 0 16px rgba(0,0,0,0.2);
+}
+
+.emotion-3 {
+ position: relative;
+}
+
+.emotion-0 {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.428571429;
+ margin: 0;
+ text-align: initial;
+}
+
+.emotion-2 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ box-sizing: border-box;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ height: 44px;
+ overflow: hidden;
+ background-color: transparent;
+ opacity: 1;
+ -webkit-transition-property: height,opacity;
+ transition-property: height,opacity;
+ -webkit-transition-duration: 300ms;
+ transition-duration: 300ms;
+ -webkit-transition-timing-function: ease;
+ transition-timing-function: ease;
+ pointer-events: none;
+}
+
+
+`;
+
+exports[`notifications-flyout/presenters/PanelPresenter renders without props 1`] = `
+.emotion-1 {
+ width: 300px;
+ overflow-y: auto;
+ overflow-x: hidden;
+}
+
+.emotion-2 {
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ box-sizing: border-box;
+ -webkit-box-pack: center;
+ -webkit-justify-content: center;
+ -ms-flex-pack: center;
+ justify-content: center;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ height: 0;
+ overflow: hidden;
+ background-color: transparent;
+ opacity: 0;
+ -webkit-transition-property: height,opacity;
+ transition-property: height,opacity;
+ -webkit-transition-duration: 300ms;
+ transition-duration: 300ms;
+ -webkit-transition-timing-function: ease;
+ transition-timing-function: ease;
+ pointer-events: none;
+}
+
+.emotion-4 {
+ background-color: #ffffff;
+ border-radius: 4px;
+ border: none;
+ box-shadow: 0 0 16px rgba(0,0,0,0.2);
+}
+
+.emotion-3 {
+ position: relative;
+}
+
+.emotion-0 {
+ color: #3c3c3c;
+ display: block;
+ font-family: ArtifaktElement,sans-serif;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.428571429;
+ margin: 0;
+ text-align: initial;
+}
+
+
+`;
diff --git a/packages/notifications-panel/src/presenters/stylesheet.js b/packages/notifications-panel/src/presenters/stylesheet.js
new file mode 100644
index 0000000000..2b113452e4
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/stylesheet.js
@@ -0,0 +1,155 @@
+import { types } from "../types";
+
+function getRulesByTransitionStatus(transitionStatus) {
+ if (transitionStatus === "exiting") {
+ return { height: 0, opacity: 0 };
+ }
+ if (transitionStatus === "exited") {
+ return { display: "none" };
+ }
+ return {};
+}
+
+function getRulesByType(type, themeData) {
+ switch (type) {
+ case types.ERROR:
+ return { borderLeftColor: themeData["colorScheme.status.error"] };
+ case types.WARNING:
+ return { borderLeftColor: themeData["colorScheme.status.warning"] };
+ case types.SUCCESS:
+ return { borderLeftColor: themeData["colorScheme.status.success"] };
+ default:
+ return {
+ borderLeftColor: themeData["basics.colors.primary.autodeskBlue.500"],
+ };
+ }
+}
+
+export default function stylesheet(props, themeData) {
+ const {
+ hasHover,
+ loadingTransitionState,
+ showCount,
+ stylesheet: customStylesheet,
+ transitionStatus,
+ type,
+ unread,
+ } = props;
+ const isEntering =
+ loadingTransitionState === `entering` ||
+ loadingTransitionState === `entered`;
+ const styles = {
+ dismissButton: {
+ display: `none`,
+ position: `absolute`,
+ top: 0,
+ right: 0,
+ ...(hasHover ? { display: `block` } : {}),
+ },
+ notification: {
+ position: `relative`,
+ overflow: `hidden`,
+ transitionProperty: `height, opacity`,
+ transitionDuration: `300ms`,
+ transitionTimingFunction: `ease`,
+ "&:last-child": {
+ borderBottom: `none`,
+ },
+ ...(transitionStatus ? getRulesByTransitionStatus(transitionStatus) : {}),
+ ...(hasHover
+ ? { backgroundColor: themeData["colorScheme.surface.level300"] }
+ : {}),
+ },
+ notificationContent: {
+ display: `flex`,
+ borderBottom: `1px solid ${themeData["divider.heavyColor"]}`,
+ borderLeft: `3px solid #bec8d2`,
+ ...(unread
+ ? getRulesByType(type, themeData)
+ : { borderLeftColor: `rgba(128, 128, 128, 0.4)` }),
+ },
+ notificationContentImage: {
+ margin: `${themeData["density.spacings.small"]} 0 ${themeData["density.spacings.small"]} ${themeData["density.spacings.small"]}`,
+ },
+ notificationContentText: {
+ margin: `${themeData["density.spacings.small"]}`,
+ wordWrap: `break-word`,
+ overflow: `hidden`,
+ },
+ panelTitle: {
+ padding: `${themeData["density.spacings.small"]}
+ ${themeData["density.spacings.large"]}`,
+ borderBottom: `1px solid ${themeData["divider.heavyColor"]}`,
+ },
+ panelContainer: {
+ width: `100%`,
+ overflowY: `auto`,
+ overflowX: `hidden`,
+ },
+ panelLoading: {
+ display: `flex`,
+ boxSizing: `border-box`,
+ justifyContent: `center`,
+ alignItems: `center`,
+ height: 0,
+ overflow: `hidden`,
+ backgroundColor: `transparent`,
+ opacity: `0`,
+ transitionProperty: `height, opacity`,
+ transitionDuration: `300ms`,
+ transitionTimingFunction: `ease`,
+ pointerEvents: `none`,
+ ...(isEntering ? { height: `44px`, opacity: 1 } : {}),
+ },
+ notificationsFooter:{
+ height: '2.5rem',
+ backgroundColor: 'white',
+ bottom: '0',
+ left: '0',
+ right: '0',
+ padding: '0.5rem',
+ marginBottom: '0px'
+ },
+ indicator: {
+ display: `flex`,
+ position: `relative`,
+ },
+ indicatorCount: {
+ position: `absolute`,
+ top: `50%`,
+ left: `50%`,
+ height: `13px`,
+ padding: `0 3px`,
+ fontSize: `11px`,
+ lineHeight: `13px`,
+ border: `1px solid ${themeData["colorScheme.surface.level100"]}`,
+ color: themeData["basics.colors.primary.white"],
+ backgroundColor: `#0696d7`,
+ borderRadius: `4px`,
+ pointerEvents: `none`,
+ fontWeight: 400,
+ fontFamily: themeData["notifications.fontFamily"],
+ margin: 0,
+ ...(showCount ? { display: `block` } : { display: `none` }),
+ },
+ image: {
+ height: `48px`,
+ width: `48px`,
+ overflow: `hidden`,
+ borderRadius: `4px`,
+ },
+ emptyState: {
+ display: `flex`,
+ flexDirection: `column`,
+ alignItems: `center`,
+ },
+ emptyStateImage: {
+ marginTop: `20px`,
+ },
+ emptyStateMessage: {
+ margin: `20px 0 37px`,
+ },
+ };
+
+ return customStylesheet ? customStylesheet(styles, props, themeData) : styles;
+}
diff --git a/packages/notifications-panel/src/presenters/stylesheet.test.js b/packages/notifications-panel/src/presenters/stylesheet.test.js
new file mode 100644
index 0000000000..4e00fe8c55
--- /dev/null
+++ b/packages/notifications-panel/src/presenters/stylesheet.test.js
@@ -0,0 +1,78 @@
+import stylesheet from "./stylesheet";
+
+describe("notifications-flyout/stylesheet", () => {
+ const styles = stylesheet({}, {}, {}, {});
+
+ it("returns an object", () => {
+ expect(styles).toEqual(expect.any(Object));
+ });
+
+ it("returned object contains property of dismissButton", () => {
+ expect(styles).toHaveProperty("dismissButton", expect.any(Object));
+ });
+
+ it("returned object contains property of notification", () => {
+ expect(styles).toHaveProperty("notification", expect.any(Object));
+ });
+
+ it("returned object contains property of notificationContent", () => {
+ expect(styles).toHaveProperty("notificationContent", expect.any(Object));
+ });
+
+ it("returned object contains property of notificationContentImage", () => {
+ expect(styles).toHaveProperty(
+ "notificationContentImage",
+ expect.any(Object)
+ );
+ });
+
+ it("returned object contains property of notificationContentText", () => {
+ expect(styles).toHaveProperty(
+ "notificationContentText",
+ expect.any(Object)
+ );
+ });
+
+ it("returned object contains property of panelTitle", () => {
+ expect(styles).toHaveProperty("panelTitle", expect.any(Object));
+ });
+
+ it("returned object contains property of panelContainer", () => {
+ expect(styles).toHaveProperty("panelContainer", expect.any(Object));
+ });
+
+ it("returned object contains property of panelLoading", () => {
+ expect(styles).toHaveProperty("panelLoading", expect.any(Object));
+ });
+
+ it("returned object contains property of indicator", () => {
+ expect(styles).toHaveProperty("indicator", expect.any(Object));
+ });
+
+ it("returned object contains property of indicatorCount", () => {
+ expect(styles).toHaveProperty("indicatorCount", expect.any(Object));
+ });
+
+ it("returned object contains property of image", () => {
+ expect(styles).toHaveProperty("image", expect.any(Object));
+ });
+
+ it("returned object contains property of emptyState", () => {
+ expect(styles).toHaveProperty("emptyState", expect.any(Object));
+ });
+
+ it("returned object contains property of emptyStateImage", () => {
+ expect(styles).toHaveProperty("emptyStateImage", expect.any(Object));
+ });
+
+ it("returned object contains property of emptyStateMessage", () => {
+ expect(styles).toHaveProperty("emptyStateMessage", expect.any(Object));
+ });
+
+ it("returns the custom stylesheet", () => {
+ const props = {
+ stylesheet: () => ({ padding: 0 }),
+ };
+ expect(stylesheet(props, {})).toEqual({ padding: 0 });
+ });
+});
diff --git a/packages/notifications-panel/src/types.js b/packages/notifications-panel/src/types.js
new file mode 100644
index 0000000000..273ccb0b0f
--- /dev/null
+++ b/packages/notifications-panel/src/types.js
@@ -0,0 +1,8 @@
+export const types = Object.freeze({
+ ERROR: "error",
+ PRIMARY: "primary",
+ SUCCESS: "success",
+ WARNING: "warning",
+});
+
+export const AVAILABLE_TYPES = Object.freeze(Object.values(types));