@stack-spot/portal-layout 0.0.3 → 0.0.5
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/dist/Layout.d.ts +5 -2
- package/dist/Layout.d.ts.map +1 -1
- package/dist/Layout.js +7 -5
- package/dist/Layout.js.map +1 -1
- package/dist/LayoutOverlayManager.d.ts.map +1 -1
- package/dist/LayoutOverlayManager.js +10 -7
- package/dist/LayoutOverlayManager.js.map +1 -1
- package/dist/components/Dialog.d.ts.map +1 -1
- package/dist/components/Dialog.js +4 -1
- package/dist/components/Dialog.js.map +1 -1
- package/dist/components/Header.d.ts +2 -1
- package/dist/components/Header.d.ts.map +1 -1
- package/dist/components/Header.js +2 -2
- package/dist/components/Header.js.map +1 -1
- package/dist/components/Menu/MenuContent.d.ts.map +1 -1
- package/dist/components/Menu/MenuContent.js +12 -5
- package/dist/components/Menu/MenuContent.js.map +1 -1
- package/dist/components/Menu/MenuSections.d.ts +1 -0
- package/dist/components/Menu/MenuSections.d.ts.map +1 -1
- package/dist/components/Menu/MenuSections.js +12 -5
- package/dist/components/Menu/MenuSections.js.map +1 -1
- package/dist/components/Menu/PageSelector.d.ts.map +1 -1
- package/dist/components/Menu/PageSelector.js +1 -1
- package/dist/components/Menu/PageSelector.js.map +1 -1
- package/dist/components/Menu/constants.d.ts.map +1 -1
- package/dist/components/Menu/constants.js.map +1 -1
- package/dist/components/Menu/types.d.ts +7 -0
- package/dist/components/Menu/types.d.ts.map +1 -1
- package/dist/components/Menu/types.js.map +1 -1
- package/dist/components/OverlayContent.d.ts.map +1 -1
- package/dist/components/OverlayContent.js +5 -1
- package/dist/components/OverlayContent.js.map +1 -1
- package/dist/components/SelectionList.d.ts +2 -1
- package/dist/components/SelectionList.d.ts.map +1 -1
- package/dist/components/SelectionList.js +3 -3
- package/dist/components/SelectionList.js.map +1 -1
- package/dist/components/Toaster.d.ts.map +1 -1
- package/dist/components/Toaster.js +5 -1
- package/dist/components/Toaster.js.map +1 -1
- package/dist/components/error/ErrorBoundary.d.ts +21 -0
- package/dist/components/error/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/error/ErrorBoundary.js +23 -0
- package/dist/components/error/ErrorBoundary.js.map +1 -0
- package/dist/components/error/ErrorDescriptor.d.ts +12 -0
- package/dist/components/error/ErrorDescriptor.d.ts.map +1 -0
- package/dist/components/error/ErrorDescriptor.js +17 -0
- package/dist/components/error/ErrorDescriptor.js.map +1 -0
- package/dist/components/error/ErrorFeedback.d.ts +3 -0
- package/dist/components/error/ErrorFeedback.d.ts.map +1 -0
- package/dist/components/error/ErrorFeedback.js +66 -0
- package/dist/components/error/ErrorFeedback.js.map +1 -0
- package/dist/components/error/SilentErrorBoundary.d.ts +17 -0
- package/dist/components/error/SilentErrorBoundary.d.ts.map +1 -0
- package/dist/components/error/SilentErrorBoundary.js +20 -0
- package/dist/components/error/SilentErrorBoundary.js.map +1 -0
- package/dist/dictionary.d.ts +15 -0
- package/dist/dictionary.d.ts.map +1 -0
- package/dist/dictionary.js +23 -0
- package/dist/dictionary.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/layout.css +22 -8
- package/dist/svg/Forbidden.d.ts +6 -0
- package/dist/svg/Forbidden.d.ts.map +1 -0
- package/dist/svg/Forbidden.js +4 -0
- package/dist/svg/Forbidden.js.map +1 -0
- package/dist/svg/Logo.d.ts +2 -0
- package/dist/svg/Logo.d.ts.map +1 -0
- package/dist/svg/Logo.js +4 -0
- package/dist/svg/Logo.js.map +1 -0
- package/dist/svg/NotFound.d.ts +6 -0
- package/dist/svg/NotFound.d.ts.map +1 -0
- package/dist/svg/NotFound.js +4 -0
- package/dist/svg/NotFound.js.map +1 -0
- package/dist/svg/ServerError.d.ts +6 -0
- package/dist/svg/ServerError.d.ts.map +1 -0
- package/dist/svg/ServerError.js +4 -0
- package/dist/svg/ServerError.js.map +1 -0
- package/dist/svg/Unauthenticated.d.ts +6 -0
- package/dist/svg/Unauthenticated.d.ts.map +1 -0
- package/dist/svg/Unauthenticated.js +4 -0
- package/dist/svg/Unauthenticated.js.map +1 -0
- package/package.json +4 -3
- package/src/Layout.tsx +22 -14
- package/src/LayoutOverlayManager.tsx +15 -10
- package/src/components/Dialog.tsx +4 -1
- package/src/components/Header.tsx +4 -3
- package/src/components/OverlayContent.tsx +16 -12
- package/src/components/SelectionList.tsx +5 -3
- package/src/components/Toaster.tsx +9 -5
- package/src/components/error/ErrorBoundary.tsx +33 -0
- package/src/components/error/ErrorDescriptor.ts +21 -0
- package/src/components/error/ErrorFeedback.tsx +114 -0
- package/src/components/error/SilentErrorBoundary.tsx +31 -0
- package/src/components/{Menu → menu}/MenuContent.tsx +12 -4
- package/src/components/{Menu → menu}/MenuSections.tsx +14 -6
- package/src/components/{Menu → menu}/PageSelector.tsx +1 -0
- package/src/components/{Menu → menu}/types.ts +7 -0
- package/src/dictionary.ts +25 -0
- package/src/index.ts +5 -4
- package/src/layout.css +22 -8
- package/src/svg/Forbidden.tsx +22 -0
- package/src/{components → svg}/Logo.tsx +1 -1
- package/src/svg/NotFound.tsx +16 -0
- package/src/svg/ServerError.tsx +33 -0
- package/src/svg/Unauthenticated.tsx +16 -0
- package/src/citric.fix.d.ts +0 -7
- /package/src/components/{Menu → menu}/constants.ts +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ServerError.js","sourceRoot":"","sources":["../../src/svg/ServerError.tsx"],"names":[],"mappings":";AAAA,4BAA4B;AAE5B,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAAE,SAAS,EAAE,KAAK,EAAuD,EAAE,EAAE,CAAC,CACxG,eAAK,KAAK,EAAC,KAAK,EAAC,MAAM,EAAC,KAAK,EAAC,OAAO,EAAC,aAAa,EAAC,IAAI,EAAC,MAAM,EAAC,KAAK,EAAC,4BAA4B,EAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,aACnI,eAAM,OAAO,EAAC,KAAK,EAAC,CAAC,EAAC,8HAA8H,EAAC,MAAM,EAAC,iCAAiC,EAAC,WAAW,EAAC,QAAQ,GAAG,EACrN,aAAG,OAAO,EAAC,KAAK,aACd,eAAM,EAAE,EAAC,2BAA2B,EAAC,IAAI,EAAC,OAAO,YAC/C,eAAM,QAAQ,EAAC,SAAS,EAAC,QAAQ,EAAC,SAAS,EAAC,CAAC,EAAC,wwBAAwwB,GAAG,GACpzB,EACP,eAAM,CAAC,EAAC,0jFAA0jF,EAAC,IAAI,EAAC,iCAAiC,EAAC,IAAI,EAAC,iCAAiC,GAAG,IACjpF,EACJ,eAAM,OAAO,EAAC,KAAK,EAAC,CAAC,EAAC,wDAAwD,EAAC,MAAM,EAAC,iCAAiC,EAAC,WAAW,EAAC,SAAS,GAAG,EAChJ,eAAM,OAAO,EAAC,MAAM,EAAC,CAAC,EAAC,yXAAyX,EAAC,IAAI,EAAC,SAAS,GAAG,EACla,eAAM,OAAO,EAAC,MAAM,EAAC,CAAC,EAAC,mYAAmY,EAAC,IAAI,EAAC,SAAS,GAAG,EAC5a,eAAM,OAAO,EAAC,MAAM,EAAC,CAAC,EAAC,6XAA6X,EAAC,IAAI,EAAC,SAAS,GAAG,EACta,eAAM,CAAC,EAAC,kCAAkC,EAAC,MAAM,EAAC,SAAS,EAAC,WAAW,EAAC,QAAQ,EAAC,aAAa,EAAC,OAAO,GAAG,EACzG,eAAM,CAAC,EAAC,kCAAkC,EAAC,MAAM,EAAC,SAAS,EAAC,WAAW,EAAC,QAAQ,EAAC,aAAa,EAAC,OAAO,GAAG,EACzG,2BACE,0BAAgB,EAAE,EAAC,2BAA2B,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,SAAS,EAAC,aAAa,EAAC,gBAAgB,aAC/H,eAAM,SAAS,EAAC,SAAS,EAAC,WAAW,EAAC,GAAG,GAAG,EAC5C,eAAM,MAAM,EAAC,GAAG,EAAC,SAAS,EAAC,SAAS,GAAG,IACxB,EACjB,0BAAgB,EAAE,EAAC,2BAA2B,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,UAAU,EAAC,EAAE,EAAC,UAAU,EAAC,aAAa,EAAC,gBAAgB,aACjI,eAAM,SAAS,EAAC,SAAS,GAAG,EAC5B,eAAM,MAAM,EAAC,UAAU,EAAC,SAAS,EAAC,SAAS,EAAC,WAAW,EAAC,MAAM,GAAG,IAClD,EACjB,0BAAgB,EAAE,EAAC,2BAA2B,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,UAAU,EAAC,aAAa,EAAC,gBAAgB,aAChI,eAAM,SAAS,EAAC,SAAS,GAAG,EAC5B,eAAM,MAAM,EAAC,UAAU,EAAC,SAAS,EAAC,SAAS,EAAC,WAAW,EAAC,MAAM,GAAG,IAClD,IACZ,IACH,CACP,CAAA"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
export declare const Unauthenticated: ({ className, style }: {
|
|
3
|
+
className?: string | undefined;
|
|
4
|
+
style?: import("react").CSSProperties | undefined;
|
|
5
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
//# sourceMappingURL=Unauthenticated.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Unauthenticated.d.ts","sourceRoot":"","sources":["../../src/svg/Unauthenticated.tsx"],"names":[],"mappings":";AAEA,eAAO,MAAM,eAAe;;;6CAa3B,CAAA"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/* eslint-disable max-len */
|
|
3
|
+
export const Unauthenticated = ({ className, style }) => (_jsxs("svg", { width: "178", height: "160", viewBox: "0 0 178 160", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: className, style: style, children: [_jsx("circle", { opacity: "0.16", cx: "89.6761", cy: "80.2113", r: "31.5629", fill: "#FAFAFB" }), _jsx("ellipse", { opacity: "0.16", cx: "89.6721", cy: "80.6057", rx: "22.094", ry: "21.6995", fill: "#FAFAFB" }), _jsx("ellipse", { opacity: "0.16", cx: "89.6759", cy: "80.6056", rx: "13.4142", ry: "13.0197", fill: "#FAFAFB" }), _jsx("path", { opacity: "0.5", fillRule: "evenodd", clipRule: "evenodd", d: "M44.7029 19.055C69.3231 -5.56521 109.24 -5.56521 133.861 19.055L177.651 62.8454C178.086 63.2808 178.086 63.9868 177.651 64.4222C177.215 64.8577 176.509 64.8577 176.074 64.4222L132.284 20.6319C108.534 -3.11747 70.0291 -3.11747 46.2798 20.6319C45.8443 21.0673 45.1383 21.0673 44.7029 20.6319C44.2675 20.1964 44.2675 19.4904 44.7029 19.055ZM161.243 79.8912L126.447 45.0949C105.747 24.3952 72.1863 24.3952 51.4866 45.0949L16.6903 79.8912L51.4866 114.687C72.1863 135.387 105.747 135.387 126.447 114.687L161.243 79.8912ZM128.024 43.518C106.453 21.9474 71.4803 21.9474 49.9097 43.5181L15.1135 78.3143L13.5366 79.8912L15.1135 81.468L49.9097 116.264C71.4803 137.835 106.453 137.835 128.024 116.264L162.82 81.468L164.397 79.8912L162.82 78.3143L128.024 43.518ZM44.7029 141.115C69.3231 165.736 109.24 165.736 133.861 141.115C134.296 140.68 134.296 139.974 133.861 139.538C133.425 139.103 132.719 139.103 132.284 139.538C108.534 163.288 70.0291 163.288 46.2798 139.538L2.48938 95.7481C2.05394 95.3126 1.34795 95.3126 0.912516 95.7481C0.477078 96.1835 0.477077 96.8895 0.912518 97.3249L44.7029 141.115Z", fill: "url(#paint0_linear_3383_243082)" }), _jsx("defs", { children: _jsxs("linearGradient", { id: "paint0_linear_3383_243082", x1: "-3.35603", y1: "94.0049", x2: "89.5555", y2: "212.99", gradientUnits: "userSpaceOnUse", children: [_jsx("stop", { stopColor: "#BCBCCF" }), _jsx("stop", { offset: "0.766098", stopColor: "#353546", stopOpacity: "0.09" })] }) })] }));
|
|
4
|
+
//# sourceMappingURL=Unauthenticated.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Unauthenticated.js","sourceRoot":"","sources":["../../src/svg/Unauthenticated.tsx"],"names":[],"mappings":";AAAA,4BAA4B;AAE5B,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,EAAE,SAAS,EAAE,KAAK,EAAuD,EAAE,EAAE,CAAC,CAC5G,eAAK,KAAK,EAAC,KAAK,EAAC,MAAM,EAAC,KAAK,EAAC,OAAO,EAAC,aAAa,EAAC,IAAI,EAAC,MAAM,EAAC,KAAK,EAAC,4BAA4B,EAAC,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,aACnI,iBAAQ,OAAO,EAAC,MAAM,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,SAAS,EAAC,CAAC,EAAC,SAAS,EAAC,IAAI,EAAC,SAAS,GAAE,EAC7E,kBAAS,OAAO,EAAC,MAAM,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,QAAQ,EAAC,EAAE,EAAC,SAAS,EAAC,IAAI,EAAC,SAAS,GAAE,EAC3F,kBAAS,OAAO,EAAC,MAAM,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,SAAS,EAAC,IAAI,EAAC,SAAS,GAAE,EAC5F,eAAM,OAAO,EAAC,KAAK,EAAC,QAAQ,EAAC,SAAS,EAAC,QAAQ,EAAC,SAAS,EAAC,CAAC,EAAC,kkCAAkkC,EAAC,IAAI,EAAC,iCAAiC,GAAE,EACvqC,yBACE,0BAAgB,EAAE,EAAC,2BAA2B,EAAC,EAAE,EAAC,UAAU,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,SAAS,EAAC,EAAE,EAAC,QAAQ,EAAC,aAAa,EAAC,gBAAgB,aAC/H,eAAM,SAAS,EAAC,SAAS,GAAE,EAC3B,eAAM,MAAM,EAAC,UAAU,EAAC,SAAS,EAAC,SAAS,EAAC,WAAW,EAAC,MAAM,GAAE,IACjD,GACZ,IACH,CACP,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stack-spot/portal-layout",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
"@citric/core": "^5.3.1",
|
|
9
9
|
"@citric/icons": "^5.3.1",
|
|
10
10
|
"@citric/ui": "^5.3.1",
|
|
11
|
-
"@stack-spot/portal-theme": "0.0.3",
|
|
11
|
+
"@stack-spot/portal-theme": "^0.0.3",
|
|
12
|
+
"@stack-spot/portal-translate": "^0.0.4",
|
|
12
13
|
"react": "^18.2.0",
|
|
13
14
|
"react-dom": "^18.2.0",
|
|
14
|
-
"styled-components": "6.1.1"
|
|
15
|
+
"styled-components": "^6.1.1"
|
|
15
16
|
},
|
|
16
17
|
"devDependencies": {
|
|
17
18
|
"@types/react": "^18.2.37",
|
package/src/Layout.tsx
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { CSSToCitricAdapter, listToClass, WithStyle } from '@stack-spot/portal-theme'
|
|
2
2
|
import '@stack-spot/portal-theme/dist/theme.css'
|
|
3
3
|
import { ReactElement, ReactNode } from 'react'
|
|
4
|
+
import { ErrorBoundary } from './components/error/ErrorBoundary'
|
|
5
|
+
import { DescriptionFn } from './components/error/ErrorDescriptor'
|
|
6
|
+
import { SilentErrorBoundary } from './components/error/SilentErrorBoundary'
|
|
4
7
|
import { Header, HeaderProps } from './components/Header'
|
|
5
|
-
import { MenuContent } from './components/
|
|
6
|
-
import { MenuSections } from './components/
|
|
7
|
-
import { MenuProps } from './components/
|
|
8
|
+
import { MenuContent } from './components/menu/MenuContent'
|
|
9
|
+
import { MenuSections } from './components/menu/MenuSections'
|
|
10
|
+
import { MenuProps } from './components/menu/types'
|
|
8
11
|
import { Toaster } from './components/Toaster'
|
|
9
12
|
import './layout.css'
|
|
10
13
|
import { overlay } from './LayoutOverlayManager'
|
|
@@ -13,6 +16,7 @@ interface Props extends WithStyle {
|
|
|
13
16
|
menu: MenuProps,
|
|
14
17
|
header: HeaderProps,
|
|
15
18
|
children: ReactNode,
|
|
19
|
+
errorDescriptor?: DescriptionFn,
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
interface RawProps extends WithStyle {
|
|
@@ -21,9 +25,12 @@ interface RawProps extends WithStyle {
|
|
|
21
25
|
header: ReactElement,
|
|
22
26
|
children: ReactNode,
|
|
23
27
|
compactMenu?: boolean,
|
|
28
|
+
errorDescriptor?: DescriptionFn,
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
export const RawLayout = (
|
|
31
|
+
export const RawLayout = (
|
|
32
|
+
{ menuSections, menuContent, header, compactMenu = true, children, className, style }: RawProps,
|
|
33
|
+
) => {
|
|
27
34
|
// @ts-ignore
|
|
28
35
|
const { bottomDialog, modal, rightPanel } = overlay.useOverlays()
|
|
29
36
|
const classes = [
|
|
@@ -35,18 +42,18 @@ export const RawLayout = ({ menuSections, menuContent, header, compactMenu = tru
|
|
|
35
42
|
return (
|
|
36
43
|
<CSSToCitricAdapter>
|
|
37
44
|
<div id="layout" className={listToClass(classes)} style={style}>
|
|
38
|
-
<header id="header">{header}</header>
|
|
39
|
-
<aside id="menu">
|
|
40
|
-
<nav id="menuContent">{menuContent}</nav>
|
|
41
|
-
<nav id="menuSections">{menuSections}</nav>
|
|
42
|
-
</aside>
|
|
43
45
|
<div id="page">
|
|
44
|
-
<article id="content">{children}</article>
|
|
46
|
+
<article id="content"><ErrorBoundary>{children}</ErrorBoundary></article>
|
|
45
47
|
</div>
|
|
46
|
-
<
|
|
47
|
-
<
|
|
48
|
+
<header id="header"><SilentErrorBoundary>{header}</SilentErrorBoundary></header>
|
|
49
|
+
<aside id="menu">
|
|
50
|
+
<nav id="menuContent"><SilentErrorBoundary>{menuContent}</SilentErrorBoundary></nav>
|
|
51
|
+
<nav id="menuSections"><SilentErrorBoundary>{menuSections}</SilentErrorBoundary></nav>
|
|
52
|
+
</aside>
|
|
53
|
+
<div id="rightPanel"><ErrorBoundary>{rightPanel}</ErrorBoundary></div>
|
|
54
|
+
<div id="bottomDialog"><ErrorBoundary>{bottomDialog}</ErrorBoundary></div>
|
|
48
55
|
<div id="backdrop">
|
|
49
|
-
<div id="modal">{modal}</div>
|
|
56
|
+
<div id="modal"><ErrorBoundary>{modal}</ErrorBoundary></div>
|
|
50
57
|
</div>
|
|
51
58
|
<Toaster />
|
|
52
59
|
</div>
|
|
@@ -59,7 +66,7 @@ const MenuContentRenderer = ({ content }: Required<Pick<Props['menu'], 'content'
|
|
|
59
66
|
return <MenuContent {...menuContent} />
|
|
60
67
|
}
|
|
61
68
|
|
|
62
|
-
export const Layout = ({ menu, header, children, className, style }: Props) => (
|
|
69
|
+
export const Layout = ({ menu, header, children, errorDescriptor, className, style }: Props) => (
|
|
63
70
|
<RawLayout
|
|
64
71
|
header={<Header {...header} />}
|
|
65
72
|
menuSections={<MenuSections {...menu} />}
|
|
@@ -68,6 +75,7 @@ export const Layout = ({ menu, header, children, className, style }: Props) => (
|
|
|
68
75
|
: undefined
|
|
69
76
|
}
|
|
70
77
|
compactMenu={menu.compact}
|
|
78
|
+
errorDescriptor={errorDescriptor}
|
|
71
79
|
className={className}
|
|
72
80
|
style={style}
|
|
73
81
|
>
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/* eslint-disable react-hooks/rules-of-hooks */
|
|
2
2
|
|
|
3
|
-
import { Button
|
|
3
|
+
import { Button } from '@citric/core'
|
|
4
4
|
import { ReactElement, useLayoutEffect, useState } from 'react'
|
|
5
5
|
import { Dialog, DialogOptions } from './components/Dialog'
|
|
6
6
|
import { OverlayContent, OverlayContentProps } from './components/OverlayContent'
|
|
7
|
+
import { getDictionary } from './dictionary'
|
|
7
8
|
import { ElementNotFound, LayoutError } from './errors'
|
|
8
9
|
import { showToaster as showReactToaster } from './toaster'
|
|
9
10
|
import { valueOfLayoutVar } from './utils'
|
|
@@ -115,24 +116,28 @@ class LayoutOverlayManager {
|
|
|
115
116
|
})
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
confirm({ confirm
|
|
119
|
-
|
|
119
|
+
confirm({ confirm, cancel, ...options }: DialogOptions): Promise<boolean> {
|
|
120
|
+
const t = getDictionary()
|
|
121
|
+
return this.showDialog({ ...options, confirm: confirm || t.confirm, cancel: cancel || t.cancel })
|
|
120
122
|
}
|
|
121
123
|
|
|
122
|
-
async alert({ confirm
|
|
123
|
-
|
|
124
|
+
async alert({ confirm, showButton = true, ...options }: AlertOptions): Promise<void> {
|
|
125
|
+
const t = getDictionary()
|
|
126
|
+
await this.showDialog({ ...options, confirm: showButton ? (confirm || t.confirm) : undefined })
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
showBottomDialog({ message, cancel, confirm }: BottomDialogOptions): Promise<boolean> {
|
|
127
130
|
if (!this.elements?.bottomDialog) throw new ElementNotFound('bottom dialog', BOTTOM_DIALOG_ID)
|
|
128
131
|
if (!this.setContent.bottomDialog) throw new LayoutError('unable to show bottom dialog, because it has not been setup yet.')
|
|
129
132
|
return new Promise((resolve) => {
|
|
130
|
-
this.setContent.bottomDialog
|
|
131
|
-
|
|
133
|
+
this.setContent.bottomDialog?.(
|
|
134
|
+
<>
|
|
132
135
|
{message}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
+
<div className="btn-group">
|
|
137
|
+
{cancel && <Button onClick={() => resolve(false)} colorScheme="light" appearance="outlined">{cancel}</Button>}
|
|
138
|
+
{confirm && <Button onClick={() => resolve(true)} colorScheme="light">{confirm}</Button>}
|
|
139
|
+
</div>
|
|
140
|
+
</>,
|
|
136
141
|
)
|
|
137
142
|
this.elements?.bottomDialog?.setAttribute('class', 'visible')
|
|
138
143
|
})
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Button, Flex, Input, Text } from '@citric/core'
|
|
2
|
+
import { interpolate } from '@stack-spot/portal-translate'
|
|
2
3
|
import { ReactNode, useState } from 'react'
|
|
4
|
+
import { useDictionary } from '../dictionary'
|
|
3
5
|
import { OverlayContent } from './OverlayContent'
|
|
4
6
|
|
|
5
7
|
interface Validation {
|
|
@@ -49,6 +51,7 @@ export const Dialog = ({
|
|
|
49
51
|
buttonPlacement = type === 'panel' ? 'left' : 'right',
|
|
50
52
|
}: Props,
|
|
51
53
|
) => {
|
|
54
|
+
const t = useDictionary()
|
|
52
55
|
const [enabled, setEnabled] = useState(!validation)
|
|
53
56
|
|
|
54
57
|
function renderValidation() {
|
|
@@ -56,7 +59,7 @@ export const Dialog = ({
|
|
|
56
59
|
const value = typeof validation === 'string' ? validation : validation.value
|
|
57
60
|
const label = typeof validation === 'object' && validation.label
|
|
58
61
|
? validation.label
|
|
59
|
-
:
|
|
62
|
+
: interpolate(t.validationLabel, value)
|
|
60
63
|
const placeholder = typeof validation === 'object' ? validation.placeholder : undefined
|
|
61
64
|
return (
|
|
62
65
|
<div style={{ margin: '16px 0' }}>
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { Flex } from '@citric/core'
|
|
2
2
|
import { ReactNode } from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import { Logo } from '../svg/Logo'
|
|
4
4
|
import { SelectionListProps } from './SelectionList'
|
|
5
5
|
import { UserMenu } from './UserMenu'
|
|
6
6
|
|
|
7
7
|
export interface HeaderProps {
|
|
8
8
|
logo?: ReactNode,
|
|
9
|
+
logoHref?: string,
|
|
9
10
|
userName?: string,
|
|
10
11
|
email?: string,
|
|
11
12
|
options?: SelectionListProps['items'],
|
|
@@ -13,9 +14,9 @@ export interface HeaderProps {
|
|
|
13
14
|
right?: ReactNode,
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
export const Header = ({ logo, center, right, userName, email, options }: HeaderProps) => (
|
|
17
|
+
export const Header = ({ logo, logoHref, center, right, userName, email, options }: HeaderProps) => (
|
|
17
18
|
<>
|
|
18
|
-
{logo ?? <
|
|
19
|
+
<a href={logoHref} title="Home">{logo ?? <Logo style={{ width: 130 }} />}</a>
|
|
19
20
|
<Flex flex={1}>{center}</Flex>
|
|
20
21
|
{right}
|
|
21
22
|
{userName && <UserMenu userName={userName} email={email} options={options} />}
|
|
@@ -4,6 +4,7 @@ import { IconButton } from '@citric/ui'
|
|
|
4
4
|
import { WithStyle, listToClass, theme } from '@stack-spot/portal-theme'
|
|
5
5
|
import { ReactNode } from 'react'
|
|
6
6
|
import { styled } from 'styled-components'
|
|
7
|
+
import { useDictionary } from '../dictionary'
|
|
7
8
|
|
|
8
9
|
export interface OverlayContentProps extends WithStyle {
|
|
9
10
|
title: string,
|
|
@@ -38,15 +39,18 @@ const ContentBox = styled.section`
|
|
|
38
39
|
}
|
|
39
40
|
`
|
|
40
41
|
|
|
41
|
-
export const OverlayContent = ({ children, title, subtitle, className, style, onClose, type }: Props) =>
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
export const OverlayContent = ({ children, title, subtitle, className, style, onClose, type }: Props) => {
|
|
43
|
+
const t = useDictionary()
|
|
44
|
+
return (
|
|
45
|
+
<ContentBox style={style} className={listToClass([className, type])}>
|
|
46
|
+
<header>
|
|
47
|
+
<Flex flexDirection="column" flex={1}>
|
|
48
|
+
<Text appearance={type === 'modal' ? 'h3' : 'h4'}>{title}</Text>
|
|
49
|
+
{subtitle && <Text appearance="body2" colorScheme="light.700">{subtitle}</Text>}
|
|
50
|
+
</Flex>
|
|
51
|
+
<IconButton onClick={onClose} title={t.close} aria-label={t.close}><TimesMini /></IconButton>
|
|
52
|
+
</header>
|
|
53
|
+
{children}
|
|
54
|
+
</ContentBox>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
@@ -47,11 +47,12 @@ export interface SelectionListProps extends WithStyle {
|
|
|
47
47
|
maxHeight?: string,
|
|
48
48
|
before?: ReactElement,
|
|
49
49
|
after?: ReactElement,
|
|
50
|
+
scroll?: boolean,
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
const SelectionBox = styled.div<{ $maxHeight: string }>`
|
|
53
|
+
const SelectionBox = styled.div<{ $maxHeight: string, $scroll?: boolean }>`
|
|
53
54
|
max-height: 0;
|
|
54
|
-
overflow-y: auto;
|
|
55
|
+
overflow-y: ${({ $scroll }) => $scroll ? 'auto' : 'hidden'};
|
|
55
56
|
overflow-x: hidden;
|
|
56
57
|
transition: max-height ease-in ${ANIMATION_DURATION_MS / 1000}s;
|
|
57
58
|
z-index: 1;
|
|
@@ -145,7 +146,7 @@ function renderItem(item: ListItem, setCurrent: (current: CurrentItemList) => vo
|
|
|
145
146
|
}
|
|
146
147
|
|
|
147
148
|
export const SelectionList = ({
|
|
148
|
-
items, className, style, visible = true, maxHeight = '300px', onHide, before, after,
|
|
149
|
+
items, className, style, visible = true, maxHeight = '300px', onHide, before, after, scroll,
|
|
149
150
|
}: SelectionListProps) => {
|
|
150
151
|
const wrapper = useRef<HTMLDivElement>(null)
|
|
151
152
|
const itemsRef = useRef(items)
|
|
@@ -182,6 +183,7 @@ export const SelectionList = ({
|
|
|
182
183
|
$maxHeight={maxHeight}
|
|
183
184
|
style={style}
|
|
184
185
|
className={listToClass(['selection-list', visible ? 'visible' : undefined, className])}
|
|
186
|
+
$scroll={scroll}
|
|
185
187
|
>
|
|
186
188
|
<div className="selection-list-content">
|
|
187
189
|
{before}
|
|
@@ -2,11 +2,15 @@ import { TimesMini } from '@citric/icons'
|
|
|
2
2
|
import { IconButton } from '@citric/ui'
|
|
3
3
|
import { CloseButtonProps, ToastContainer } from 'react-toastify'
|
|
4
4
|
import 'react-toastify/dist/ReactToastify.css'
|
|
5
|
+
import { useDictionary } from '../dictionary'
|
|
5
6
|
|
|
6
|
-
const CloseButton = ({ closeToast }: CloseButtonProps) =>
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const CloseButton = ({ closeToast }: CloseButtonProps) => {
|
|
8
|
+
const t = useDictionary()
|
|
9
|
+
return (
|
|
10
|
+
<IconButton onClick={() => closeToast(null as any)} title={t.dismiss}>
|
|
11
|
+
<TimesMini />
|
|
12
|
+
</IconButton>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
11
15
|
|
|
12
16
|
export const Toaster = () => <ToastContainer closeButton={CloseButton} />
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Component } from 'react'
|
|
2
|
+
import { ErrorDescription, ErrorDescriptor } from './ErrorDescriptor'
|
|
3
|
+
import { ErrorFeedback } from './ErrorFeedback'
|
|
4
|
+
|
|
5
|
+
interface State extends ErrorDescription {
|
|
6
|
+
hasError: boolean,
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
children: React.ReactNode,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class ErrorBoundary extends Component<Props, State> {
|
|
14
|
+
constructor(props: Props) {
|
|
15
|
+
super(props)
|
|
16
|
+
this.state = { hasError: false }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static getDerivedStateFromError(error: any) {
|
|
20
|
+
return { hasError: true, ...ErrorDescriptor.describe(error) }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
componentDidCatch(error: any, errorInfo: any) {
|
|
24
|
+
// eslint-disable-next-line no-console
|
|
25
|
+
console.error(error, errorInfo)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
render() {
|
|
29
|
+
return this.state.hasError
|
|
30
|
+
? <ErrorFeedback code={this.state.code} message={this.state.message} debug={this.state.debug} />
|
|
31
|
+
: this.props.children
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface ErrorDescription {
|
|
2
|
+
code?: number,
|
|
3
|
+
message?: string,
|
|
4
|
+
debug?: boolean,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export type DescriptionFn = (error: any) => ErrorDescription
|
|
8
|
+
|
|
9
|
+
export class ErrorDescriptor {
|
|
10
|
+
private static descriptionFunction: DescriptionFn = error => ({
|
|
11
|
+
message: error.message || `${error}`,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
static setDescriptionFunction(fn: DescriptionFn) {
|
|
15
|
+
this.descriptionFunction = fn
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static describe(error: any) {
|
|
19
|
+
return this.descriptionFunction(error)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Box, Button, Container, Flex, LinkBox, Text } from '@citric/core'
|
|
2
|
+
import { Dictionary, useTranslate } from '@stack-spot/portal-translate'
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { Forbidden } from '../../svg/Forbidden'
|
|
5
|
+
import { Logo } from '../../svg/Logo'
|
|
6
|
+
import { NotFound } from '../../svg/NotFound'
|
|
7
|
+
import { ServerError } from '../../svg/ServerError'
|
|
8
|
+
import { Unauthenticated } from '../../svg/Unauthenticated'
|
|
9
|
+
import { ErrorDescription } from './ErrorDescriptor'
|
|
10
|
+
|
|
11
|
+
const imageStyle: React.CSSProperties = {
|
|
12
|
+
width: '200px',
|
|
13
|
+
height: '200px',
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const imageMap: Record<number, React.ReactElement> = {
|
|
17
|
+
401: <Unauthenticated style={imageStyle} />,
|
|
18
|
+
403: <Forbidden style={imageStyle} />,
|
|
19
|
+
404: <NotFound style={imageStyle} />,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const ErrorFeedback = ({ code = 0, message, debug }: ErrorDescription) => {
|
|
23
|
+
const t = useTranslate(dictionary) as Record<string, string>
|
|
24
|
+
const [showDetails, setShowDetails] = useState(false)
|
|
25
|
+
|
|
26
|
+
return (
|
|
27
|
+
<Box bg="light.400">
|
|
28
|
+
<Container>
|
|
29
|
+
<Flex alignItems="center" sx={{ padding: 12 }}>
|
|
30
|
+
<Box width={5} sx={{ display: ['block', 'none'] }}>
|
|
31
|
+
<Flex justifyContent="flex-end" pr={20}>
|
|
32
|
+
{imageMap[code] ?? <ServerError style={imageStyle} />}
|
|
33
|
+
</Flex>
|
|
34
|
+
</Box>
|
|
35
|
+
<Box width={[7, 12]}>
|
|
36
|
+
<LinkBox href="/">
|
|
37
|
+
<Logo style={{ width: '130px', height: '30px' }} />
|
|
38
|
+
</LinkBox>
|
|
39
|
+
<Box w={[7, 12]}>
|
|
40
|
+
<Text appearance="h4" mt={5} colorScheme="inverse">
|
|
41
|
+
{code ? `${code}. ` : ''}
|
|
42
|
+
<Text appearance="h4" as="span" colorScheme="light.700">
|
|
43
|
+
{t[`${code}.title`]}
|
|
44
|
+
</Text>
|
|
45
|
+
</Text>
|
|
46
|
+
|
|
47
|
+
<Text appearance="body1" mt={5} colorScheme="inverse">
|
|
48
|
+
{t[`${code}.description`]}
|
|
49
|
+
</Text>
|
|
50
|
+
|
|
51
|
+
<Text appearance="body1" colorScheme="light.700" mt={1}>
|
|
52
|
+
{t[`${code}.help`]}
|
|
53
|
+
</Text>
|
|
54
|
+
{debug && message && (
|
|
55
|
+
<Button appearance="outlined" colorScheme="inverse" onClick={() => setShowDetails(v => !v)}>
|
|
56
|
+
{showDetails ? t.hideDetails : t.showDetails}
|
|
57
|
+
</Button>
|
|
58
|
+
)}
|
|
59
|
+
{showDetails && (
|
|
60
|
+
<Box bg="danger" mt={8} p={4} sx={{ borderRadius: '5px' }}>
|
|
61
|
+
<Text appearance="microtext1" colorScheme="danger.contrastText">{message}</Text>
|
|
62
|
+
</Box>
|
|
63
|
+
)}
|
|
64
|
+
</Box>
|
|
65
|
+
</Box>
|
|
66
|
+
</Flex>
|
|
67
|
+
</Container>
|
|
68
|
+
</Box>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const dictionary = {
|
|
73
|
+
en: {
|
|
74
|
+
altLogo: 'Logo Stackspot',
|
|
75
|
+
'0.title': 'Unknown client error',
|
|
76
|
+
'0.description': 'An unknown error happened while trying to load the resource.',
|
|
77
|
+
'0.help': 'Reload the page and, if it still doesn\'t work, report the error the Stackspot team.',
|
|
78
|
+
'401.title': 'Not authorized',
|
|
79
|
+
'401.description': 'There was a failure loading credentials for this page.',
|
|
80
|
+
'401.help': 'Check if the URL is correct or clear your cache and cookies from your browser and try again.',
|
|
81
|
+
'403.title': 'Private access',
|
|
82
|
+
'403.description': 'The page you have tried to visit is private.',
|
|
83
|
+
'403.help': 'Log in with another account or request access from the person who manages your organization.',
|
|
84
|
+
'404.title': 'Resource not found',
|
|
85
|
+
'404.description': 'This resource no longer exists.',
|
|
86
|
+
'404.help': 'Please try again or request a new URL from the person who manages your organization.',
|
|
87
|
+
'500.title': 'Server error',
|
|
88
|
+
'500.description':
|
|
89
|
+
"We have identified a problem with the server, but don't worry. We are already investigating what happened.",
|
|
90
|
+
'500.help': 'Please try again after a few minutes.',
|
|
91
|
+
showDetails: 'Show Details',
|
|
92
|
+
hideDetails: 'Hide Details',
|
|
93
|
+
},
|
|
94
|
+
pt: {
|
|
95
|
+
altLogo: 'Logo Stackspot',
|
|
96
|
+
'0.title': 'Erro desconhecido (cliente)',
|
|
97
|
+
'0.description': 'Um erro desconhecido aconteceu ao carregar o recurso',
|
|
98
|
+
'0.help': 'Recarregue a página e, se ainda não funcionar, reporte o problema para o time da Stackspot.',
|
|
99
|
+
'401.title': 'Não autorizado',
|
|
100
|
+
'401.description': 'Houve uma falha no carregamento de credenciais dessa página.',
|
|
101
|
+
'401.help': 'Verifique se a URL está correta ou limpe o cache e os cookies de seu navegador e tente novamente.',
|
|
102
|
+
'403.title': 'Acesso privado',
|
|
103
|
+
'403.description': '"A página que você tentou visualizar é particular."',
|
|
104
|
+
'403.help': 'Solicite acesso com o administrador da sua organização.',
|
|
105
|
+
'404.title': 'Recurso não encontrado',
|
|
106
|
+
'404.description': 'Este recurso não existe mais.',
|
|
107
|
+
'404.help': 'Tente novamente ou fale com o administrador da sua organização.',
|
|
108
|
+
'500.title': 'Erro ao exibir o recurso',
|
|
109
|
+
'500.description': 'Mas não se preocupe, já estamos investigando o que aconteceu.',
|
|
110
|
+
'500.help': 'Tente novamente após alguns minutos.',
|
|
111
|
+
showDetails: 'Ver Detalhes',
|
|
112
|
+
hideDetails: 'Esconder Detalhes',
|
|
113
|
+
},
|
|
114
|
+
} satisfies Dictionary
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Component } from 'react'
|
|
2
|
+
|
|
3
|
+
interface State {
|
|
4
|
+
hasError: boolean,
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
children: React.ReactNode,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class SilentErrorBoundary extends Component<Props, State> {
|
|
12
|
+
constructor(props: Props) {
|
|
13
|
+
super(props)
|
|
14
|
+
this.state = { hasError: false }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static getDerivedStateFromError() {
|
|
18
|
+
return { hasError: true }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
componentDidCatch(error: any, errorInfo: any) {
|
|
22
|
+
// eslint-disable-next-line no-console
|
|
23
|
+
console.error(error, errorInfo)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
render() {
|
|
27
|
+
return this.state.hasError
|
|
28
|
+
? null
|
|
29
|
+
: this.props.children
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -5,6 +5,7 @@ import { LoadingCircular } from '@citric/ui'
|
|
|
5
5
|
import { listToClass, theme } from '@stack-spot/portal-theme'
|
|
6
6
|
import { useMemo, useState } from 'react'
|
|
7
7
|
import { styled } from 'styled-components'
|
|
8
|
+
import { hideOverlayImmediately } from './MenuSections'
|
|
8
9
|
import { PageSelector } from './PageSelector'
|
|
9
10
|
import { MENU_CONTENT_ITEM_PADDING as ITEM_PADDING, MENU_CONTENT_PADDING as PADDING } from './constants'
|
|
10
11
|
import { ItemGroup, MenuAction, MenuItem, MenuSectionContent } from './types'
|
|
@@ -128,18 +129,23 @@ const Title = styled.header`
|
|
|
128
129
|
margin: ${PADDING}px 0 24px ${PADDING}px;
|
|
129
130
|
`
|
|
130
131
|
|
|
131
|
-
const ActionItem = ({ label, onClick, href, active, icon, overflow = 'wrap' }: MenuAction) => (
|
|
132
|
+
const ActionItem = ({ label, onClick, href, active, icon, badge, overflow = 'wrap' }: MenuAction) => (
|
|
132
133
|
<a
|
|
133
134
|
href={active ? undefined : href}
|
|
134
|
-
onClick={
|
|
135
|
+
onClick={() => {
|
|
136
|
+
if (active) return
|
|
137
|
+
if (onClick) onClick()
|
|
138
|
+
hideOverlayImmediately()
|
|
139
|
+
}}
|
|
135
140
|
className={listToClass(['action', 'item-row', active ? 'active' : undefined])}
|
|
136
141
|
>
|
|
137
142
|
{icon}
|
|
138
143
|
<Text appearance="body2" className={`label ${overflow}`}>{label}</Text>
|
|
144
|
+
{badge}
|
|
139
145
|
</a>
|
|
140
146
|
)
|
|
141
147
|
|
|
142
|
-
const CollapsibleGroupItem = ({ label, open: initiallyOpened, children, icon, overflow = 'wrap' }: ItemGroup) => {
|
|
148
|
+
const CollapsibleGroupItem = ({ label, open: initiallyOpened, children, icon, badge, overflow = 'wrap' }: ItemGroup) => {
|
|
143
149
|
const [open, setOpen] = useState(initiallyOpened ?? children?.some(c => 'active' in c && c.active) ?? false)
|
|
144
150
|
const items = useMemo(() => children?.map(renderOption), [children])
|
|
145
151
|
|
|
@@ -148,6 +154,7 @@ const CollapsibleGroupItem = ({ label, open: initiallyOpened, children, icon, ov
|
|
|
148
154
|
<a onClick={() => setOpen(!open)} className="item-row">
|
|
149
155
|
{icon}
|
|
150
156
|
<Text appearance="body2" className={`label ${overflow}`}>{label}</Text>
|
|
157
|
+
{badge}
|
|
151
158
|
<IconBox><ChevronDown className={listToClass(['chevron', open ? 'open' : ''])} /></IconBox>
|
|
152
159
|
</a>
|
|
153
160
|
<MenuGroup className={open ? 'open' : undefined}>{items}</MenuGroup>
|
|
@@ -155,7 +162,7 @@ const CollapsibleGroupItem = ({ label, open: initiallyOpened, children, icon, ov
|
|
|
155
162
|
)
|
|
156
163
|
}
|
|
157
164
|
|
|
158
|
-
const RootGroupItem = ({ label, children, icon, overflow = 'wrap' }: ItemGroup) => {
|
|
165
|
+
const RootGroupItem = ({ label, children, icon, badge, overflow = 'wrap' }: ItemGroup) => {
|
|
159
166
|
const items = useMemo(() => children?.filter(i => !i.hidden).map(renderOption), [children])
|
|
160
167
|
|
|
161
168
|
return (
|
|
@@ -163,6 +170,7 @@ const RootGroupItem = ({ label, children, icon, overflow = 'wrap' }: ItemGroup)
|
|
|
163
170
|
<div className="item-row">
|
|
164
171
|
{icon}
|
|
165
172
|
<Text appearance="overheader2" colorScheme="light.700" className={`group-title label ${overflow}`}>{label}</Text>
|
|
173
|
+
{badge}
|
|
166
174
|
</div>
|
|
167
175
|
<MenuGroup className="open no-indentation">{items}</MenuGroup>
|
|
168
176
|
</>
|