@stack-spot/portal-layout 0.0.50 → 0.0.52

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