@stack-spot/portal-layout 2.0.0 → 2.1.0

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 (74) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/LayoutOverlayManager.d.ts +20 -0
  3. package/dist/LayoutOverlayManager.d.ts.map +1 -1
  4. package/dist/LayoutOverlayManager.js +74 -22
  5. package/dist/LayoutOverlayManager.js.map +1 -1
  6. package/dist/components/Backdrop.d.ts +10 -0
  7. package/dist/components/Backdrop.d.ts.map +1 -0
  8. package/dist/components/Backdrop.js +18 -0
  9. package/dist/components/Backdrop.js.map +1 -0
  10. package/dist/components/Header.d.ts +5 -1
  11. package/dist/components/Header.d.ts.map +1 -1
  12. package/dist/components/Header.js +3 -2
  13. package/dist/components/Header.js.map +1 -1
  14. package/dist/components/NotificationCenter/NotificationPanel.d.ts +3 -0
  15. package/dist/components/NotificationCenter/NotificationPanel.d.ts.map +1 -0
  16. package/dist/components/NotificationCenter/NotificationPanel.js +16 -0
  17. package/dist/components/NotificationCenter/NotificationPanel.js.map +1 -0
  18. package/dist/components/NotificationCenter/NotificationPanelHeader.d.ts +3 -0
  19. package/dist/components/NotificationCenter/NotificationPanelHeader.d.ts.map +1 -0
  20. package/dist/components/NotificationCenter/NotificationPanelHeader.js +16 -0
  21. package/dist/components/NotificationCenter/NotificationPanelHeader.js.map +1 -0
  22. package/dist/components/NotificationCenter/NotificationsPanelFooter.d.ts +4 -0
  23. package/dist/components/NotificationCenter/NotificationsPanelFooter.d.ts.map +1 -0
  24. package/dist/components/NotificationCenter/NotificationsPanelFooter.js +12 -0
  25. package/dist/components/NotificationCenter/NotificationsPanelFooter.js.map +1 -0
  26. package/dist/components/NotificationCenter/dictionary.d.ts +2 -0
  27. package/dist/components/NotificationCenter/dictionary.d.ts.map +1 -0
  28. package/dist/components/NotificationCenter/dictionary.js +43 -0
  29. package/dist/components/NotificationCenter/dictionary.js.map +1 -0
  30. package/dist/components/NotificationCenter/index.d.ts +2 -0
  31. package/dist/components/NotificationCenter/index.d.ts.map +1 -0
  32. package/dist/components/NotificationCenter/index.js +36 -0
  33. package/dist/components/NotificationCenter/index.js.map +1 -0
  34. package/dist/components/NotificationCenter/styled.d.ts +4 -0
  35. package/dist/components/NotificationCenter/styled.d.ts.map +1 -0
  36. package/dist/components/NotificationCenter/styled.js +74 -0
  37. package/dist/components/NotificationCenter/styled.js.map +1 -0
  38. package/dist/components/NotificationCenter/tour.d.ts +2 -0
  39. package/dist/components/NotificationCenter/tour.d.ts.map +1 -0
  40. package/dist/components/NotificationCenter/tour.js +15 -0
  41. package/dist/components/NotificationCenter/tour.js.map +1 -0
  42. package/dist/components/NotificationCenter/types.d.ts +21 -0
  43. package/dist/components/NotificationCenter/types.d.ts.map +1 -0
  44. package/dist/components/NotificationCenter/types.js +2 -0
  45. package/dist/components/NotificationCenter/types.js.map +1 -0
  46. package/dist/components/NotificationCenter/utils.d.ts +5 -0
  47. package/dist/components/NotificationCenter/utils.d.ts.map +1 -0
  48. package/dist/components/NotificationCenter/utils.js +18 -0
  49. package/dist/components/NotificationCenter/utils.js.map +1 -0
  50. package/dist/components/error/ErrorBoundary.d.ts +3 -0
  51. package/dist/components/error/ErrorBoundary.d.ts.map +1 -1
  52. package/dist/components/error/SilentErrorBoundary.d.ts +3 -0
  53. package/dist/components/error/SilentErrorBoundary.d.ts.map +1 -1
  54. package/dist/index.d.ts +4 -4
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +4 -4
  57. package/dist/index.js.map +1 -1
  58. package/dist/layout.css +1 -1
  59. package/package.json +2 -2
  60. package/readme.md +2 -0
  61. package/src/LayoutOverlayManager.tsx +69 -16
  62. package/src/components/Backdrop.tsx +32 -0
  63. package/src/components/Header.tsx +7 -1
  64. package/src/components/NotificationCenter/NotificationPanel.tsx +35 -0
  65. package/src/components/NotificationCenter/NotificationPanelHeader.tsx +53 -0
  66. package/src/components/NotificationCenter/NotificationsPanelFooter.tsx +25 -0
  67. package/src/components/NotificationCenter/dictionary.ts +44 -0
  68. package/src/components/NotificationCenter/index.tsx +61 -0
  69. package/src/components/NotificationCenter/styled.ts +75 -0
  70. package/src/components/NotificationCenter/tour.tsx +19 -0
  71. package/src/components/NotificationCenter/types.ts +24 -0
  72. package/src/components/NotificationCenter/utils.ts +20 -0
  73. package/src/index.ts +4 -4
  74. package/src/layout.css +1 -1
