@equinor/fusion-framework-dev-portal 11.0.4 → 11.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.
Files changed (40) hide show
  1. package/dist/main.js +17 -17
  2. package/package.json +43 -40
  3. package/CHANGELOG.md +0 -1117
  4. package/dev-server.ts +0 -109
  5. package/src/AppLoader.tsx +0 -135
  6. package/src/BookmarkSideSheet.tsx +0 -47
  7. package/src/ContextSelector/ContextSelector.tsx +0 -85
  8. package/src/ContextSelector/index.ts +0 -1
  9. package/src/ContextSelector/useContextResolver.ts +0 -337
  10. package/src/EquinorLoader.tsx +0 -33
  11. package/src/ErrorViewer.tsx +0 -26
  12. package/src/FusionLogo.tsx +0 -76
  13. package/src/Header.tsx +0 -94
  14. package/src/HeaderActions.tsx +0 -47
  15. package/src/PersonSideSheet/index.tsx +0 -65
  16. package/src/PersonSideSheet/sheets/FeatureSheetContent.tsx +0 -52
  17. package/src/PersonSideSheet/sheets/FeatureTogglerApp.tsx +0 -39
  18. package/src/PersonSideSheet/sheets/FeatureTogglerPortal.tsx +0 -33
  19. package/src/PersonSideSheet/sheets/LandingSheetContent.tsx +0 -64
  20. package/src/PersonSideSheet/sheets/index.ts +0 -3
  21. package/src/PersonSideSheet/sheets/roles/ClaimableRole.test.tsx +0 -81
  22. package/src/PersonSideSheet/sheets/roles/ClaimableRole.tsx +0 -229
  23. package/src/PersonSideSheet/sheets/roles/RolesApi.test.ts +0 -62
  24. package/src/PersonSideSheet/sheets/roles/RolesApi.ts +0 -112
  25. package/src/PersonSideSheet/sheets/roles/RolesSheetContent.test.tsx +0 -84
  26. package/src/PersonSideSheet/sheets/roles/RolesSheetContent.tsx +0 -207
  27. package/src/PersonSideSheet/sheets/roles/index.ts +0 -1
  28. package/src/PersonSideSheet/sheets/styled.tsx +0 -33
  29. package/src/PersonSideSheet/sheets/types.ts +0 -14
  30. package/src/Router.tsx +0 -83
  31. package/src/configure.ts +0 -186
  32. package/src/get-app-tag-from-url.ts +0 -17
  33. package/src/globals.d.ts +0 -32
  34. package/src/main.tsx +0 -43
  35. package/src/resources/svg.ts +0 -7
  36. package/src/version.ts +0 -2
  37. package/tsconfig.json +0 -79
  38. package/tsconfig.tsbuildinfo +0 -1
  39. package/vite.config.ts +0 -15
  40. package/vitest.config.ts +0 -30
