@stack-spot/portal-layout 0.0.52 → 0.0.53

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 (97) hide show
  1. package/dist/Layout.d.ts +2 -2
  2. package/dist/Layout.js +1 -1
  3. package/dist/LayoutOverlayManager.js +6 -6
  4. package/dist/LayoutOverlayManager.js.map +1 -1
  5. package/dist/components/Dialog.d.ts +1 -1
  6. package/dist/components/Dialog.js +1 -1
  7. package/dist/components/Header.d.ts +1 -1
  8. package/dist/components/Header.js +1 -1
  9. package/dist/components/OverlayContent.d.ts +1 -1
  10. package/dist/components/OverlayContent.js +20 -20
  11. package/dist/components/PortalSwitcher.d.ts +1 -1
  12. package/dist/components/PortalSwitcher.js +54 -54
  13. package/dist/components/SelectionList.d.ts +1 -1
  14. package/dist/components/SelectionList.js +54 -54
  15. package/dist/components/SelectionList.js.map +1 -1
  16. package/dist/components/Toaster.d.ts +1 -1
  17. package/dist/components/Toaster.js +1 -1
  18. package/dist/components/UserMenu.d.ts +1 -1
  19. package/dist/components/UserMenu.js +41 -41
  20. package/dist/components/error/ErrorBoundary.d.ts +1 -1
  21. package/dist/components/error/ErrorBoundary.js +1 -1
  22. package/dist/components/error/ErrorFeedback.d.ts +1 -1
  23. package/dist/components/error/ErrorFeedback.js +1 -1
  24. package/dist/components/error/SilentErrorBoundary.d.ts +1 -1
  25. package/dist/components/error/SilentErrorBoundary.js +1 -1
  26. package/dist/components/menu/MenuContent.d.ts +12 -10
  27. package/dist/components/menu/MenuContent.d.ts.map +1 -1
  28. package/dist/components/menu/MenuContent.js +146 -146
  29. package/dist/components/menu/MenuContent.js.map +1 -1
  30. package/dist/components/menu/MenuSections.d.ts +1 -1
  31. package/dist/components/menu/MenuSections.js +1 -1
  32. package/dist/components/menu/MenuSections.js.map +1 -1
  33. package/dist/components/menu/PageSelector.d.ts +1 -1
  34. package/dist/components/menu/PageSelector.js +65 -65
  35. package/dist/components/menu/PageSelector.js.map +1 -1
  36. package/dist/components/menu/use-check-text-overflow.js.map +1 -1
  37. package/dist/layout-context.d.ts +1 -1
  38. package/dist/layout-context.js +1 -1
  39. package/dist/layout.css +466 -466
  40. package/dist/svg/AI.d.ts +1 -1
  41. package/dist/svg/AI.js +1 -1
  42. package/dist/svg/EDP.d.ts +1 -1
  43. package/dist/svg/EDP.js +1 -1
  44. package/dist/svg/Forbidden.d.ts +1 -1
  45. package/dist/svg/Forbidden.js +1 -1
  46. package/dist/svg/HUB.d.ts +1 -1
  47. package/dist/svg/HUB.js +1 -1
  48. package/dist/svg/Logo.d.ts +1 -1
  49. package/dist/svg/Logo.js +1 -1
  50. package/dist/svg/NotFound.d.ts +1 -1
  51. package/dist/svg/NotFound.js +1 -1
  52. package/dist/svg/ServerError.d.ts +1 -1
  53. package/dist/svg/ServerError.js +1 -1
  54. package/dist/svg/Unauthenticated.d.ts +1 -1
  55. package/dist/svg/Unauthenticated.js +1 -1
  56. package/dist/toaster.js +2 -2
  57. package/dist/toaster.js.map +1 -1
  58. package/dist/utils.js.map +1 -1
  59. package/package.json +5 -5
  60. package/src/Layout.tsx +106 -106
  61. package/src/LayoutOverlayManager.tsx +273 -273
  62. package/src/components/Dialog.tsx +93 -93
  63. package/src/components/Header.tsx +34 -34
  64. package/src/components/OverlayContent.tsx +58 -58
  65. package/src/components/PortalSwitcher.tsx +147 -147
  66. package/src/components/SelectionList.tsx +272 -272
  67. package/src/components/Toaster.tsx +16 -16
  68. package/src/components/UserMenu.tsx +111 -111
  69. package/src/components/error/ErrorBoundary.tsx +38 -38
  70. package/src/components/error/ErrorFeedback.tsx +114 -114
  71. package/src/components/error/ErrorManager.ts +31 -31
  72. package/src/components/error/SilentErrorBoundary.tsx +54 -54
  73. package/src/components/menu/MenuContent.tsx +296 -296
  74. package/src/components/menu/MenuSections.tsx +270 -270
  75. package/src/components/menu/PageSelector.tsx +154 -154
  76. package/src/components/menu/constants.ts +2 -2
  77. package/src/components/menu/types.ts +112 -112
  78. package/src/components/menu/use-check-text-overflow.tsx +26 -26
  79. package/src/components/menu/use-keyboard-controls.tsx +70 -70
  80. package/src/components/types.ts +15 -15
  81. package/src/dictionary.ts +25 -25
  82. package/src/elements.ts +24 -24
  83. package/src/errors.ts +11 -11
  84. package/src/index.ts +17 -17
  85. package/src/layout-context.tsx +22 -22
  86. package/src/layout.css +466 -466
  87. package/src/svg/AI.tsx +37 -37
  88. package/src/svg/EDP.tsx +35 -35
  89. package/src/svg/Forbidden.tsx +22 -22
  90. package/src/svg/HUB.tsx +35 -35
  91. package/src/svg/Logo.tsx +35 -35
  92. package/src/svg/NotFound.tsx +16 -16
  93. package/src/svg/ServerError.tsx +33 -33
  94. package/src/svg/Unauthenticated.tsx +16 -16
  95. package/src/toaster.tsx +76 -76
  96. package/src/utils.ts +114 -114
  97. package/tsconfig.json +8 -8