@@ -0,0 +1,74 @@
1
+ import { theme } from '@stack-spot/portal-theme';
2
+ import { styled } from 'styled-components';
3
+ import { Backdrop } from '../Backdrop.js';
4
+ export const NotificationBox = styled.div `
5
+ .notification-button {
6
+ border: none;
7
+ background: transparent;
8
+ margin: 0 40px;
9
+ position: relative;
10
+
11
+ &.loading {
12
+ cursor: progress;
13
+ opacity: 0.5;
14
+ }
15
+
16
+ &:before {
17
+ content: '';
18
+ position: absolute;
19
+ top: -1px;
20
+ right: -1px;
21
+ width: 12px;
22
+ height: 12px;
23
+ border-radius: 50%;
24
+ background-color: ${theme.color.danger['500']};
25
+ transform: scale(0);
26
+ transition: transform ease-in 0.3s;
27
+ }
28
+
29
+ &.unread:before {
30
+ transform: scale(1);
31
+ }
32
+ }
33
+ `;
34
+ export const StyledBackdrop = styled(Backdrop) `
35
+ position: absolute;
36
+ top: calc(var(--header-height) + 10px);
37
+ right: 60px;
38
+ box-shadow: 4px 4px 48px ${theme.color.danger.contrastText};
39
+
40
+ .notification-panel {
41
+ width: 368px;
42
+ padding: 16px;
43
+ border-radius: 4px;
44
+ background-color: ${theme.color.light[300]};
45
+ border: 1px solid ${theme.color.light[400]};
46
+ }
47
+
48
+ .filter-list {
49
+ list-style: none;
50
+ margin: 16px 0;
51
+ padding: 0;
52
+ display: flex;
53
+ flex-direction: row;
54
+ justify-content: space-between;
55
+ .filter-btn {
56
+ &:focus {
57
+ border-color: transparent;
58
+ }
59
+ &[aria-pressed="true"] {
60
+ border-color: ${theme.color.primary[500]};
61
+ }
62
+ }
63
+ }
64
+
65
+ .see-all {
66
+ margin-top: 8px;
67
+ width: 100%;
68
+ }
69
+
70
+ .error-feedback {
71
+ flex-direction: column;
72
+ }
73
+ `;
74
+ //# sourceMappingURL=styled.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styled.js","sourceRoot":"","sources":["../../../src/components/NotificationCenter/styled.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAA;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC,GAAG,CAAA;;;;;;;;;;;;;;;;;;;;0BAoBf,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC;;;;;;;;;CASlD,CAAA;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAA;;;;6BAIjB,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY;;;;;;wBAMpC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;wBACtB,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;wBAetB,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;CAa/C,CAAA"}
@@ -0,0 +1,2 @@
1
+ export declare const useNotificationsTourStep: () => void;
2
+ //# sourceMappingURL=tour.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tour.d.ts","sourceRoot":"","sources":["../../../src/components/NotificationCenter/tour.tsx"],"names":[],"mappings":"AAIA,eAAO,MAAM,wBAAwB,YAcpC,CAAA"}
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from '@citric/core';
3
+ import { useTour } from '../tour/index.js';
4
+ import { useNotificationsDictionary } from './dictionary.js';
5
+ export const useNotificationsTourStep = () => {
6
+ const t = useNotificationsDictionary();
7
+ const { addStep } = useTour();
8
+ addStep({
9
+ content: _jsx(Box, { px: 5, py: 3, children: _jsx(Text, { appearance: "microtext1", colorScheme: "inverse.contrastText", children: t.tour }) }),
10
+ selector: '.notificationsTour',
11
+ title: t.notifications,
12
+ position: 'bottom',
13
+ });
14
+ };
15
+ //# sourceMappingURL=tour.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tour.js","sourceRoot":"","sources":["../../../src/components/NotificationCenter/tour.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AAEzD,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,EAAE;IAC3C,MAAM,CAAC,GAAG,0BAA0B,EAAE,CAAA;IACtC,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAA;IAE7B,OAAO,CAAC;QACN,OAAO,EAAE,KAAC,GAAG,IAAC,EAAE,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,YACxB,KAAC,IAAI,IAAC,UAAU,EAAC,YAAY,EAAC,WAAW,EAAC,sBAAsB,YAC7D,CAAC,CAAC,IAAI,GACF,GACH;QACN,QAAQ,EAAE,oBAAoB;QAC9B,KAAK,EAAE,CAAC,CAAC,aAAa;QACtB,QAAQ,EAAE,QAAQ;KACZ,CAAC,CAAA;AACX,CAAC,CAAA"}
@@ -0,0 +1,21 @@
1
+ import { NotificationListProps } from '@stack-spot/portal-components/Notifications';
2
+ export type NotificationFilter = 'ALL' | 'UNREAD' | 'HIGH' | 'MEDIUM' | 'LOW';
3
+ export interface NotificationPanelProps extends Omit<NotificationListProps, 'compact' | 'onCommit'> {
4
+ filter: NotificationFilter;
5
+ loading: boolean;
6
+ error: any;
7
+ onFilter: (filter: NotificationFilter) => Promise<void>;
8
+ visible: boolean;
9
+ onClose: () => void;
10
+ }
11
+ export interface NotificationPanelHeaderProps {
12
+ filter: NotificationFilter;
13
+ onChangeFilter: (value: NotificationFilter) => Promise<void>;
14
+ onClose: () => void;
15
+ }
16
+ export interface FilterButtonProps {
17
+ value: NotificationFilter;
18
+ current: NotificationFilter;
19
+ onChangeFilter: (value: NotificationFilter) => void;
20
+ }
21
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/components/NotificationCenter/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,6CAA6C,CAAA;AAEnF,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAA;AAE7E,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,qBAAqB,EAAE,SAAS,GAAG,UAAU,CAAC;IACjG,MAAM,EAAE,kBAAkB,CAAC;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC;IACX,QAAQ,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,kBAAkB,CAAC;IAC3B,cAAc,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,kBAAkB,CAAC;IAC1B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,cAAc,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACrD"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/components/NotificationCenter/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,5 @@
1
+ import { LoadNotificationsFilters } from '@stack-spot/portal-components/Notifications';
2
+ import { NotificationFilter } from './types.js';
3
+ export declare function getFiltersFromName(filterName: NotificationFilter): LoadNotificationsFilters;
4
+ export declare function getNameFromFilters(filters: LoadNotificationsFilters): NotificationFilter;
5
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/components/NotificationCenter/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,6CAA6C,CAAA;AACtF,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAI5C,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,kBAAkB,GAAG,wBAAwB,CAQ3F;AAED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,wBAAwB,GAAG,kBAAkB,CAIxF"}
@@ -0,0 +1,18 @@
1
+ const empty = { committed: undefined, context: undefined, criticality: undefined, search: undefined };
2
+ export function getFiltersFromName(filterName) {
3
+ switch (filterName) {
4
+ case 'ALL': return empty;
5
+ case 'HIGH': return { ...empty, criticality: 'HIGH' };
6
+ case 'LOW': return { ...empty, criticality: 'LOW' };
7
+ case 'MEDIUM': return { ...empty, criticality: 'MEDIUM' };
8
+ case 'UNREAD': return { ...empty, committed: false };
9
+ }
10
+ }
11
+ export function getNameFromFilters(filters) {
12
+ if (filters.committed === false)
13
+ return 'UNREAD';
14
+ if (filters.criticality)
15
+ return filters.criticality;
16
+ return 'ALL';
17
+ }
18
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../../../src/components/NotificationCenter/utils.ts"],"names":[],"mappings":"AAGA,MAAM,KAAK,GAA6B,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAA;AAE/H,MAAM,UAAU,kBAAkB,CAAC,UAA8B;IAC/D,QAAQ,UAAU,EAAE,CAAC;QACnB,KAAK,KAAK,CAAC,CAAC,OAAO,KAAK,CAAA;QACxB,KAAK,MAAM,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,CAAA;QACrD,KAAK,KAAK,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,CAAA;QACnD,KAAK,QAAQ,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAA;QACzD,KAAK,QAAQ,CAAC,CAAC,OAAO,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;IACtD,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,OAAiC;IAClE,IAAI,OAAO,CAAC,SAAS,KAAK,KAAK;QAAE,OAAO,QAAQ,CAAA;IAChD,IAAI,OAAO,CAAC,WAAW;QAAE,OAAO,OAAO,CAAC,WAAW,CAAA;IACnD,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -30,6 +30,9 @@ export declare class ErrorBoundary extends Component<Props, State> {
30
30
  } | undefined;
