@camunda/camunda-composite-components 0.0.31-rc1 → 0.0.31-rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/lib/esm/api/api.d.ts +22 -0
  2. package/lib/esm/api/api.js +66 -0
  3. package/lib/esm/api/endpoints.const.d.ts +9 -0
  4. package/lib/esm/api/endpoints.const.js +18 -0
  5. package/lib/esm/api/jwt.utils.d.ts +5 -0
  6. package/lib/esm/api/jwt.utils.js +23 -0
  7. package/lib/esm/api/notifications.d.ts +39 -0
  8. package/lib/esm/api/notifications.js +153 -0
  9. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-info-sidebar.d.ts +6 -0
  10. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-info-sidebar.js +40 -0
  11. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar-element.d.ts +11 -0
  12. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar-element.js +16 -0
  13. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar.d.ts +9 -0
  14. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar.js +38 -0
  15. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar.types.d.ts +58 -0
  16. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar.types.js +1 -0
  17. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-notification-sidebar.d.ts +5 -0
  18. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-notification-sidebar.js +110 -0
  19. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-org-sidebar.d.ts +6 -0
  20. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-org-sidebar.js +52 -0
  21. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-user-sidebar.d.ts +6 -0
  22. package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-user-sidebar.js +44 -0
  23. package/lib/esm/components/c3-navigation/c3-navigation.d.ts +1 -1
  24. package/lib/esm/components/c3-navigation/c3-navigation.js +21 -214
  25. package/lib/esm/components/c3-navigation/c3-navigation.types.d.ts +15 -40
  26. package/lib/esm/components/c3-navigation/c3-notification-provider/c3-notification-container.d.ts +12 -0
  27. package/lib/esm/components/c3-navigation/c3-notification-provider/c3-notification-container.js +106 -0
  28. package/lib/esm/components/c3-navigation/c3-notification-provider/c3-notification-provider.d.ts +30 -0
  29. package/lib/esm/components/c3-navigation/c3-notification-provider/c3-notification-provider.js +91 -0
  30. package/lib/esm/components/c3-navigation/helpers.d.ts +4 -0
  31. package/lib/esm/components/c3-navigation/helpers.js +62 -0
  32. package/lib/esm/components/c3-navigation/index.d.ts +3 -0
  33. package/lib/esm/components/c3-navigation/index.js +3 -0
  34. package/lib/esm/components/c3-navigation/story-helpers.d.ts +23 -0
  35. package/lib/esm/components/c3-navigation/story-helpers.js +179 -0
  36. package/lib/esm/components/c3-user-configuration/c3-user-configuration-provider.d.ts +13 -0
  37. package/lib/esm/components/c3-user-configuration/c3-user-configuration-provider.js +4 -0
  38. package/lib/esm/icons/c3-icons.d.ts +2 -0
  39. package/lib/esm/icons/c3-icons.js +6 -1
  40. package/lib/esm/index.d.ts +5 -2
  41. package/lib/esm/index.js +2 -1
  42. package/package.json +8 -2