@@ -1,296 +1,296 @@
1
- /* eslint-disable react-refresh/only-export-components */
2
- /* eslint-disable @typescript-eslint/no-unused-vars */
3
- import { Flex, IconBox, Text } from '@citric/core'
4
- import { ArrowLeft, ChevronDown } from '@citric/icons'
5
- import { LoadingCircular } from '@citric/ui'
6
- import { listToClass, theme } from '@stack-spot/portal-theme'
7
- import { useMemo, useState } from 'react'
8
- import { styled } from 'styled-components'
9
- import { useAnchorTag } from '../../layout-context'
10
- import { hideOverlayImmediately } from './MenuSections'
11
- import { PageSelector } from './PageSelector'
12
- import { MENU_CONTENT_ITEM_PADDING as ITEM_PADDING, MENU_CONTENT_PADDING as PADDING } from './constants'
13
- import { ItemGroup, MenuAction, MenuItem, MenuSectionContent } from './types'
14
- import { useCheckTextOverflow } from './use-check-text-overflow'
15
-
16
- const BackLink = styled.a`
17
- display: flex;
18
- flex-direction: row;
19
- align-items: center;
20
- margin: ${PADDING}px;
21
- margin-bottom: 16px;
22
- gap: 6px;
23
- `
24
-
25
- export const MenuGroup = styled.ul`
26
- padding: 0 0 0 16px;
27
- display: flex;
28
- flex-direction: column;
29
- visibility: hidden;
30
- transition: visibility 0s 0.3s;
31
-
32
- &.no-indentation {
33
- padding: 0;
34
- }
35
-
36
- .item-row-group > a {
37
- padding: 0 16px;
38
- margin: 0;
39
- border-radius: 0;
40
- }
41
-
42
- .item-row-group > a::before {
43
- content: '';
44
- position: absolute;
45
- top: 0;
46
- left: 0;
47
- right: 0;
48
- bottom: 0;
49
- background-color: var(--light-300);
50
- opacity: 0.24;
51
- border-radius: inherit;
52
- }
53
-
54
- .item-row {
55
- display: flex;
56
- flex-direction: row;
57
- gap: 8px;
58
- align-items: center;
59
-
60
- .label {
61
- flex: 1;
62
- &.hidden, &.ellipsis {
63
- white-space: nowrap;
64
- overflow: hidden;
65
- }
66
- &.ellipsis {
67
- text-overflow: ellipsis;
68
- }
69
- }
70
- }
71
-
72
- .item-row-group {
73
- margin-top: 16px;
74
- }
75
-
76
- li a {
77
- position: relative;
78
- height: 0;
79
- overflow: hidden;
80
- transition: height 0.3s, background-color 0.2s;
81
- margin-left: ${PADDING - ITEM_PADDING}px;
82
- padding-left: ${ITEM_PADDING}px;
83
-
84
- &:hover {
85
- background-color: ${theme.color.light['500']};
86
- }
87
-
88
- &.action {
89
- &:before {
90
- content: '';
91
- position: absolute;
92
- left: 2px;
93
- width: 2px;
94
- height: 0;
95
- background: inherit;
96
- transition: height 0.2s;
97
- }
98
-
99
- &.active {
100
-
101
- &:hover {
102
- background-color: transparent;
103
- }
104
-
105
- &:before {
106
- background: ${theme.color.primary['500']};
107
- height: 24px;
108
- }
109
- }
110
-
111
- &:not(.active):hover:before {
112
- background: ${theme.color.light.contrastText};
113
- height: 24px;
114
- }
115
- }
116
-
117
- .chevron {
118
- transition: transform 0.3s;
119
- &:not(.open) {
120
- transform: rotate(-90deg);
121
- }
122
- }
123
-
124
- .item-row-title {
125
- opacity: 0.7;
126
- }
127
- }
128
-
129
- &.open {
130
- visibility: visible;
131
- transition: unset;
132
- & > li > a, & > li > .item-row-group > a {
133
- height: 40px;
134
- }
135
- }
136
-
137
- &:not(.open) &.open > li > a {
138
- height: 0;
139
- }
140
-
141
- &.root {
142
- margin-bottom: ${PADDING}px;
143
-
144
- & > li {
145
- .group-title {
146
- margin-left: ${PADDING}px;
147
- margin-bottom: 5px;
148
- margin-top: 40px;
149
- display: block;
150
- }
151
-
152
- &:first-child {
153
- .group-title {
154
- margin-top: 0;
155
- }
156
- }
157
- }
158
- }
159
- `
160
-
161
- export const Title = styled.header`
162
- display: flex;
163
- flex-direction: column;
164
- margin: ${PADDING}px 0 24px ${PADDING}px;
165
- `
166
-
167
- export const ActionItem = ({ label, onClick, href, active, icon, badge, overflow = 'wrap' }: MenuAction) => {
168
- const Link = useAnchorTag()
169
- const { ref, overflow: textOverflow } = useCheckTextOverflow()
170
- const isTextLabel = typeof label === 'string'
171
- return (
172
- <Link
173
- href={href}
174
- onClick={() => {
175
- if (active) return
176
- if (onClick) onClick()
177
- hideOverlayImmediately()
178
- }}
179
- className={listToClass(['action', 'item-row', active ? 'active' : undefined])}
180
- {...(active ? { 'aria-current': 'page' } : undefined)}
181
- {...(!href ? { 'tabIndex': 0 } : undefined)}
182
- >
183
- {icon}
184
- {isTextLabel ?
185
- <Text ref={ref} appearance="body2" className={`label ${overflow}`} title={textOverflow ? label : ''}>{label}</Text> :
186
- label.element}
187
- {badge}
188
- </Link>
189
- )
190
- }
191
-
192
- const CollapsibleGroupItem = ({ label, open: initiallyOpened, children, icon, badge, root, overflow = 'wrap' }:
193
- ItemGroup & { root?: boolean }) => {
194
- const [open, setOpen] = useState(initiallyOpened ?? children?.some(c => 'active' in c && c.active) ?? false)
195
- const items = useMemo(() => children?.filter(i => !i.hidden).map(renderOption), [children])
196
- const id = `menuGroup${label}`
197
-
198
- return (
199
- <>
200
- <a
201
- onClick={() => setOpen(!open)}
202
- onKeyDown={e => e.key === 'Enter' && setOpen(!open)}
203
- className="item-row"
204
- tabIndex={0}
205
- aria-controls={id}
206
- aria-expanded={open}
207
- >
208
- {icon}
209
- <Text appearance={root ? 'overheader2' : 'body2'}
210
- colorScheme="light.contrastText"
211
- className={`label ${overflow} ${root ? 'item-row-title' : ''}`}>
212
- {label}
213
- </Text>
214
- {badge}
215
- <IconBox sx={{ mr: root ? undefined : '5' }}>
216
- <ChevronDown className={listToClass(['chevron', open ? 'open' : ''])} />
217
- </IconBox>
218
- </a>
219
- <MenuGroup id={id}
220
- className={`${open ? 'open' : ''} ${root ? 'no-indentation' : ''}`}
221
- aria-hidden={!open}>{items}</MenuGroup>
222
- </>
223
- )
224
- }
225
-
226
- const RootGroupItem = (props: ItemGroup) => {
227
- const items = useMemo(() => props.children?.filter(i => !i.hidden).map(renderOption), [props.children])
228
-
229
- return <>
230
- {items.length ? <div className="item-row-group">
231
- <CollapsibleGroupItem {...props} open={true} root={true} />
232
- </div> :
233
- <>
234
- <div className="item-row">
235
- {props.icon}
236
- <Text appearance="overheader2" colorScheme="light.700" className={`group-title label ${props.overflow}`}>{props.label}</Text>
237
- {props.badge}
238
- </div>
239
- <MenuGroup className="open no-indentation">{items}</MenuGroup>
240
- </>
241
- }
242
- </>
243
- }
244
-
245
- const GroupItem = ({ root, ...item }: ItemGroup & { root?: boolean }) => (
246
- root ? <RootGroupItem {...item} /> : <CollapsibleGroupItem {...item} />
247
- )
248
-
249
- function renderOption({ root, ...option }: MenuItem & { root?: boolean }) {
250
- const labelText = typeof option.label === 'string' ? option.label : option.label.id
251
- return (
252
- <li key={labelText} role="menuitem" aria-selected={'children' in option ? undefined : option.active}>
253
- {'children' in option ? <GroupItem root={root} {...option} /> : <ActionItem {...option} />}
254
- </li >
255
- )
256
- }
257
-
258
- export const MenuContent = ({ pageSelector, goBack, title, subtitle, afterTitle, options = [], loading, error }: MenuSectionContent) => {
259
- const items = useMemo(() => options.filter(o => !o.hidden).map(o => renderOption({ ...o, root: true })), [options])
260
-
261
- function renderContent() {
262
- if (loading) {
263
- return (
264
- <Flex justifyContent="center" alignItems="center" flex={1} sx={{ padding: '40px' }}>
265
- <LoadingCircular />
266
- </Flex>
267
- )
268
- }
269
- if (error) return <Text colorScheme="danger">{error}</Text>
270
- return <MenuGroup className="open root no-indentation">{items}</MenuGroup>
271
- }
272
-
273
- return (
274
- <>
275
- {goBack && (
276
- <BackLink href={goBack.href} onClick={goBack.onClick}>
277
- <IconBox colorIcon="inverse.500" size="sm">
278
- <ArrowLeft />
279
- </IconBox>
280
- {typeof goBack?.label === 'string' ?
281
- <Text appearance="body2" nowrapEllipsis>{goBack.label}</Text> :
282
- goBack.label.element}
283
- </BackLink>
284
- )}
285
- {title && (
286
- <Title>
287
- <Text appearance="overheader1" colorScheme="primary" sx={{ fontSize: '0.75rem', mt: 2, mb: 2 }}>{title}</Text>
288
- {subtitle && <Text appearance="h5">{subtitle}</Text>}
289
- </Title>
290
- )}
291
- {afterTitle}
292
- {pageSelector && <PageSelector {...pageSelector} />}
293
- {renderContent()}
294
- </>
295
- )
296
- }
1
+ /* eslint-disable react-refresh/only-export-components */
2
+ /* eslint-disable @typescript-eslint/no-unused-vars */
3
+ import { Flex, IconBox, Text } from '@citric/core'
4
+ import { ArrowLeft, ChevronDown } from '@citric/icons'
5
+ import { LoadingCircular } from '@citric/ui'
6
+ import { listToClass, theme } from '@stack-spot/portal-theme'
7
+ import { useMemo, useState } from 'react'
8
+ import { styled } from 'styled-components'
9
+ import { useAnchorTag } from '../../layout-context'
10
+ import { hideOverlayImmediately } from './MenuSections'
11
+ import { PageSelector } from './PageSelector'
12
+ import { MENU_CONTENT_ITEM_PADDING as ITEM_PADDING, MENU_CONTENT_PADDING as PADDING } from './constants'
13
+ import { ItemGroup, MenuAction, MenuItem, MenuSectionContent } from './types'
14
+ import { useCheckTextOverflow } from './use-check-text-overflow'
15
+
16
+ const BackLink = styled.a`
17
+ display: flex;
18
+ flex-direction: row;
19
+ align-items: center;
20
+ margin: ${PADDING}px;
21
+ margin-bottom: 16px;
22
+ gap: 6px;
23
+ `
24
+
25
+ export const MenuGroup = styled.ul`
26
+ padding: 0 0 0 16px;
27
+ display: flex;
28
+ flex-direction: column;
29
+ visibility: hidden;
30
+ transition: visibility 0s 0.3s;
31
+
32
+ &.no-indentation {
33
+ padding: 0;
34
+ }
35
+
36
+ .item-row-group > a {
37
+ padding: 0 16px;
38
+ margin: 0;
39
+ border-radius: 0;
40
+ }
41
+
42
+ .item-row-group > a::before {
43
+ content: '';
44
+ position: absolute;
45
+ top: 0;
46
+ left: 0;
47
+ right: 0;
48
+ bottom: 0;
49
+ background-color: var(--light-300);
50
+ opacity: 0.24;
51
+ border-radius: inherit;
52
+ }
53
+
54
+ .item-row {
55
+ display: flex;
56
+ flex-direction: row;
57
+ gap: 8px;
58
+ align-items: center;
59
+
60
+ .label {
61
+ flex: 1;
62
+ &.hidden, &.ellipsis {
63
+ white-space: nowrap;
64
+ overflow: hidden;
65
+ }
66
+ &.ellipsis {
67
+ text-overflow: ellipsis;
68
+ }
69
+ }
70
+ }
71
+
72
+ .item-row-group {
73
+ margin-top: 16px;
74
+ }
75
+
76
+ li a {
77
+ position: relative;
78
+ height: 0;
79
+ overflow: hidden;
80
+ transition: height 0.3s, background-color 0.2s;
81
+ margin-left: ${PADDING - ITEM_PADDING}px;
82
+ padding-left: ${ITEM_PADDING}px;
83
+
84
+ &:hover {
85
+ background-color: ${theme.color.light['500']};
86
+ }
87
+
88
+ &.action {
89
+ &:before {
90
+ content: '';
91
+ position: absolute;
92
+ left: 2px;
93
+ width: 2px;
94
+ height: 0;
95
+ background: inherit;
96
+ transition: height 0.2s;
97
+ }
98
+
99
+ &.active {
100
+
101
+ &:hover {
102
+ background-color: transparent;
103
+ }
104
+
105
+ &:before {
106
+ background: ${theme.color.primary['500']};
107
+ height: 24px;
108
+ }
109
+ }
110
+
111
+ &:not(.active):hover:before {
112
+ background: ${theme.color.light.contrastText};
113
+ height: 24px;
114
+ }
115
+ }
116
+
117
+ .chevron {
118
+ transition: transform 0.3s;
119
+ &:not(.open) {
120
+ transform: rotate(-90deg);
121
+ }
122
+ }
123
+
124
+ .item-row-title {
125
+ opacity: 0.7;
126
+ }
127
+ }
128
+
129
+ &.open {
130
+ visibility: visible;
131
+ transition: unset;
132
+ & > li > a, & > li > .item-row-group > a {
133
+ height: 40px;
134
+ }
135
+ }
136
+
137
+ &:not(.open) &.open > li > a {
138
+ height: 0;
139
+ }
140
+
141
+ &.root {
142
+ margin-bottom: ${PADDING}px;
143
+
144
+ & > li {
145
+ .group-title {
146
+ margin-left: ${PADDING}px;
147
+ margin-bottom: 5px;
148
+ margin-top: 40px;
149
+ display: block;
150
+ }
151
+
152
+ &:first-child {
153
+ .group-title {
154
+ margin-top: 0;
155
+ }
156
+ }
157
+ }
158
+ }
159
+ `
160
+
161
+ export const Title = styled.header`
162
+ display: flex;
163
+ flex-direction: column;
164
+ margin: ${PADDING}px 0 24px ${PADDING}px;
165
+ `
166
+
167
+ export const ActionItem = ({ label, onClick, href, active, icon, badge, overflow = 'wrap' }: MenuAction) => {
168
+ const Link = useAnchorTag()
169
+ const { ref, overflow: textOverflow } = useCheckTextOverflow()
170
+ const isTextLabel = typeof label === 'string'
171
+ return (
172
+ <Link
173
+ href={href}
174
+ onClick={() => {
175
+ if (active) return
176
+ if (onClick) onClick()
177
+ hideOverlayImmediately()
178
+ }}
179
+ className={listToClass(['action', 'item-row', active ? 'active' : undefined])}
180
+ {...(active ? { 'aria-current': 'page' } : undefined)}
181
+ {...(!href ? { 'tabIndex': 0 } : undefined)}
182
+ >
183
+ {icon}
184
+ {isTextLabel ?
185
+ <Text ref={ref} appearance="body2" className={`label ${overflow}`} title={textOverflow ? label : ''}>{label}</Text> :
186
+ label.element}
187
+ {badge}
188
+ </Link>
189
+ )
190
+ }
191
+
192
+ const CollapsibleGroupItem = ({ label, open: initiallyOpened, children, icon, badge, root, overflow = 'wrap' }:
193
+ ItemGroup & { root?: boolean }) => {
194
+ const [open, setOpen] = useState(initiallyOpened ?? children?.some(c => 'active' in c && c.active) ?? false)
195
+ const items = useMemo(() => children?.filter(i => !i.hidden).map(renderOption), [children])
196
+ const id = `menuGroup${label}`
197
+
198
+ return (
199
+ <>
200
+ <a
201
+ onClick={() => setOpen(!open)}
202
+ onKeyDown={e => e.key === 'Enter' && setOpen(!open)}
203
+ className="item-row"
204
+ tabIndex={0}
205
+ aria-controls={id}
206
+ aria-expanded={open}
207
+ >
208
+ {icon}
209
+ <Text appearance={root ? 'overheader2' : 'body2'}
210
+ colorScheme="light.contrastText"
211
+ className={`label ${overflow} ${root ? 'item-row-title' : ''}`}>
212
+ {label}
213
+ </Text>
214
+ {badge}
215
+ <IconBox sx={{ mr: root ? undefined : '5' }}>
216
+ <ChevronDown className={listToClass(['chevron', open ? 'open' : ''])} />
217
+ </IconBox>
218
+ </a>
219
+ <MenuGroup id={id}
220
+ className={`${open ? 'open' : ''} ${root ? 'no-indentation' : ''}`}
221
+ aria-hidden={!open}>{items}</MenuGroup>
222
+ </>
223
+ )
224
+ }
225
+
226
+ const RootGroupItem = (props: ItemGroup) => {
227
+ const items = useMemo(() => props.children?.filter(i => !i.hidden).map(renderOption), [props.children])
228
+
229
+ return <>
230
+ {items.length ? <div className="item-row-group">
231
+ <CollapsibleGroupItem {...props} open={true} root={true} />
232
+ </div> :
233
+ <>
234
+ <div className="item-row">
235
+ {props.icon}
236
+ <Text appearance="overheader2" colorScheme="light.700" className={`group-title label ${props.overflow}`}>{props.label}</Text>
237
+ {props.badge}
238
+ </div>
239
+ <MenuGroup className="open no-indentation">{items}</MenuGroup>
240
+ </>
241
+ }
242
+ </>
243
+ }
244
+
245
+ const GroupItem = ({ root, ...item }: ItemGroup & { root?: boolean }) => (
246
+ root ? <RootGroupItem {...item} /> : <CollapsibleGroupItem {...item} />
247
+ )
248
+
249
+ function renderOption({ root, ...option }: MenuItem & { root?: boolean }) {
250
+ const labelText = typeof option.label === 'string' ? option.label : option.label.id
251
+ return (
252
+ <li key={labelText} role="menuitem" aria-selected={'children' in option ? undefined : option.active}>
253
+ {'children' in option ? <GroupItem root={root} {...option} /> : <ActionItem {...option} />}
254
+ </li >
255
+ )
256
+ }
257
+
258
+ export const MenuContent = ({ pageSelector, goBack, title, subtitle, afterTitle, options = [], loading, error }: MenuSectionContent) => {
259
+ const items = useMemo(() => options.filter(o => !o.hidden).map(o => renderOption({ ...o, root: true })), [options])
260
+
261
+ function renderContent() {
262
+ if (loading) {
263
+ return (
264
+ <Flex justifyContent="center" alignItems="center" flex={1} sx={{ padding: '40px' }}>
265
+ <LoadingCircular />
266
+ </Flex>
267
+ )
268
+ }
269
+ if (error) return <Text colorScheme="danger">{error}</Text>
270
+ return <MenuGroup className="open root no-indentation">{items}</MenuGroup>
271
+ }
272
+
273
+ return (
274
+ <>
275
+ {goBack && (
276
+ <BackLink href={goBack.href} onClick={goBack.onClick}>
277
+ <IconBox colorIcon="inverse.500" size="sm">
278
+ <ArrowLeft />
279
+ </IconBox>
280
+ {typeof goBack?.label === 'string' ?
281
+ <Text appearance="body2" nowrapEllipsis>{goBack.label}</Text> :
282
+ goBack.label.element}
283
+ </BackLink>
284
+ )}
285
+ {title && (
286
+ <Title>
287
+ <Text appearance="overheader1" colorScheme="primary" sx={{ fontSize: '0.75rem', mt: 2, mb: 2 }}>{title}</Text>
288
+ {subtitle && <Text appearance="h5">{subtitle}</Text>}
289
+ </Title>
290
+ )}
291
+ {afterTitle}
292
+ {pageSelector && <PageSelector {...pageSelector} />}
293
+ {renderContent()}
294
+ </>
295
+ )
296
+ }