31
31
  body?: import("react").ReactNode;
32
32
  image?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | undefined;
33
+ direction?: "column" | "row" | undefined;
34
+ style?: import("react").CSSProperties | undefined;
35
+ className?: string | undefined;
33
36
  hasError: boolean;
34
37
  };
35
38
  componentDidCatch(error: any, errorInfo: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/components/error/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAiB,MAAM,qCAAqC,CAAA;AACrF,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGjC,UAAU,KAAM,SAAQ,gBAAgB;IACtC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,KAAK;IACb,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,qBAAa,aAAc,SAAQ,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;gBAC5C,KAAK,EAAE,KAAK;IAKxB,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,GAAG;;;;;;;;;;;;;;;IAI1C,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG;IAM5C,kBAAkB,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAI7C,MAAM;CAKP"}
1
+ {"version":3,"file":"ErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/components/error/ErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAiB,MAAM,qCAAqC,CAAA;AACrF,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGjC,UAAU,KAAM,SAAQ,gBAAgB;IACtC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,KAAK;IACb,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,qBAAa,aAAc,SAAQ,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;gBAC5C,KAAK,EAAE,KAAK;IAKxB,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,GAAG;;;;;;;;;;;;;;;;;;IAI1C,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG;IAM5C,kBAAkB,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAI7C,MAAM;CAKP"}
@@ -31,6 +31,9 @@ export declare class SilentErrorBoundary extends Component<Props, State> {
31
31
  } | undefined;
32
32
  body?: import("react").ReactNode;
33
33
  image?: import("react").ReactElement<any, string | import("react").JSXElementConstructor<any>> | undefined;
34
+ direction?: "column" | "row" | undefined;
35
+ style?: import("react").CSSProperties | undefined;
36
+ className?: string | undefined;
34
37
  hasError: boolean;
35
38
  };
