@camunda/camunda-composite-components 0.0.31-rc1 → 0.0.31
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.
- package/lib/esm/api/api.d.ts +22 -0
- package/lib/esm/api/api.js +66 -0
- package/lib/esm/api/endpoints.const.d.ts +9 -0
- package/lib/esm/api/endpoints.const.js +18 -0
- package/lib/esm/api/jwt.utils.d.ts +5 -0
- package/lib/esm/api/jwt.utils.js +23 -0
- package/lib/esm/api/notifications.d.ts +39 -0
- package/lib/esm/api/notifications.js +153 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-info-sidebar.d.ts +6 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-info-sidebar.js +40 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar-element.d.ts +11 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar-element.js +16 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar.d.ts +9 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar.js +38 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar.types.d.ts +60 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar.types.js +1 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-notification-sidebar.d.ts +5 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-notification-sidebar.js +110 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-org-sidebar.d.ts +6 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-org-sidebar.js +52 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-user-sidebar.d.ts +6 -0
- package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-user-sidebar.js +45 -0
- package/lib/esm/components/c3-navigation/c3-navigation.d.ts +1 -1
- package/lib/esm/components/c3-navigation/c3-navigation.js +16 -224
- package/lib/esm/components/c3-navigation/c3-navigation.types.d.ts +15 -40
- package/lib/esm/components/c3-navigation/c3-notification-provider/c3-notification-container.d.ts +12 -0
- package/lib/esm/components/c3-navigation/c3-notification-provider/c3-notification-container.js +106 -0
- package/lib/esm/components/c3-navigation/c3-notification-provider/c3-notification-provider.d.ts +30 -0
- package/lib/esm/components/c3-navigation/c3-notification-provider/c3-notification-provider.js +91 -0
- package/lib/esm/components/c3-navigation/helpers.d.ts +4 -0
- package/lib/esm/components/c3-navigation/helpers.js +62 -0
- package/lib/esm/components/c3-navigation/index.d.ts +3 -0
- package/lib/esm/components/c3-navigation/index.js +3 -0
- package/lib/esm/components/c3-navigation/story-helpers.d.ts +25 -0
- package/lib/esm/components/c3-navigation/story-helpers.js +180 -0
- package/lib/esm/components/c3-user-configuration/c3-user-configuration-provider.d.ts +13 -0
- package/lib/esm/components/c3-user-configuration/c3-user-configuration-provider.js +4 -0
- package/lib/esm/icons/c3-icons.d.ts +2 -0
- package/lib/esm/icons/c3-icons.js +6 -1
- package/lib/esm/index.d.ts +5 -2
- package/lib/esm/index.js +2 -1
- 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,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,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;
|
package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar-element.d.ts
ADDED
|
@@ -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;
|
package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar-element.js
ADDED
|
@@ -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;
|
package/lib/esm/components/c3-navigation/c3-navigation-sidebar/c3-navigation-sidebar.types.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { C3NavigationElementProps } from "../c3-navigation.types";
|
|
3
|
+
import { Notification } from "../../../api/notifications";
|
|
4
|
+
export declare type C3NavigationSideBarBaseProps = {
|
|
5
|
+
ariaLabel?: string;
|
|
6
|
+
isOpen?: boolean;
|
|
7
|
+
closeOnClick?: boolean;
|
|
8
|
+
elementClicked?: (key: string) => void;
|
|
9
|
+
elements?: C3NavigationElementProps[];
|
|
10
|
+
bottomElements?: C3NavigationElementProps[];
|
|
11
|
+
};
|
|
12
|
+
export declare type C3NavigationOrgSideBarProps = C3NavigationSideBarBaseProps & {
|
|
13
|
+
type: "org";
|
|
14
|
+
customElements?: {
|
|
15
|
+
activeOrganization?: {
|
|
16
|
+
activeLabel: string;
|
|
17
|
+
otherLabel: string;
|
|
18
|
+
orgName: string;
|
|
19
|
+
action: {
|
|
20
|
+
label: string;
|
|
21
|
+
onClick: () => void;
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export declare type C3NavigationInfoSideBarProps = C3NavigationSideBarBaseProps & {
|
|
27
|
+
type: "info";
|
|
28
|
+
version?: string;
|
|
29
|
+
};
|
|
30
|
+
export declare type C3NavigationUserSideBarProps = C3NavigationSideBarBaseProps & {
|
|
31
|
+
type: "user";
|
|
32
|
+
customElements?: {
|
|
33
|
+
customSection?: JSX.Element;
|
|
34
|
+
profile?: {
|
|
35
|
+
label: string;
|
|
36
|
+
user: {
|
|
37
|
+
name: string;
|
|
38
|
+
email: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
themeSelector?: {
|
|
42
|
+
currentTheme: string;
|
|
43
|
+
onChange: (newValue: string) => void;
|
|
44
|
+
};
|
|
45
|
+
stageToggle?: {
|
|
46
|
+
prodFeaturesEnabled: boolean;
|
|
47
|
+
toggle: () => void;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
export declare type C3NavigationNotificationsSideBarProps = C3NavigationSideBarBaseProps & {
|
|
52
|
+
type: "notifications";
|
|
53
|
+
onLinkClick?: (meta: Notification["meta"]) => void;
|
|
54
|
+
};
|
|
55
|
+
export declare type C3NavigationAppBarProps = C3NavigationSideBarBaseProps & {
|
|
56
|
+
type: "app";
|
|
57
|
+
};
|
|
58
|
+
export declare type C3NavigationSideBarProps = {
|
|
59
|
+
setIsOpen: (isOpen: boolean) => void;
|
|
60
|
+
} & (C3NavigationOrgSideBarProps | C3NavigationInfoSideBarProps | C3NavigationUserSideBarProps | C3NavigationNotificationsSideBarProps | C3NavigationAppBarProps);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
};
|