@@ -0,0 +1,22 @@
1
+ import { Stage } from "./endpoints.const";
2
+ export declare class HttpError extends Error {
3
+ status: number;
4
+ constructor(message: string, status: number);
5
+ }
6
+ export interface RequestPayload {
7
+ base?: "notifications";
8
+ stage?: Stage;
9
+ url: string;
10
+ method: "get" | "post" | "put" | "delete" | "patch";
11
+ headers?: {
12
+ [key: string]: string;
13
+ };
14
+ type?: "json" | "text" | "none";
15
+ responseType?: "json" | "text" | "none";
16
+ camundaAuth?: {
17
+ token: string;
18
+ refreshTokenMethod: () => Promise<string>;
19
+ };
20
+ body?: any;
21
+ }
22
+ export declare function request(payload: RequestPayload): Promise<any>;
@@ -0,0 +1,66 @@
1
+ import { getEndpoint, NOTIFICATIONS } from "./endpoints.const";
2
+ import { JWTUtils } from "./jwt.utils";
3
+ export class HttpError extends Error {
4
+ status;
5
+ constructor(message, status) {
6
+ super(message);
7
+ this.status = status;
8
+ }
9
+ }
10
+ export async function request(payload) {
11
+ const headers = {};
12
+ if (payload.camundaAuth) {
13
+ if (JWTUtils.isExpired(payload.camundaAuth.token)) {
14
+ const newToken = await payload.camundaAuth.refreshTokenMethod();
15
+ headers.Authorization = `Bearer ${newToken}`;
16
+ }
17
+ else {
18
+ headers.Authorization = `Bearer ${payload.camundaAuth.token}`;
19
+ }
20
+ }
21
+ const type = payload.type ?? "json";
22
+ if (type) {
23
+ switch (type) {
24
+ case "json":
25
+ headers["Content-Type"] = "application/json";
26
+ break;
27
+ case "text":
28
+ headers["Content-Type"] = "text/plain";
29
+ break;
30
+ }
31
+ }
32
+ const body = payload.body ? JSON.stringify(payload.body) : undefined;
33
+ let base;
34
+ if (payload.base && payload.stage) {
35
+ switch (payload.base) {
36
+ case "notifications":
37
+ base = getEndpoint(payload.stage, NOTIFICATIONS);
38
+ break;
39
+ }
40
+ }
41
+ const url = base ? `${base}/${payload.url}` : payload.url;
42
+ const response = await fetch(url, {
43
+ method: payload.method.toUpperCase(),
44
+ headers,
45
+ body,
46
+ });
47
+ let success = true;
48
+ if (response.status >= 400 && response.status < 500) {
49
+ success = false;
50
+ }
51
+ if (response.status >= 500) {
52
+ success = false;
53
+ }
54
+ if (!success) {
55
+ throw new HttpError(response.statusText, response.status);
56
+ }
57
+ const responseType = payload.responseType ?? type;
58
+ switch (responseType) {
59
+ case "json":
60
+ return response.json();
61
+ case "text":
62
+ return response.text();
63
+ default:
64
+ return response;
65
+ }
66
+ }
@@ -0,0 +1,9 @@
1
+ export interface Endpoint {
2
+ id: string;
3
+ dev: string;
4
+ int: string;
5
+ prod: string;
6
+ }
7
+ export declare const NOTIFICATIONS: Endpoint;
8
+ export declare type Stage = "dev" | "int" | "prod";
9
+ export declare function getEndpoint(stage: Stage, endpoint: Endpoint): string;
@@ -0,0 +1,18 @@
1
+ export const NOTIFICATIONS = {
2
+ id: "notifications",
3
+ dev: "https://notifications.cloud.dev.ultrawombat.com",
4
+ int: "https://notifications.cloud.ultrawombat.com",
5
+ prod: "https://notifications.cloud.camunda.io",
6
+ };
7
+ export function getEndpoint(stage, endpoint) {
8
+ switch (stage) {
9
+ case "dev":
10
+ return endpoint.dev;
11
+ case "int":
12
+ return endpoint.int;
13
+ case "prod":
14
+ return endpoint.prod;
15
+ default:
16
+ throw new Error(`Unknown stage: ${stage}`);
17
+ }
18
+ }
@@ -0,0 +1,5 @@
1
+ export declare class JWTUtils {
2
+ static decode(token: string): any;
3
+ static getExpiration(token: string): number;
4
+ static isExpired(token: string): boolean;
5
+ }
@@ -0,0 +1,23 @@
1
+ export class JWTUtils {
2
+ static decode(token) {
3
+ const base64Url = token.split(".")[1];
4
+ const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
5
+ const jsonPayload = decodeURIComponent(window
6
+ .atob(base64)
7
+ .split("")
8
+ .map(function (c) {
9
+ return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
10
+ })
11
+ .join(""));
12
+ return JSON.parse(jsonPayload);
13
+ }
14
+ static getExpiration(token) {
15
+ const decoded = JWTUtils.decode(token);
16
+ return decoded.exp;
17
+ }
18
+ static isExpired(token) {
19
+ const expiration = JWTUtils.getExpiration(token);
20
+ const now = new Date().getTime() / 1000;
21
+ return expiration < now;
22
+ }
23
+ }
@@ -0,0 +1,39 @@
1
+ import { C3NotificationsProps } from "../components/c3-navigation/c3-navigation.types";
2
+ export interface Notification {
3
+ uuid: string;
4
+ userId?: string;
5
+ orgId?: string;
6
+ timestamp: number;
7
+ source: string;
8
+ type: string;
9
+ title: string;
10
+ description: string;
11
+ state: string;
12
+ meta?: {
13
+ identifier?: string;
14
+ href?: string;
15
+ label?: string;
16
+ entity?: {
17
+ id: string;
18
+ type: string;
19
+ };
20
+ parentEntity?: {
21
+ id: string;
22
+ type: string;
23
+ };
24
+ };
25
+ }
26
+ export declare type AnalyticsEvent = "notification-panel-opened" | "notification-clicked-cta";
27
+ export declare class NotificationService {
28
+ static getNotifications(options: C3NotificationsProps): Promise<Notification[]>;
29
+ static sendAnalyticsEvent(options: C3NotificationsProps, eventOptions: {
30
+ event: AnalyticsEvent;
31
+ id?: string;
32
+ }): void;
33
+ static changeState(options: C3NotificationsProps, notificationId: string, operation: "read" | "dismiss"): void;
34
+ static changeManyStates(options: C3NotificationsProps, notificationIds: string[], operation: "read" | "dismiss"): void;
35
+ static notificationsStream(options: C3NotificationsProps, handler: (notification: Notification) => void): void;
36
+ static updateNotifications(allNotifications: Notification[], newNotification: Notification): Notification[];
37
+ static updateNotificationState(allNotifications: Notification[], notificationId: string, newState: "read" | "dismiss"): Notification[];
38
+ static updateNotificationStates(allNotifications: Notification[], notificationIds: string[], newState: "read" | "dismiss"): Notification[];
39
+ }
@@ -0,0 +1,153 @@
1
+ import { EventSourcePolyfill } from "event-source-polyfill";
2
+ import { request } from "./api";
3
+ import { getEndpoint, NOTIFICATIONS } from "./endpoints.const";
4
+ export class NotificationService {
5
+ static async getNotifications(options) {
6
+ let notifications = [];
7
+ try {
8
+ notifications = await request({
9
+ method: "get",
10
+ base: "notifications",
11
+ stage: options.stage,
12
+ camundaAuth: {
13
+ token: options.userToken,
14
+ refreshTokenMethod: options.getNewUserToken,
15
+ },
16
+ url: `notifications/orgs/${options.activeOrganizationId}`,
17
+ });
18
+ return notifications;
19
+ }
20
+ catch (error) {
21
+ console.error(error);
22
+ }
23
+ return notifications;
24
+ }
25
+ static sendAnalyticsEvent(options, eventOptions) {
26
+ request({
27
+ method: "post",
28
+ base: "notifications",
29
+ stage: options.stage,
30
+ camundaAuth: {
31
+ token: options.userToken,
32
+ refreshTokenMethod: options.getNewUserToken,
33
+ },
34
+ url: `analytics/${options.activeOrganizationId}/${eventOptions.event}`,
35
+ body: {
36
+ id: eventOptions.id,
37
+ },
38
+ responseType: "text",
39
+ })
40
+ .then(() => {
41
+ // do nothing
42
+ })
43
+ .catch((error) => {
44
+ console.error(error);
45
+ });
46
+ }
47
+ static changeState(options, notificationId, operation) {
48
+ request({
49
+ method: "patch",
50
+ base: "notifications",
51
+ stage: options.stage,
52
+ camundaAuth: {
53
+ token: options.userToken,
54
+ refreshTokenMethod: options.getNewUserToken,
55
+ },
56
+ url: `notifications/${notificationId}/${operation}`,
57
+ })
58
+ .then(() => {
59
+ // do nothing
60
+ })
61
+ .catch((error) => {
62
+ console.error(error);
63
+ });
64
+ }
65
+ static changeManyStates(options, notificationIds, operation) {
66
+ if (notificationIds.length === 0) {
67
+ return;
68
+ }
69
+ request({
70
+ method: "patch",
71
+ base: "notifications",
72
+ stage: options.stage,
73
+ camundaAuth: {
74
+ token: options.userToken,
75
+ refreshTokenMethod: options.getNewUserToken,
76
+ },
77
+ url: `notifications/batch/state/${operation}`,
78
+ body: {
79
+ uuids: notificationIds,
80
+ },
81
+ })
82
+ .then(() => {
83
+ // do nothing
84
+ })
85
+ .catch((error) => {
86
+ console.error(error);
87
+ });
88
+ }
89
+ static notificationsStream(options, handler) {
90
+ const source = new EventSourcePolyfill(`${getEndpoint(options.stage, NOTIFICATIONS)}/notifications/events`, {
91
+ headers: {
92
+ authorization: `Bearer ${options.userToken}`,
93
+ },
94
+ heartbeatTimeout: 1000 * 60 * 60,
95
+ });
96
+ source.onmessage = function (event) {
97
+ try {
98
+ const notification = JSON.parse(event.data);
99
+ if (!notification.keepAlive) {
100
+ handler(notification);
101
+ }
102
+ }
103
+ catch (error) {
104
+ console.error(`Failed to handle SSE event`, { error });
105
+ }
106
+ };
107
+ }
108
+ // updates <allNotifications> with <newNotification>
109
+ // if <newNotification> is read or new, it will be added to <allNotifications>
110
+ // if <newNotification> is dismissed, it will be removed from <allNotifications>
111
+ static updateNotifications(allNotifications, newNotification) {
112
+ const updatedNotifications = allNotifications
113
+ .slice()
114
+ .filter((currentNotification) => currentNotification.uuid !== newNotification.uuid);
115
+ if (newNotification.state === "read" || newNotification.state === "new") {
116
+ updatedNotifications.push(newNotification);
117
+ }
118
+ updatedNotifications.sort((a, b) => b.timestamp - a.timestamp);
119
+ return updatedNotifications;
120
+ }
121
+ static updateNotificationState(allNotifications, notificationId, newState) {
122
+ let updatedNotifications = allNotifications.slice();
123
+ let foundNotification;
124
+ switch (newState) {
125
+ case "dismiss":
126
+ updatedNotifications = updatedNotifications.filter((notification) => notification.uuid !== notificationId);
127
+ break;
128
+ case "read":
129
+ foundNotification = updatedNotifications.find((currentNotification) => currentNotification.uuid === notificationId);
130
+ if (foundNotification) {
131
+ foundNotification.state = newState;
132
+ }
133
+ break;
134
+ }
135
+ return updatedNotifications;
136
+ }
137
+ static updateNotificationStates(allNotifications, notificationIds, newState) {
138
+ let updatedNotifications = allNotifications.slice();
139
+ let foundNotifications = [];
140
+ switch (newState) {
141
+ case "dismiss":
142
+ updatedNotifications = updatedNotifications.filter((notification) => !notificationIds.includes(notification.uuid));
143
+ break;
144
+ case "read":
145
+ foundNotifications = updatedNotifications.filter((notification) => notificationIds.includes(notification.uuid));
146
+ foundNotifications.forEach((foundNotification) => {
147
+ foundNotification.state = newState;
148
+ });
149
+ break;
150
+ }
151
+ return updatedNotifications;
152
+ }
153
+ }
@@ -0,0 +1,6 @@
1
+ import { FC } from "react";
2
+ import { C3NavigationInfoSideBarProps } from "./c3-navigation-sidebar.types";
3
+ declare const C3InfoSidebar: FC<{
4
+ sideBar: C3NavigationInfoSideBarProps;
5
+ }>;
6
+ export default C3InfoSidebar;
@@ -0,0 +1,40 @@
1
+ import React, { useState } from "react";
2
+ import { SwitcherDivider } from "@carbon/react";
3
+ import { Help } from "@carbon/react/icons";
4
+ import C3NavigationSideBar from "./c3-navigation-sidebar";
5
+ const C3InfoSidebar = ({ sideBar, }) => {
6
+ const { version, isOpen, ...sideBarProps } = sideBar;
7
+ const [isSidebarOpen, setIsSidebarOpen] = useState(isOpen);
8
+ return (React.createElement(C3NavigationSideBar, { sideBar: {
9
+ ...sideBarProps,
10
+ ariaLabel: sideBarProps.ariaLabel || "Info Sidebar",
11
+ isOpen: isSidebarOpen,
12
+ setIsOpen: setIsSidebarOpen,
13
+ }, icon: React.createElement(Help, { size: 20 }), bottomChildren: version !== undefined && (React.createElement(React.Fragment, null,
14
+ React.createElement(SwitcherDivider, null),
15
+ React.createElement("span", { className: "cds--switcher__item", style: {
16
+ padding: "var(--cds-spacing-05)",
17
+ paddingTop: "var(--cds-spacing-03)",
18
+ paddingBottom: 0,
19
+ color: "var(--cds-text-primary)",
20
+ fontSize: "var(--cds-body-01-font-size)",
21
+ fontWeight: "var(--cds-body-01-font-weight)",
22
+ lineHeight: "var(--cds-body-01-line-height)",
23
+ letterSpacing: "var(--cds-body-01-letter-spacing)",
24
+ } },
25
+ "Version ",
26
+ version),
27
+ React.createElement("span", { className: "cds--switcher__item", style: {
28
+ paddingRight: "var(--cds-spacing-05)",
29
+ paddingLeft: "var(--cds-spacing-05)",
30
+ color: "var(--cds-text-secondary)",
31
+ fontSize: "var(--cds-label-01-font-size)",
32
+ fontWeight: "var(--cds-label-01-font-weight)",
33
+ lineHeight: "var(--cds-label-01-line-height)",
34
+ letterSpacing: "var(--cds-label-01-letter-spacing)",
35
+ } },
36
+ `© Camunda Services GmbH ${new Date().getFullYear()}`,
37
+ React.createElement("br", null),
38
+ " All rights reserved."))) }));
39
+ };
40
+ export default C3InfoSidebar;
@@ -0,0 +1,11 @@
1
+ /// <reference types="react" />
2
+ import { C3NavigationElementProps } from "../c3-navigation.types";
3
+ import { C3NavigationSideBarProps } from "./c3-navigation-sidebar.types";
4
+ declare const C3NavigationSidebarElement: (props: {
5
+ element: C3NavigationElementProps;
6
+ index: number;
7
+ itemTabIndex?: number | undefined;
8
+ sideBar: C3NavigationSideBarProps;
9
+ setSideBarOpen: (open: boolean) => void;
10
+ }) => JSX.Element;
11
+ export default C3NavigationSidebarElement;
@@ -0,0 +1,16 @@
1
+ import { Button, SwitcherDivider } from "@carbon/react";
2
+ import React from "react";
3
+ const C3NavigationSidebarElement = (props) => (React.createElement(React.Fragment, null,
4
+ props.element.preceedingDivider && React.createElement(SwitcherDivider, null),
5
+ React.createElement(Button, { style: props.index === 0 &&
6
+ (!("elements" in props.sideBar) || !props.sideBar.elements)
7
+ ? { marginTop: "1.5rem", whiteSpace: "nowrap" }
8
+ : { whiteSpace: "nowrap" }, size: "sm", kind: props.element.kind ?? "ghost", className: "cds--switcher__item", onClick: () => {
9
+ if (props.element.onClick) {
10
+ props.element.onClick();
11
+ }
12
+ if (props.sideBar.closeOnClick !== false) {
13
+ props.setSideBarOpen(false);
14
+ }
15
+ }, tabIndex: props.itemTabIndex }, props.element.label)));
16
+ export default C3NavigationSidebarElement;
@@ -0,0 +1,9 @@
1
+ import { ReactElement, ReactNode } from "react";
2
+ import { C3NavigationSideBarProps } from "./c3-navigation-sidebar.types";
3
+ declare const C3NavigationSideBar: (props: {
4
+ sideBar: C3NavigationSideBarProps;
5
+ icon: ReactElement;
6
+ children?: ReactNode;
7
+ bottomChildren?: ReactNode;
8
+ }) => JSX.Element;
9
+ export default C3NavigationSideBar;
@@ -0,0 +1,38 @@
1
+ import { Button, HeaderGlobalAction, HeaderPanel, Stack, SwitcherDivider, } from "@carbon/react";
2
+ import React from "react";
3
+ import { useOnClickOutside } from "../helpers";
4
+ import C3NavigationSidebarElement from "./c3-navigation-sidebar-element";
5
+ const C3NavigationSideBar = (props) => {
6
+ const { icon, sideBar, children, bottomChildren } = props;
7
+ const { isOpen, setIsOpen } = sideBar;
8
+ const itemTabIndex = isOpen ? undefined : -1;
9
+ const [panelRef, iconRef] = useOnClickOutside(() => isOpen && setIsOpen(false));
10
+ return (React.createElement(React.Fragment, null,
11
+ React.createElement(HeaderGlobalAction, { ref: iconRef, "aria-label": `Open ${sideBar.ariaLabel}`, onClick: () => setIsOpen(!isOpen), isActive: isOpen, tooltipAlignment: sideBar.type === "user" ? "end" : "center" }, icon),
12
+ React.createElement(HeaderPanel, { ref: panelRef, "aria-label": sideBar.ariaLabel, expanded: isOpen, style: {
13
+ display: "grid",
14
+ gridAutoFlow: "row",
15
+ gridAutoRows: "max-content 1fr",
16
+ overflow: "auto",
17
+ } },
18
+ React.createElement(Stack, null,
19
+ children,
20
+ sideBar.elements &&
21
+ sideBar.elements.length > 0 &&
22
+ "customElements" in sideBar &&
23
+ sideBar.customElements &&
24
+ "activeOrganization" in sideBar.customElements &&
25
+ !sideBar.customElements?.activeOrganization && React.createElement(SwitcherDivider, null),
26
+ sideBar.elements?.map((element, index) => (React.createElement(C3NavigationSidebarElement, { key: element.key, element: element, index: index, sideBar: sideBar, setSideBarOpen: setIsOpen, itemTabIndex: itemTabIndex }))),
27
+ bottomChildren),
28
+ sideBar.bottomElements &&
29
+ sideBar.bottomElements.map((element) => (React.createElement(Button, { kind: element.kind, key: element.key, className: "cds--switcher__item", renderIcon: element.renderIcon, onClick: () => {
30
+ if (element.onClick) {
31
+ element.onClick();
32
+ }
33
+ if (sideBar.closeOnClick !== false) {
34
+ setIsOpen(false);
35
+ }
36
+ }, style: { alignSelf: "end" }, tabIndex: itemTabIndex }, element.label))))));
37
+ };
38
+ export default C3NavigationSideBar;
@@ -0,0 +1,58 @@
1
+ import { C3NavigationElementProps } from "../c3-navigation.types";
2
+ import { Notification } from "../../../api/notifications";
3
+ export declare type C3NavigationSideBarBaseProps = {
4
+ ariaLabel?: string;
5
+ isOpen?: boolean;
6
+ closeOnClick?: boolean;
7
+ elementClicked?: (key: string) => void;
8
+ elements?: C3NavigationElementProps[];
9
+ bottomElements?: C3NavigationElementProps[];
10
+ };
11
+ export declare type C3NavigationOrgSideBarProps = C3NavigationSideBarBaseProps & {
12
+ type: "org";
13
+ customElements?: {
14
+ activeOrganization?: {
15
+ activeLabel: string;
16
+ otherLabel: string;
17
+ orgName: string;
18
+ action: {
19
+ label: string;
20
+ onClick: () => void;
21
+ };
22
+ };
23
+ };
24
+ };
25
+ export declare type C3NavigationInfoSideBarProps = C3NavigationSideBarBaseProps & {
26
+ type: "info";
27
+ version?: string;
28
+ };
29
+ export declare type C3NavigationUserSideBarProps = C3NavigationSideBarBaseProps & {
30
+ type: "user";
31
+ customElements?: {
32
+ profile?: {
33
+ label: string;
34
+ user: {
35
+ name: string;
36
+ email: string;
37
+ };
38
+ };
39
+ themeSelector?: {
40
+ currentTheme: string;
41
+ onChange: (newValue: string) => void;
42
+ };
43
+ stageToggle?: {
44
+ prodFeaturesEnabled: boolean;
45
+ toggle: () => void;
46
+ };
47
+ };
48
+ };
49
+ export declare type C3NavigationNotificationsSideBarProps = C3NavigationSideBarBaseProps & {
50
+ type: "notifications";
51
+ onLinkClick?: (meta: Notification["meta"]) => void;
52
+ };
53
+ export declare type C3NavigationAppBarProps = C3NavigationSideBarBaseProps & {
54
+ type: "app";
55
+ };
56
+ export declare type C3NavigationSideBarProps = {
57
+ setIsOpen: (isOpen: boolean) => void;
58
+ } & (C3NavigationOrgSideBarProps | C3NavigationInfoSideBarProps | C3NavigationUserSideBarProps | C3NavigationNotificationsSideBarProps | C3NavigationAppBarProps);
@@ -0,0 +1,5 @@
1
+ import { FC } from "react";
2
+ import { C3NavigationNotificationsSideBarProps } from "./c3-navigation-sidebar.types";
3
+ export declare const C3NotificationSidebar: FC<{
4
+ sideBar: C3NavigationNotificationsSideBarProps;
5
+ }>;
@@ -0,0 +1,110 @@
1
+ import { Button } from "@carbon/react";
2
+ import { Notification as NotificationIcon } from "@carbon/react/icons";
3
+ import React, { useContext, useEffect, useState } from "react";
4
+ import styled from "styled-components";
5
+ import { C3BellIcon, C3NotificationsUnreadIcon } from "../../../icons/c3-icons";
6
+ import C3NotificationContainer, { NotificationDescription, NotificationTitle, } from "../c3-notification-provider/c3-notification-container";
7
+ import { C3NotificationContext } from "../c3-notification-provider/c3-notification-provider";
8
+ import C3NavigationSideBar from "./c3-navigation-sidebar";
9
+ const PanelHeader = styled.div `
10
+ background: var(--cds-layer-01);
11
+ box-shadow: inset 0px -1px 0px var(--cds-border-subtle-01);
12
+ width: 100%;
13
+ height: 60px;
14
+ padding: 24px 16px 13px;
15
+ display: flex;
16
+ flex-direction: row;
17
+ justify-content: space-between;
18
+ align-items: center;
19
+ `;
20
+ const PanelTitle = styled.span `
21
+ color: var(--cds-text-primary);
22
+ font-size: var(--cds-body-01-font-size);
23
+ font-weight: var(--cds-body-01-font-weight);
24
+ line-height: var(--cds-body-01-line-height);
25
+ letter-spacing: var(--cds-body-01-letter-spacing);
26
+ `;
27
+ const DismissAllButton = styled(Button) `
28
+ font-size: var(--cds-helper-text-01-font-size);
29
+ font-weight: var(--cds-helper-text-01-font-weight);
30
+ line-height: var(--cds-helper-text-01-line-height);
31
+ letter-spacing: var(--cds-helper-text-01-letter-spacing);
32
+ `;
33
+ const EmptyState = styled.div `
34
+ padding: 16px;
35
+ `;
36
+ const EmptyStateTitle = styled(NotificationTitle) `
37
+ margin-top: 24px;
38
+ `;
39
+ const EmptyStateDescription = styled(NotificationDescription) `
40
+ color: var(--cds-text-secondary);
41
+ margin-top: 8px;
42
+ `;
43
+ export const C3NotificationSidebar = ({ sideBar }) => {
44
+ const { isOpen, onLinkClick } = sideBar;
45
+ const [isSidebarOpen, setIsSidebarOpen] = useState(isOpen);
46
+ const { notifications, markAsRead, dismiss, markAllAsRead, dismissAll, analytics, enabled, } = useContext(C3NotificationContext);
47
+ const [unreadNotifications, setUnreadNotifications] = useState([]);
48
+ const hasUnreadNotifications = notifications.some(({ state }) => state === "new");
49
+ const markAllAsReadSidebar = () => {
50
+ const newNotifications = notifications.filter((notification) => notification.state === "new");
51
+ markAllAsRead(newNotifications);
52
+ };
53
+ const dismissAllSidebar = () => dismissAll(notifications);
54
+ const setIsOpen = (open) => {
55
+ if (open) {
56
+ // remember new notifications, so the dots can be displayed
57
+ setUnreadNotifications(notifications
58
+ .filter(({ state }) => state === "new")
59
+ .map(({ uuid }) => uuid));
60
+ markAllAsReadSidebar();
61
+ if (enabled && analytics) {
62
+ analytics("notification-panel-opened");
63
+ }
64
+ }
65
+ setIsSidebarOpen(open);
66
+ if (!open) {
67
+ markAllAsReadSidebar();
68
+ setUnreadNotifications([]);
69
+ }
70
+ };
71
+ const sortNotificationsDescending = (a, b) => {
72
+ if (new Date(a.timestamp) > new Date(b.timestamp))
73
+ return -1;
74
+ return 0;
75
+ };
76
+ useEffect(() => {
77
+ // mark notifications as read on unmount
78
+ return () => {
79
+ if (isSidebarOpen)
80
+ markAllAsReadSidebar();
81
+ };
82
+ }, []);
83
+ return (React.createElement(C3NavigationSideBar, { sideBar: {
84
+ ...sideBar,
85
+ ariaLabel: sideBar.ariaLabel || "Notification Sidebar",
86
+ isOpen: isSidebarOpen,
87
+ setIsOpen,
88
+ }, icon: hasUnreadNotifications ? (React.createElement(C3NotificationsUnreadIcon, { size: 20 })) : (React.createElement(NotificationIcon, { size: 20, label: "Notifications" })) },
89
+ React.createElement(PanelHeader, null,
90
+ React.createElement(PanelTitle, null, "Notifications"),
91
+ notifications.length > 0 && (
92
+ // eslint-disable-next-line
93
+ // @ts-ignore
94
+ React.createElement(DismissAllButton, { kind: "ghost", size: "sm", onClick: dismissAllSidebar }, "Dismiss all"))),
95
+ notifications.length > 0 ? (notifications.sort(sortNotificationsDescending).map((notification) => (React.createElement(C3NotificationContainer, { key: notification.uuid, onRead: () => {
96
+ if (notification.state === "new") {
97
+ markAsRead(notification);
98
+ }
99
+ }, onDismiss: () => dismiss(notification), onLinkClick: () => {
100
+ if (enabled && analytics) {
101
+ analytics("notification-clicked-cta", notification.meta?.identifier);
102
+ }
103
+ if (onLinkClick) {
104
+ onLinkClick(notification.meta);
105
+ }
106
+ }, unread: unreadNotifications.some((id) => id === notification.uuid), ...notification })))) : (React.createElement(EmptyState, null,
107
+ React.createElement(C3BellIcon, { size: 56 }),
108
+ React.createElement(EmptyStateTitle, null, "No notifications"),
109
+ React.createElement(EmptyStateDescription, null, "New updates regarding your processes, clusters and more will appear here.")))));
110
+ };
@@ -0,0 +1,6 @@
1
+ import { FC } from "react";
2
+ import { C3NavigationOrgSideBarProps } from "./c3-navigation-sidebar.types";
3
+ declare const C3OrgSidebar: FC<{
4
+ sideBar: C3NavigationOrgSideBarProps;
5
+ }>;
6
+ export default C3OrgSidebar;