36
39
  componentDidCatch(error: any, errorInfo: any): void;
@@ -1 +1 @@
1
- {"version":3,"file":"SilentErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/components/error/SilentErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AAEtE,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGjC,UAAU,KAAM,SAAQ,gBAAgB;IACtC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,KAAK;IACb,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,qBAAa,mBAAoB,SAAQ,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;gBAClD,KAAK,EAAE,KAAK;IAKxB,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,GAAG;;;;;;;;;;;;;;;IAI1C,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG;IAM5C,kBAAkB,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAI7C,MAAM;CAoBP"}
1
+ {"version":3,"file":"SilentErrorBoundary.d.ts","sourceRoot":"","sources":["../../../src/components/error/SilentErrorBoundary.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AAEtE,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGjC,UAAU,KAAM,SAAQ,gBAAgB;IACtC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,UAAU,KAAK;IACb,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,qBAAa,mBAAoB,SAAQ,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC;gBAClD,KAAK,EAAE,KAAK;IAKxB,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,GAAG;;;;;;;;;;;;;;;;;;IAI1C,iBAAiB,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG;IAM5C,kBAAkB,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC;IAI7C,MAAM;CAoBP"}
package/dist/index.d.ts CHANGED
@@ -1,15 +1,15 @@
1
- export { Layout, RawLayout } from './Layout.js';
2
- export { overlay } from './LayoutOverlayManager.js';
3
1
  export { Dialog } from './components/Dialog.js';
4
2
  export { Header, HeaderProps } from './components/Header.js';
5
- export { OverlayContent } from './components/OverlayContent.js';
6
- export { PortalSwitcher } from './components/PortalSwitcher.js';
7
3
  export { ActionItem, MenuContent, MenuGroup, Title } from './components/menu/MenuContent.js';
8
4
  export { MenuSections } from './components/menu/MenuSections.js';
9
5
  export * from './components/menu/types.js';
6
+ export { CLOSE_OVERLAY_ID, OverlayContent } from './components/OverlayContent.js';
7
+ export { PortalSwitcher } from './components/PortalSwitcher.js';
10
8
  export * from './components/tour/index.js';
11
9
  export * from './components/types.js';
12
10
  export * from './elements.js';
13
11
  export * from './errors.js';
12
+ export { Layout, RawLayout } from './Layout.js';
13
+ export { overlay } from './LayoutOverlayManager.js';
14
14
  export * from './utils.js';
15
15
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAA;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAC7D,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAA;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAC7D,cAAc,yBAAyB,CAAA;AACvC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAChD,cAAc,SAAS,CAAA"}
package/dist/index.js CHANGED
@@ -1,15 +1,15 @@
1
- export { Layout, RawLayout } from './Layout.js';
2
- export { overlay } from './LayoutOverlayManager.js';
3
1
  export { Dialog } from './components/Dialog.js';
4
2
  export { Header } from './components/Header.js';
5
- export { OverlayContent } from './components/OverlayContent.js';
6
- export { PortalSwitcher } from './components/PortalSwitcher.js';
7
3
  export { ActionItem, MenuContent, MenuGroup, Title } from './components/menu/MenuContent.js';
8
4
  export { MenuSections } from './components/menu/MenuSections.js';
9
5
  export * from './components/menu/types.js';
6
+ export { CLOSE_OVERLAY_ID, OverlayContent } from './components/OverlayContent.js';
7
+ export { PortalSwitcher } from './components/PortalSwitcher.js';
10
8
  export * from './components/tour/index.js';
11
9
  export * from './components/types.js';
12
10
  export * from './elements.js';
13
11
  export * from './errors.js';
12
+ export { Layout, RawLayout } from './Layout.js';
13
+ export { overlay } from './LayoutOverlayManager.js';
14
14
  export * from './utils.js';
15
15
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAChD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAA;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAC7D,cAAc,yBAAyB,CAAA;AACvC,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,cAAc,SAAS,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAC5C,OAAO,EAAE,MAAM,EAAe,MAAM,qBAAqB,CAAA;AACzD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAA;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAA;AAC7D,cAAc,yBAAyB,CAAA;AACvC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC9E,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,cAAc,mBAAmB,CAAA;AACjC,cAAc,oBAAoB,CAAA;AAClC,cAAc,YAAY,CAAA;AAC1B,cAAc,UAAU,CAAA;AACxB,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAChD,cAAc,SAAS,CAAA"}
package/dist/layout.css CHANGED
@@ -27,7 +27,7 @@ body {
27
27
  display: flex;
28
28
  flex-direction: column;
29
29
  flex: 1;
30
- background: var(--light-400);
30
+ background: var(--light-300);
31
31
  font: var(--font);
32
32
  color: var(--light-contrastText);
33
33
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stack-spot/portal-layout",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,7 +15,7 @@
15
15
  "@citric/ui": "^5.7.2 || ^6.0.0",
16
16
  "@stack-spot/portal-theme": "^1.0.0",
17
17
  "@stack-spot/portal-translate": "^1.0.0",
18
- "@stack-spot/portal-components": "^2.0.0",
18
+ "@stack-spot/portal-components": "^2.1.0",
19
19
  "react": "^18.2.0",
20
20
  "react-dom": "^18.2.0",
21
21
  "styled-components": "^6.1.10"
package/readme.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Layout
2
2
  Implements the general layout for a Stackspot web application.
3
3
 
4
+
4
5
  ## Components
5
6
  - Header: a top bar with a logo and a user menu.
6
7
  - Menu (sections): a left vertical bar with every section in the application.
@@ -58,6 +59,7 @@ const MyApp = () => {
58
59
  }
59
60
  ```
60
61
 
62
+
61
63
  The Header and Menu in a layout are highly customizable. Check the code documentation for more details!
62
64
 
63
65
  ## Modal and right panel pitfalls
@@ -71,24 +71,53 @@ class LayoutOverlayManager {
71
71
  */
72
72
  private lastActiveElement: Element | null = null
73
73
 
74
+ private closeCustomBackdrops(elements: NodeListOf<Element>) {
75
+ // this is the easiest way to close each custom backdrop by calling their respective "onClose" callbacks. This is a hidden button
76
+ // that exists in every <Backdrop> component.
77
+ elements.forEach(element => (element.querySelector('[data-custom-backdrop-close]') as HTMLElement)?.click?.())
78
+ }
79
+
80
+ private onClickBackdrop(event: MouseEvent) {
81
+ if (this.isModalOpen()) !this.elements?.modal?.contains?.(event.target as Node) && this.closeModal()
82
+ else if (this.isRightPanelOpen()) !this.elements?.rightPanel?.contains?.(event.target as Node) && this.closeRightPanel()
83
+ else {
84
+ const customBackdrops = this.getAllVisibleCustomBackdrops()
85
+ if (customBackdrops.length) {
86
+ let isClickInside = false
87
+ customBackdrops.forEach((element) => {
88
+ if (element.contains(event.target as Node)) isClickInside = true
89
+ })
90
+ if (!isClickInside) this.closeCustomBackdrops(customBackdrops)
91
+ } else {
92
+ this.setMainContentInteractivity(true)
93
+ }
94
+ }
95
+ }
96
+
97
+ private onPressKeyInBackdrop(event: KeyboardEvent) {
98
+ if (event.key !== 'Escape') return
99
+ if (this.isModalOpen()) this.closeModal()
100
+ if (this.isRightPanelOpen()) this.closeRightPanel()
101
+ else {
102
+ const customBackdrops = this.getAllVisibleCustomBackdrops()
103
+ if (customBackdrops.length) this.closeCustomBackdrops(customBackdrops)
104
+ else this.setMainContentInteractivity(true)
105
+ }
106
+ event.preventDefault()
107
+ }
108
+
74
109
  private setupElements() {
75
110
  this.elements = getLayoutElements()
76
- this.elements.backdrop?.addEventListener('mousedown', (event) => {
77
- if (this.isModalOpen()) !this.elements?.modal?.contains?.(event.target as Node) && this.closeModal()
78
- else if (this.isRightPanelOpen()) !this.elements?.rightPanel?.contains?.(event.target as Node) && this.closeRightPanel()
79
- else this.setMainContentInteractivity(true)
80
- })
81
- this.elements.backdrop?.addEventListener('keydown', (event) => {
82
- if (event.key !== 'Escape') return
83
- if (this.isModalOpen()) this.closeModal()
84
- if (this.isRightPanelOpen()) this.closeRightPanel()
85
- else this.setMainContentInteractivity(true)
86
- event.preventDefault()
87
- })
111
+ this.elements.backdrop?.addEventListener('mousedown', (event) => this.onClickBackdrop(event))
112
+ this.elements.backdrop?.addEventListener('keydown', (event) => this.onPressKeyInBackdrop(event))
88
113
  this.setInteractivity(this.elements?.modal, false)
89
114
  this.setInteractivity(this.elements?.rightPanel, false)
90
115
  this.setInteractivity(this.elements?.bottomDialog, false)
91
116
  }
117
+
118
+ private getAllVisibleCustomBackdrops() {
119
+ return document.querySelectorAll('[data-custom-backdrop-visibility=true]')
120
+ }
92
121
 
93
122
  /**
94
123
  * Setup the overlay layout elements.
@@ -130,9 +159,9 @@ class LayoutOverlayManager {
130
159
  this.elements?.backdrop?.setAttribute('class', interactive ? '' : 'visible')
131
160
  }
132
161
 
133
- private showOverlay(element: HTMLElement | null | undefined, extraClasses: string[] = [], blockMainContent = true) {
162
+ private showOverlay(element: HTMLElement | null | undefined, extraClasses: string[] = [], blockMainContent = true, manageClasses = true) {
134
163
  this.lastActiveElement = document.activeElement
135
- element?.classList.add('visible', ...extraClasses)
164
+ if (manageClasses) element?.classList.add('visible', ...extraClasses)
136
165
  this.setInteractivity(element, true)
137
166
  if (blockMainContent) this.setMainContentInteractivity(false)
138
167
  setTimeout(() => focusFirstChild(
@@ -141,12 +170,36 @@ class LayoutOverlayManager {
141
170
  ), 50)
142
171
  }
143
172
 
144
- private hideOverlay(element: HTMLElement | null | undefined) {
145
- element?.setAttribute('class', '')
173
+ private hideOverlay(element: HTMLElement | null | undefined, manageClasses = true) {
174
+ if (manageClasses) element?.setAttribute('class', '')
146
175
  this.setInteractivity(element, false)
147
176
  this.setMainContentInteractivity(true)
148
177
  }
149
178
 
179
+ /**
180
+ * Shows the backdrop. The element passed as parameter must be a child of backdrop. Some accessibility features will be attached to
181
+ * the element.
182
+ *
183
+ * Consider using the component <Backdrop> from this library instead of calling this function directly.
184
+ * @param element the element to show inside the backdrop. It must already be a child of the backdrop.
185
+ */
186
+ showBackdrop(element?: HTMLElement | null) {
187
+ this.showOverlay(element, [], true, false)
188
+ }
189
+
190
+ /**
191
+ * Closes the backdrop. The element passed as parameter must be a child of backdrop. Some accessibility features will be run on the
192
+ * element.
193
+ *
194
+ * Consider using the component <Backdrop> from this library instead of calling this function directly.
195
+ * @param element the element showing inside the backdrop. It must be a child of the backdrop.
196
+ */
197
+ closeBackdrop(element?: HTMLElement | null) {
198
+ this.hideOverlay(element, false)
199
+ const lastActiveElement = this.lastActiveElement as HTMLElement | null
200
+ lastActiveElement?.focus?.()
201
+ }
202
+
150
203
  /**
151
204
  * @returns true if the modal is currently opened. False otherwise.
152
205
  */
@@ -0,0 +1,32 @@
1
+ import { useLayoutEffect, useRef } from 'react'
2
+ import { createPortal } from 'react-dom'
3
+ import { getLayoutElements } from '../elements'
4
+ import { overlay } from '../LayoutOverlayManager'
5
+
6
+ export interface BackdropProps {
7
+ children: React.ReactNode,
8
+ visible: boolean,
9
+ onClose: () => void,
10
+ style?: React.CSSProperties,
11
+ className?: string,
12
+ }
13
+
14
+ export const Backdrop = ({ children, visible = false, onClose, className, style }: BackdropProps) => {
15
+ const ref = useRef<HTMLDivElement>(null)
16
+
17
+ useLayoutEffect(() => {
18
+ if (visible) overlay.showBackdrop(ref.current)
19
+ else overlay.closeBackdrop(ref.current)
20
+ }, [visible])
21
+
22
+ const backdropContent = (
23
+ <div ref={ref} data-custom-backdrop-visibility={visible} className={className} style={style}>
24
+ {children}
25
+ {/* This button allows the OverlayManager to programmatically close any custom backdrop by calling their respective onClose prop. */}
26
+ <button data-custom-backdrop-close onClick={onClose} style={{ display: 'none' }}>Close</button>
27
+ </div>
28
+ )
29
+
30
+ const backdrop = getLayoutElements().backdrop
31
+ return backdrop ? createPortal(backdropContent, backdrop) : null
32
+ }
@@ -3,6 +3,7 @@ import { SelectionListProps } from '@stack-spot/portal-components/SelectionList'
3
3
  import { useAnchorTag } from '@stack-spot/portal-components/anchor'
4
4
  import { Logo } from '@stack-spot/portal-components/svg'
5
5
  import { ReactNode } from 'react'
6
+ import { NotificationCenter } from './NotificationCenter'
6
7
  import { PortalSwitcher, PortalSwitcherProps } from './PortalSwitcher'
7
8
  import { UserMenu } from './UserMenu'
8
9
 
@@ -39,13 +40,17 @@ export interface HeaderProps {
39
40
  * A custom React Node to show at the right (end) of the header.
40
41
  */
41
42
  right?: ReactNode,
43
+ /**
44
+ * Whether or not to show the notification center.
45
+ */
46
+ showNotificationCenter?: boolean,
42
47
  }
43
48
 
44
49
  /**
45
50
  * The page header.
46
51
  * @param props the React props for the header {@link HeaderProps}.
47
52
  */
48
- export const Header = ({ logo, logoHref, center, right, userName, email, options, portalSwitch }: HeaderProps) => {
53
+ export const Header = ({ logo, logoHref, center, right, userName, email, options, portalSwitch, showNotificationCenter }: HeaderProps) => {
49
54
  const Link = useAnchorTag()
50
55
 
51
56
  return (
@@ -56,6 +61,7 @@ export const Header = ({ logo, logoHref, center, right, userName, email, options
56
61
  }
57
62
  <Flex flex={1}>{center}</Flex>
58
63
  {right}
64
+ {showNotificationCenter && <NotificationCenter />}
59
65
  {userName && <UserMenu userName={userName} email={email} options={options} />}
60
66
  </>
61
67
  )
@@ -0,0 +1,35 @@
1
+ import { AnimatedHeight } from '@stack-spot/portal-components/AnimatedHeight'
2
+ import { ErrorFeedback } from '@stack-spot/portal-components/error'
3
+ import { NotificationList, useNotificationController } from '@stack-spot/portal-components/Notifications'
4
+ import { ScrollView } from '@stack-spot/portal-components/ScrollView'
5
+ import { NotificationPanelHeader } from './NotificationPanelHeader'
6
+ import { NotificationPanelFooter } from './NotificationsPanelFooter'
7
+ import { StyledBackdrop } from './styled'
8
+ import { NotificationPanelProps } from './types'
9
+
10
+ export const NotificationPanel = ({ filter, onFilter, loading, error, visible, onClose, ...props }: NotificationPanelProps) => {
11
+ const controller = useNotificationController()
12
+ return (
13
+ <StyledBackdrop visible={visible} onClose={onClose}>
14
+ <AnimatedHeight
15
+ className="notification-panel"
16
+ visible={visible}
17
+ header={<NotificationPanelHeader filter={filter} onChangeFilter={onFilter} onClose={onClose} />}
18
+ footer={<NotificationPanelFooter onClose={onClose} />}
19
+ >
20
+ <ScrollView direction="vertical" style={{ maxHeight: 'calc(100vh - 300px)' }}>
21
+ {error ? <ErrorFeedback code={error.code} direction="column" /> : <NotificationList
22
+ {...props}
23
+ compact
24
+ loading={loading}
25
+ onCommit={id => controller.markAsCommitted(id)}
26
+ onClickAction={(id) => {
27
+ onClose()
28
+ controller.config.onClickAction?.(id)
29
+ }}
30
+ />}
31
+ </ScrollView>
32
+ </AnimatedHeight>
33
+ </StyledBackdrop>
34
+ )
35
+ }
@@ -0,0 +1,53 @@
1
+ import { Button, Flex, IconBox, Text } from '@citric/core'
2
+ import { TimesMini } from '@citric/icons'
3
+ import { IconButton } from '@citric/ui'
4
+ import { CLOSE_OVERLAY_ID } from '../OverlayContent'
5
+ import { useNotificationsDictionary } from './dictionary'
6
+ import { FilterButtonProps, NotificationPanelHeaderProps } from './types'
7
+
8
+ const FilterButton = ({ current, onChangeFilter, value }: FilterButtonProps) => {
9
+ const t = useNotificationsDictionary()
10
+ const active = current === value
11
+
12
+ return (
13
+ <Button
14
+ appearance="text"
15
+ role="button"
16
+ aria-label={t[`${value}.ariaLabel`]}
17
+ aria-pressed={active}
18
+ onClick={() => onChangeFilter(value)}
19
+ className="filter-btn"
20
+ >
21
+ <Flex flexDirection="row" style={{ gap: '8px' }} alignItems="center">
22
+ <Text colorScheme="inverse" appearance="microtext1">
23
+ {t[`${value}.label`]}
24
+ </Text>
25
+ </Flex>
26
+ </Button>
27
+ )
28
+ }
29
+
30
+ export const NotificationPanelHeader = ({ filter, onChangeFilter, onClose }: NotificationPanelHeaderProps) => {
31
+ const t = useNotificationsDictionary()
32
+ return (
33
+ <Flex flexDirection="column">
34
+ <Flex justifyContent="space-between">
35
+ <Text appearance="h4">
36
+ {t.notifications}
37
+ </Text>
38
+ <IconButton id={CLOSE_OVERLAY_ID} aria-label={t.close} onClick={onClose}>
39
+ <IconBox size="xs">
40
+ <TimesMini />
41
+ </IconBox>
42
+ </IconButton>
43
+ </Flex>
44
+ <ul className="filter-list">
45
+ <li><FilterButton current={filter} onChangeFilter={onChangeFilter} value="ALL" /></li>
46
+ <li><FilterButton current={filter} onChangeFilter={onChangeFilter} value="UNREAD" /></li>
47
+ <li><FilterButton current={filter} onChangeFilter={onChangeFilter} value="HIGH" /></li>
48
+ <li><FilterButton current={filter} onChangeFilter={onChangeFilter} value="MEDIUM" /></li>
49
+ <li><FilterButton current={filter} onChangeFilter={onChangeFilter} value="LOW" /></li>
50
+ </ul>
51
+ </Flex>
52
+ )
53
+ }
@@ -0,0 +1,25 @@
1
+ import { Button, Text } from '@citric/core'
2
+ import { useAnchorTag } from '@stack-spot/portal-components/anchor'
3
+ import { useNotificationController } from '@stack-spot/portal-components/Notifications'
4
+ import { useNotificationsDictionary } from './dictionary'
5
+
6
+ export const NotificationPanelFooter = ({ onClose }: { onClose: () => void }) => {
7
+ const t = useNotificationsDictionary()
8
+ const controller = useNotificationController()
9
+ const Link = useAnchorTag()
10
+ return (
11
+ <Button
12
+ as={Link}
13
+ onClick={onClose}
14
+ size="sm"
15
+ colorScheme="inverse"
16
+ appearance="text"
17
+ className="see-all"
18
+ href={controller.config.notificationsPath}
19
+ >
20
+ <Text appearance="microtext1">
21
+ {t.seeAll}
22
+ </Text>
23
+ </Button>
24
+ )
25
+ }