@@ -1,337 +0,0 @@
1
- import { useCallback, useEffect, useMemo, useState } from 'react';
2
- import { useFramework } from '@equinor/fusion-framework-react';
3
- import { useCurrentApp } from '@equinor/fusion-framework-react/app';
4
- import type { AppModule } from '@equinor/fusion-framework-module-app';
5
- import type { NavigationModule } from '@equinor/fusion-framework-module-navigation';
6
- import type {
7
- ContextItem,
8
- ContextModule,
9
- IContextProvider,
10
- } from '@equinor/fusion-framework-module-context';
11
- import { useObservableState, useObservableSubscription } from '@equinor/fusion-observable/react';
12
- import '@equinor/fusion-framework-app';
13
- import { ChipElement } from '@equinor/fusion-wc-chip';
14
- ChipElement;
15
-
16
- import { EMPTY, catchError, lastValueFrom, map, of } from 'rxjs';
17
-
18
- import type {
19
- ContextResult,
20
- ContextResultItem,
21
- ContextResolver,
22
- } from '@equinor/fusion-react-context-selector';
23
- import type { AppModulesInstance } from '@equinor/fusion-framework-app';
24
- import type { QueryClientError } from '@equinor/fusion-query/client';
25
- import type { FusionContextSearchError } from '@equinor/fusion-framework-module-context/errors.js';
26
-
27
- /**
28
- * Capitalizes the first letter of a string and lowercases the rest.
29
- *
30
- * @param string - The input string to capitalize.
31
- * @returns The capitalized string.
32
- */
33
- function capitalizeFirstLetter(string: string): string {
34
- return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
35
- }
36
-
37
- /**
38
- * Converts a {@link ContextItem} graphic field to the shape expected by `ContextResultItem`.
39
- *
40
- * @param graphic - The graphic value from a context item (string, SVG object, or undefined).
41
- * @returns An object with `graphic` and `graphicType` properties, or an empty object.
42
- */
43
- function convertGraphic(
44
- graphic: ContextItem['graphic'],
45
- ): Pick<ContextResultItem, 'graphic' | 'graphicType'> {
46
- // No graphic provided means nothing to render
47
- if (graphic === undefined) {
48
- return {};
49
- }
50
-
51
- // A string graphic is either an inline SVG/HTML markup or an EDS icon name
52
- if (typeof graphic === 'string') {
53
- // EDS icon names aren't part of the ContextResultItem['graphicType'] union upstream, so cast explicitly
54
- const edsGraphicType = 'eds' as unknown as ContextResultItem['graphicType'];
55
- return {
56
- graphicType: graphic.startsWith('<') ? 'inline-html' : edsGraphicType,
57
- graphic: graphic,
58
- };
59
- }
60
-
61
- // A structured SVG graphic object maps directly to the inline-svg type
62
- if (graphic.type === 'svg') {
63
- return {
64
- graphicType: 'inline-svg',
65
- graphic: graphic.content,
66
- };
67
- }
68
-
69
- return {
70
- graphicType: 'inline-html',
71
- graphic: graphic.content,
72
- };
73
- }
74
-
75
- /**
76
- * Converts a {@link ContextItem} meta field to the shape expected by `ContextResultItem`.
77
- *
78
- * @param meta - The meta value from a context item (string, SVG object, or undefined).
79
- * @returns An object with `meta` and `metaType` properties, or an empty object.
80
- */
81
- function convertMeta(meta: ContextItem['meta']): Pick<ContextResultItem, 'metaType' | 'meta'> {
82
- // No meta provided means nothing to render
83
- if (meta === undefined) {
84
- return {};
85
- }
86
-
87
- // A string meta is either an inline SVG/HTML markup or an EDS icon name
88
- if (typeof meta === 'string') {
89
- // EDS icon names aren't part of the ContextResultItem['metaType'] union upstream, so cast explicitly
90
- const edsMetaType = 'eds' as unknown as ContextResultItem['metaType'];
91
- return {
92
- metaType: meta.startsWith('<') ? 'inline-html' : edsMetaType,
93
- meta: meta,
94
- };
95
- }
96
-
97
- // A structured SVG meta object maps directly to the inline-svg type
98
- if (meta.type === 'svg') {
99
- return {
100
- metaType: 'inline-svg',
101
- meta: meta.content,
102
- };
103
- }
104
-
105
- return {
106
- metaType: 'inline-html',
107
- meta: meta.content,
108
- };
109
- }
110
-
111
- /**
112
- * Maps an array of context items to `ContextResult` for the context selector dropdown.
113
- *
114
- * Applies custom rendering for `EquinorTask` (shows inactive state chip) and
115
- * `OrgChart` (shows list icon and inactive state chip) context types.
116
- *
117
- * @param src - Array of context items from the context query.
118
- * @returns Mapped array of `ContextResultItem` objects for the selector UI.
119
- */
120
- const mapper = (src: ContextItem<{ taskState?: string; state?: string }>[]): ContextResult => {
121
- // Map each raw context item to its ContextResultItem shape, applying per-type overrides
122
- return src.map((i) => {
123
- // Layer the base fields with graphic and meta overrides derived from the item
124
- const baseResult = {
125
- id: i.id,
126
- title: i.title,
127
- subTitle: i.subTitle ?? i.type.id,
128
- ...convertGraphic(i.graphic),
129
- ...convertMeta(i.meta),
130
- };
131
-
132
- // Displays the status of the EquinorTask if it is not 'active'
133
- const isEquinorTaskInactive = !!(
134
- i.value.taskState && i.value.taskState.toLowerCase() !== 'active'
135
- );
136
- // Only override the meta chip for EquinorTask items that are inactive
137
- if (i.type.id === 'EquinorTask' && isEquinorTaskInactive) {
138
- baseResult.meta = `<fwc-chip disabled variant="outlined" value="${i.value.taskState}" />`;
139
- baseResult.metaType = 'inline-html';
140
- }
141
-
142
- // OrgChart items always get a fixed icon plus an optional inactive-state chip
143
- if (i.type.id === 'OrgChart') {
144
- // Org charts should always have 'list' icon
145
- baseResult.graphic = 'list';
146
- // 'eds' isn't part of the ContextResultItem['graphicType'] union upstream, so cast explicitly
147
- baseResult.graphicType = 'eds' as unknown as ContextResultItem['graphicType'];
148
-
149
- // Displays the org chart status if it is not 'active'
150
- const isOrgChartInactive = !!(i.value.state && i.value.state.toLowerCase() !== 'active');
151
- // Only override the meta chip when the org chart is inactive
152
- if (isOrgChartInactive) {
153
- baseResult.meta = `<fwc-chip disabled variant="outlined" value="${capitalizeFirstLetter(i.value.state ?? '')}" />`;
154
- baseResult.metaType = 'inline-html';
155
- }
156
- }
157
-
158
- return baseResult;
159
- });
160
- };
161
-
162
- /**
163
- * Creates a single `ContextResultItem` with sensible defaults.
164
- *
165
- * Used to generate placeholder or error entries in the context selector dropdown.
166
- *
167
- * @param props - Partial properties to merge into the default item shape.
168
- * @returns A complete `ContextResultItem` with defaults for `id` and `title`.
169
- */
170
- const singleItem = (props: Partial<ContextResultItem>): ContextResultItem => {
171
- // Layer the caller-supplied props on top of the placeholder defaults
172
- return Object.assign({ id: 'no-such-item', title: 'Change me' }, props);
173
- };
174
-
175
- /**
176
- * Hook that creates a context resolver, tracks the current context provider,
177
- * and provides the currently selected context for the {@link ContextSelector}.
178
- *
179
- * Observes the current application's module instances. When the app exposes a
180
- * context module, the hook wires up a search resolver that queries context items
181
- * and maps results to `ContextResultItem`. It also handles error display and
182
- * minimum query length enforcement.
183
- *
184
- * @see {@link https://equinor.github.io/fusion-react-components/?path=/docs/data-contextselector--component | ContextSelector Storybook}
185
- * @returns An object containing the `resolver` for the selector, the current `provider`, and `currentContext` items.
186
- */
187
- export const useContextResolver = (): {
188
- resolver: ContextResolver | null;
189
- provider: IContextProvider | null;
190
- currentContext: ContextResult;
191
- } => {
192
- /* Framework modules */
193
- const framework = useFramework<[AppModule, NavigationModule]>();
194
-
195
- const { currentApp } = useCurrentApp();
196
-
197
- /** App module collection instance */
198
- const instance$ = useMemo(() => currentApp?.instance$ || EMPTY, [currentApp]);
199
-
200
- /* context provider state */
201
- const [provider, setProvider] = useState<IContextProvider | null>(null);
202
-
203
- /* Current context observable */
204
- const { value: currentContext } = useObservableState(
205
- useMemo(() => provider?.currentContext$ || EMPTY, [provider]),
206
- );
207
-
208
- const preselected: ContextResult = useMemo(() => {
209
- return currentContext ? mapper([currentContext]) : [];
210
- }, [currentContext]);
211
-
212
- /** callback function when current app instance changes */
213
- const onContextProviderChange = useCallback((modules: AppModulesInstance) => {
214
- /** try to get the context module from the app module instance */
215
- const contextProvider = (modules as AppModulesInstance<[ContextModule]>).context;
216
- // Only set a provider when the loaded app actually exposes a context module
217
- if (contextProvider) {
218
- setProvider(contextProvider);
219
- } else {
220
- setProvider(null);
221
- }
222
- }, []);
223
-
224
- /** clear the app provider */
225
- const clearContextProvider = useCallback(() => {
226
- setProvider(null);
227
- }, []);
228
-
229
- /** observe changes to app modules and clear / set the context provider on change */
230
- useObservableSubscription(instance$, onContextProviderChange, clearContextProvider);
231
- useEffect(
232
- () =>
233
- framework.modules.event.addEventListener('onReactAppLoaded', (e) => {
234
- console.debug('useContextResolver::onReactAppLoaded', 'using legacy register hack method');
235
- return onContextProviderChange(e.detail.modules);
236
- }),
237
- [framework, onContextProviderChange],
238
- );
239
-
240
- const processError = useCallback((err: Error): ContextResult => {
241
- // Unwrap query-client errors to get at the underlying cause
242
- if (err.name === 'QueryClientError') {
243
- return processError((err as QueryClientError).cause as Error);
244
- }
245
-
246
- // Render context-search errors with their own title/description
247
- if (err.name === 'FusionContextSearchError') {
248
- const error = err as FusionContextSearchError;
249
- return [
250
- singleItem({
251
- id: error.name,
252
- title: error.title,
253
- subTitle: error.description,
254
- graphic: 'error_outlined',
255
- isDisabled: true,
256
- }),
257
- ];
258
- }
259
-
260
- return [
261
- singleItem({
262
- title: 'Unexpected error occurred',
263
- subTitle: 'Please try again or report the issue in Services@Equinor',
264
- graphic: 'error_outlined',
265
- isDisabled: true,
266
- }),
267
- ];
268
- }, []);
269
-
270
- /**
271
- * set resolver for ContextSelector
272
- * @return ContextResolver
273
- */
274
- const minLength = 2;
275
- const resolver = useMemo(
276
- (): ContextResolver | null =>
277
- provider && {
278
- searchQuery: async (search: string): Promise<ContextResult> => {
279
- // Avoid firing a search query until the minimum character threshold is met
280
- if (search.length < minLength) {
281
- return [
282
- singleItem({
283
- // TODO(#5064): make as enum if used for checks, or type
284
- id: 'min-length',
285
- title: `Type ${minLength - search.length} more chars to search`,
286
- isDisabled: true,
287
- }),
288
- ];
289
- }
290
- try {
291
- const query$ = provider.queryContext(search);
292
- return lastValueFrom(
293
- query$
294
- // Run the raw query results through the mapper, falling back to a no-results placeholder
295
- .pipe(
296
- map(mapper),
297
- map((x) =>
298
- x.length
299
- ? x
300
- : [
301
- singleItem({
302
- // TODO(#5064): make as enum if used for checks, or type
303
- id: 'no-results',
304
- title: 'No results found',
305
- graphic: 'info_circle',
306
- isDisabled: true,
307
- }),
308
- ],
309
- ),
310
- /** handle failures */
311
- catchError((err) => {
312
- console.error(
313
- 'PORTAL::ContextResolver',
314
- `failed to resolve context for query ${search}`,
315
- err,
316
- err.cause,
317
- );
318
-
319
- return of(processError(err));
320
- }),
321
- ),
322
- );
323
- /** this should NEVER happen! */
324
- } catch (e) {
325
- const err = e as Error;
326
- console.error('PORTAL::ContextResolver', `unhandled error for [${search}]`, e);
327
- return processError(err);
328
- }
329
- },
330
- initialResult: preselected,
331
- },
332
- [provider, preselected, processError],
333
- );
334
- return { resolver, provider, currentContext: preselected };
335
- };
336
-
337
- export default useContextResolver;
@@ -1,33 +0,0 @@
1
- import type React from 'react';
2
- import { StarProgress } from '@equinor/fusion-react-progress-indicator';
3
-
4
- /**
5
- * Full-viewport loading indicator displaying the Equinor star spinner.
6
- *
7
- * Used as a fallback while the Fusion Framework or an application is initializing.
8
- *
9
- * @param props.text - Status message displayed below the spinner.
10
- * @param props.children - Optional additional content rendered inside the spinner.
11
- * @returns A centered full-screen loading overlay.
12
- */
13
- export const EquinorLoader = ({
14
- children,
15
- text,
16
- }: React.PropsWithChildren<{ readonly text: string }>): React.ReactElement => {
17
- return (
18
- <div
19
- style={{
20
- display: 'flex',
21
- justifyContent: 'center',
22
- alignItems: 'center',
23
- width: '100vw',
24
- height: '100vh',
25
- overflow: 'hidden',
26
- }}
27
- >
28
- <StarProgress text={text}>{children}</StarProgress>
29
- </div>
30
- );
31
- };
32
-
33
- export default EquinorLoader;
@@ -1,26 +0,0 @@
1
- import { Typography } from '@equinor/eds-core-react';
2
-
3
- /**
4
- * Recursively renders an error and its causal chain.
5
- *
6
- * Displays the error message and stack trace for each error in the `cause`
7
- * chain, providing full visibility into nested failures during app loading.
8
- *
9
- * @param props.error - The error to display, including any nested `cause` errors.
10
- * @returns A bordered section showing the error message, stack trace, and any nested causes.
11
- */
12
- export const ErrorViewer = ({ error }: { readonly error: Error }) => {
13
- return (
14
- <>
15
- <div style={{ marginTop: 20, border: '1px solid' }}>
16
- <Typography variant="h4" color="warning">
17
- {error.message}
18
- </Typography>
19
- <section style={{ padding: 10 }}>{error.stack && <pre>{error.stack}</pre>}</section>
20
- </div>
21
- {error.cause && <ErrorViewer error={error.cause as Error} />}
22
- </>
23
- );
24
- };
25
-
26
- export default ErrorViewer;
@@ -1,76 +0,0 @@
1
- import { useId } from 'react';
2
- import type { SVGProps } from 'react';
3
-
4
- /** Props for the {@link FusionLogo} component. */
5
- type FusionLogoProps = Omit<SVGProps<SVGSVGElement>, 'viewBox'> & {
6
- /** Uniform scale multiplier applied via CSS transform. Defaults to `1`. */
7
- readonly scale?: number;
8
- };
9
-
10
- /**
11
- * Inline SVG rendering of the Fusion logo.
12
- *
13
- * Uses unique gradient IDs per instance so multiple logos can coexist on the
14
- * same page without gradient collisions.
15
- *
16
- * @param props.scale - Scale multiplier for the logo size.
17
- * @param props.style - Additional inline styles merged with the computed transform.
18
- * @returns An inline SVG element sized to `1em` height.
19
- */
20
- export const FusionLogo = ({ scale = 1, style }: FusionLogoProps) => {
21
- const paint0Id = useId();
22
- const paint1Id = useId();
23
-
24
- return (
25
- <svg viewBox="0 0 50 35" style={{ height: '1em', ...style, transform: `scale(${scale})` }}>
26
- <title>Fusion Logo</title>
27
- <path
28
- d="M0 2V23.1776L7.05405 16.1235V7.05405H16.1235L23.1776 0H2C0.895431 0 0 0.89543 0 2Z"
29
- transform="translate(50 17.5) scale(0.92727 1.06779) rotate(135)"
30
- fill={`url(#${paint0Id})`}
31
- />
32
- <path
33
- d="M0 2V23.1776L7.05405 16.1235V7.05405H16.1235L23.1776 0H2C0.895431 0 0 0.89543 0 2Z"
34
- transform="translate(0 17.5) scale(0.92727 1.06779) rotate(-45)"
35
- fill={`url(#${paint1Id})`}
36
- />
37
- <path
38
- d="M9.61965 36.6972L2.60087 29.6784L1.96135 22.3809L8.42623 22.9069L9.61965 36.6972Z"
39
- transform="translate(33.8887 34.9863) scale(0.92727 -1.06779) rotate(45)"
40
- fill="#990025"
41
- />
42
- <path
43
- d="M7.05434 7.05434L0 0L1.21096 13.8183L7.68846 14.3818L7.05434 7.05434Z"
44
- transform="translate(33.8887 34.9863) scale(0.92727 -1.06779) rotate(45)"
45
- fill="#990025"
46
- />
47
- <path
48
- d="M0 0L2.49398 29.5715L9.61965 36.6972L7.01878 7.01878L0 0Z"
49
- transform="translate(33.8887 0.015625) scale(0.92727 1.06779) rotate(45)"
50
- fill="#FF1243"
51
- />
52
- <defs>
53
- <linearGradient
54
- id={paint0Id}
55
- x2="1"
56
- gradientUnits="userSpaceOnUse"
57
- gradientTransform="matrix(-13.5478 9.01983 -12.9578 -13.5478 18.0677 6.77391)"
58
- >
59
- <stop offset="0.508287" stopColor="#DC002E" />
60
- <stop offset="0.508387" stopColor="#FF1243" />
61
- </linearGradient>
62
- <linearGradient
63
- id={paint1Id}
64
- x2="1"
65
- gradientUnits="userSpaceOnUse"
66
- gradientTransform="matrix(-13.5478 9.01983 -12.9578 -13.5478 18.0677 6.77391)"
67
- >
68
- <stop offset="0.508287" stopColor="#DC002E" />
69
- <stop offset="0.508387" stopColor="#FF1243" />
70
- </linearGradient>
71
- </defs>
72
- </svg>
73
- );
74
- };
75
-
76
- export default FusionLogo;
package/src/Header.tsx DELETED
@@ -1,94 +0,0 @@
1
- import { useCallback, useId, useState } from 'react';
2
- import { ContextSelector } from './ContextSelector';
3
- import { FusionLogo } from './FusionLogo';
4
-
5
- import styled from 'styled-components';
6
- import { add, menu, tag } from '@equinor/eds-icons';
7
- import { Icon, TopBar } from '@equinor/eds-core-react';
8
- Icon.add({ menu, add, tag });
9
-
10
- import { useCurrentUser } from '@equinor/fusion-framework-react/hooks';
11
- import { useCurrentApp, useCurrentAppModule } from '@equinor/fusion-framework-react/app';
12
-
13
- import type { BookmarkModule } from '@equinor/fusion-framework-react-module-bookmark';
14
-
15
- import { BookmarkProvider } from '@equinor/fusion-framework-react-components-bookmark';
16
-
17
- import PersonAvatarElement from '@equinor/fusion-wc-person/avatar';
18
- PersonAvatarElement; // Register the custom element - prevent tree-shaking
19
-
20
- import { PersonSideSheet } from './PersonSideSheet';
21
-
22
- import { BookmarkSideSheet } from './BookmarkSideSheet';
23
-
24
- import { HeaderActions } from './HeaderActions';
25
-
26
- const Styled = {
27
- Title: styled.div`
28
- display: flex;
29
- align-items: center;
30
- gap: 0.75rem;
31
- font-size: 1rem;
32
- font-weight: 500;
33
- `,
34
- };
35
-
36
- /**
37
- * Portal top bar header containing the Fusion logo, context selector, and action buttons.
38
- *
39
- * Composes the bookmark provider with the current app and user so bookmark
40
- * and person side sheets can operate in context. Provides the sticky top bar
41
- * layout used across all portal pages.
42
- */
43
- export const Header = () => {
44
- const currentUser = useCurrentUser();
45
- const topBarId = useId();
46
- const [isPersonSheetOpen, setIsPersonSheetOpen] = useState(false);
47
-
48
- const [isBookmarkOpen, setIsBookmarkOpen] = useState(false);
49
- const onBookmarkClose = useCallback(() => {
50
- setIsBookmarkOpen(false);
51
- }, []);
52
-
53
- const { currentApp } = useCurrentApp();
54
-
55
- const { module: bookmarkProvider } = useCurrentAppModule<BookmarkModule>('bookmark');
56
-
57
- return (
58
- <BookmarkProvider
59
- provider={bookmarkProvider ?? undefined}
60
- currentApp={
61
- currentApp
62
- ? { appKey: currentApp.appKey, name: currentApp.manifest?.displayName }
63
- : undefined
64
- }
65
- currentUser={
66
- currentUser ? { id: currentUser.localAccountId, name: currentUser.name } : undefined
67
- }
68
- >
69
- <TopBar id={topBarId} sticky={false} style={{ padding: '0 1em', height: 48 }}>
70
- <TopBar.Header>
71
- <Styled.Title>
72
- <FusionLogo />
73
- <span>Fusion Framework CLI</span>
74
- </Styled.Title>
75
- </TopBar.Header>
76
- <HeaderActions
77
- userAzureId={currentUser?.localAccountId}
78
- toggleBookmark={setIsBookmarkOpen}
79
- togglePerson={setIsPersonSheetOpen}
80
- />
81
- <TopBar.CustomContent>
82
- <ContextSelector />
83
- </TopBar.CustomContent>
84
- {/* since buttons are 40px but have 48px click bounds */}
85
- </TopBar>
86
- <BookmarkSideSheet isOpen={isBookmarkOpen} onClose={onBookmarkClose} />
87
- <PersonSideSheet
88
- azureId={currentUser?.localAccountId}
89
- isOpen={isPersonSheetOpen}
90
- onClose={() => setIsPersonSheetOpen(!isPersonSheetOpen)}
91
- />
92
- </BookmarkProvider>
93
- );
94
- };
@@ -1,47 +0,0 @@
1
- import { tag } from '@equinor/eds-icons';
2
- import { Button, Icon, TopBar } from '@equinor/eds-core-react';
3
-
4
- import PersonAvatarElement from '@equinor/fusion-wc-person/avatar';
5
- PersonAvatarElement;
6
-
7
- import { useBookmarkComponentContext } from '@equinor/fusion-framework-react-components-bookmark';
8
-
9
- /** Props for the {@link HeaderActions} component. */
10
- interface HeaderActionProps {
11
- /** Azure AD object ID of the current user, used for the person avatar. */
12
- readonly userAzureId?: string;
13
- /** Toggle callback for the bookmark side sheet open/close state. */
14
- readonly toggleBookmark: (open: (status: boolean) => boolean) => void;
15
- /** Toggle callback for the person settings side sheet open/close state. */
16
- readonly togglePerson: (open: (status: boolean) => boolean) => void;
17
- }
18
-
19
- /**
20
- * Action buttons displayed in the portal top bar header.
21
- *
22
- * Renders a bookmark toggle button (disabled when no bookmark provider is
23
- * available) and a person-avatar button that opens the user settings sheet.
24
- *
25
- * @param props - {@link HeaderActionProps}
26
- */
27
- export const HeaderActions = (props: HeaderActionProps) => {
28
- const { toggleBookmark, togglePerson, userAzureId } = props;
29
-
30
- const bookmarkContext = useBookmarkComponentContext();
31
-
32
- return (
33
- <TopBar.Actions style={{ minWidth: 48, minHeight: 48 }}>
34
- <Button
35
- onClick={() => toggleBookmark((x) => !x)}
36
- variant="ghost_icon"
37
- disabled={!bookmarkContext.provider}
38
- title={bookmarkContext.provider ? 'Bookmarks' : 'Bookmarks not available, enable in app'}
39
- >
40
- <Icon data={tag} />
41
- </Button>
42
- <Button onClick={() => togglePerson((x) => !x)} variant="ghost_icon">
43
- <fwc-person-avatar size="small" azureId={userAzureId} clickable={false} />
44
- </Button>
45
- </TopBar.Actions>
46
- );
47
- };
@@ -1,65 +0,0 @@
1
- import { useCallback, useMemo, useState } from 'react';
2
- import { SideSheet } from '@equinor/fusion-react-side-sheet';
3
- import PersonListItem from '@equinor/fusion-wc-person/list-item';
4
- PersonListItem;
5
-
6
- import { Divider } from '@equinor/eds-core-react';
7
-
8
- import { LandingSheetContent, FeatureSheetContent, RolesSheetContent } from './sheets';
9
-
10
- /** Props for the {@link PersonSideSheet} component. */
11
- type PersonSideSheetProps = {
12
- /** Azure AD object ID of the user to display in the side sheet. */
13
- readonly azureId?: string;
14
- /** Whether the side sheet is currently visible. */
15
- readonly isOpen: boolean;
16
- /** Callback invoked when the user dismisses the side sheet. */
17
- onClose(): void;
18
- };
19
-
20
- /**
21
- * Side sheet overlay that displays user settings and feature toggles.
22
- *
23
- * Contains a person list item for the current user and navigable sub-sheets
24
- * for viewing and toggling application and portal feature flags.
25
- *
26
- * @param props - {@link PersonSideSheetProps}
27
- */
28
- export const PersonSideSheet = ({ azureId, isOpen, onClose }: PersonSideSheetProps) => {
29
- const [currentSheet, setCurrentSheet] = useState<string>('landing');
30
-
31
- const Component = useMemo(() => {
32
- // Pick the sub-sheet component matching the currently navigated-to sheet
33
- switch (currentSheet) {
34
- case 'features':
35
- return FeatureSheetContent;
36
- case 'roles':
37
- return RolesSheetContent;
38
- default:
39
- return LandingSheetContent;
40
- }
41
- }, [currentSheet]);
42
-
43
- const navigateCallback = useCallback((sheet: string) => {
44
- setCurrentSheet(sheet ?? 'landing');
45
- }, []);
46
-
47
- return (
48
- <SideSheet isOpen={isOpen} onClose={onClose} isDismissable={true}>
49
- <SideSheet.Title title="User settings" />
50
- <SideSheet.SubTitle subTitle={'Settings for your user in Fusion portal'} />
51
- <SideSheet.Actions />
52
- <SideSheet.Content>
53
- <section style={{ paddingLeft: '0.5em' }}>
54
- <div>
55
- <fwc-person-list-item azureId={azureId} />
56
- </div>
57
- <Divider />
58
- <Component azureId={azureId} sheet={currentSheet} navigate={navigateCallback} />
59
- </section>
60
- </SideSheet.Content>
61
- </SideSheet>
62
- );
63
- };
64
-
65
- export default PersonSideSheet;