@xh/hoist 80.0.0-SNAPSHOT.1767982629403 → 80.0.0-SNAPSHOT.1768251023007
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -3
- package/build/types/cmp/tab/TabContainerModel.d.ts +10 -5
- package/build/types/core/XH.d.ts +3 -1
- package/build/types/desktop/cmp/button/AppMenuButton.d.ts +10 -1
- package/build/types/mobile/cmp/header/AppMenuButton.d.ts +10 -1
- package/build/types/svc/IdentityService.d.ts +4 -2
- package/cmp/tab/TabContainerModel.ts +9 -5
- package/core/XH.ts +9 -4
- package/desktop/cmp/appbar/AppBar.scss +24 -5
- package/desktop/cmp/button/AppMenuButton.ts +30 -2
- package/mobile/cmp/header/AppBar.scss +11 -0
- package/mobile/cmp/header/AppMenuButton.ts +29 -1
- package/package.json +1 -1
- package/styles/vars.scss +2 -1
- package/svc/IdentityService.ts +14 -2
- package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -11,9 +11,15 @@
|
|
|
11
11
|
so there is no deprecated alias. Any app usages should swap to `XH.appLoadObserver`.
|
|
12
12
|
* Removed additional references to deprecated `loadModel` within Hoist itself.
|
|
13
13
|
* Removed the following instance getters - use new static typeguards instead:
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
* `Store.isStore`
|
|
15
|
+
* `View.isView`
|
|
16
|
+
* `Filter.isFilter`
|
|
17
|
+
|
|
18
|
+
### 🎁 New Features
|
|
19
|
+
|
|
20
|
+
* Added new `AppMenuButton.renderWithUserProfile` prop as a built-in alternative to the default
|
|
21
|
+
hamburger menu. Set to `true` to render the current user's initials instead or provide a function
|
|
22
|
+
to render a custom element for the user.
|
|
17
23
|
|
|
18
24
|
### ⚙️ Typescript API Adjustments
|
|
19
25
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { TabSwitcherConfig, IDynamicTabSwitcherModel } from '@xh/hoist/cmp/tab/Types';
|
|
2
|
-
import { HoistModel,
|
|
1
|
+
import type { TabSwitcherConfig, IDynamicTabSwitcherModel, TabContainerModelPersistOptions } from '@xh/hoist/cmp/tab/Types';
|
|
2
|
+
import { HoistModel, RefreshContextModel, RefreshMode, RenderMode } from '@xh/hoist/core';
|
|
3
3
|
import { ReactNode } from 'react';
|
|
4
4
|
import { TabConfig, TabModel } from './TabModel';
|
|
5
5
|
export interface TabContainerConfig {
|
|
@@ -12,7 +12,7 @@ export interface TabContainerConfig {
|
|
|
12
12
|
defaultTabId?: string;
|
|
13
13
|
/**
|
|
14
14
|
* Base route name for this container. If set, this container will be route-enabled, with the
|
|
15
|
-
* route for each tab being "[route]/[tab.id]".
|
|
15
|
+
* route for each tab being "[route]/[tab.id]".
|
|
16
16
|
*/
|
|
17
17
|
route?: string;
|
|
18
18
|
/**
|
|
@@ -35,8 +35,13 @@ export interface TabContainerConfig {
|
|
|
35
35
|
* See enum for description of supported modes.
|
|
36
36
|
*/
|
|
37
37
|
refreshMode?: RefreshMode;
|
|
38
|
-
/**
|
|
39
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Options governing persistence. Tab containers can persist their last-active tab as well
|
|
40
|
+
* as favorite tabs for the dynamic `switcher` option. Note that this must be left unset or
|
|
41
|
+
* its nested `persistActiveTabId` option must be set to false if also using `route`, to avoid
|
|
42
|
+
* a possible conflict between an initial route and persisted last active tab.
|
|
43
|
+
*/
|
|
44
|
+
persistWith?: TabContainerModelPersistOptions;
|
|
40
45
|
/**
|
|
41
46
|
* Placeholder to display if no tabs are provided or all tabs have been removed via
|
|
42
47
|
* their `omit` config.
|
package/build/types/core/XH.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RouterModel } from '@xh/hoist/appcontainer/RouterModel';
|
|
2
2
|
import { HoistAuthModel } from '@xh/hoist/core/HoistAuthModel';
|
|
3
3
|
import { Store } from '@xh/hoist/data';
|
|
4
|
-
import { AlertBannerService, AutoRefreshService, ChangelogService, ConfigService, EnvironmentService, FetchOptions, FetchService, GridAutosizeService, GridExportService, IdentityService, IdleService, InspectorService, JsonBlobService, LocalStorageService, PrefService, SessionStorageService, TrackService, WebSocketService
|
|
4
|
+
import { AlertBannerService, AutoRefreshService, ChangelogService, ClientHealthService, ConfigService, EnvironmentService, FetchOptions, FetchService, GridAutosizeService, GridExportService, IdentityService, IdleService, InspectorService, JsonBlobService, LocalStorageService, PrefService, SessionStorageService, TrackService, WebSocketService } from '@xh/hoist/svc';
|
|
5
5
|
import { LogLevel } from '@xh/hoist/utils/js';
|
|
6
6
|
import { Router, State } from 'router5';
|
|
7
7
|
import { CancelFn } from 'router5/types/types/base';
|
|
@@ -175,6 +175,8 @@ export declare class XHApi {
|
|
|
175
175
|
* @see IdentityService.username
|
|
176
176
|
*/
|
|
177
177
|
getUsername(): string;
|
|
178
|
+
/** @returns the current acting user's initials. */
|
|
179
|
+
getUserInitials(): string;
|
|
178
180
|
/**
|
|
179
181
|
* Logout the current user.
|
|
180
182
|
* @see HoistAuthModel.logoutAsync
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { MenuItemLike } from '@xh/hoist/core';
|
|
1
|
+
import { HoistUser, MenuItemLike } from '@xh/hoist/core';
|
|
2
2
|
import { ButtonProps } from '@xh/hoist/desktop/cmp/button';
|
|
3
3
|
import '@xh/hoist/desktop/register';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
4
5
|
export interface AppMenuButtonProps extends ButtonProps {
|
|
5
6
|
/**
|
|
6
7
|
* Array of extra menu items. Can contain:
|
|
@@ -31,5 +32,13 @@ export interface AppMenuButtonProps extends ButtonProps {
|
|
|
31
32
|
hideOptionsItem?: boolean;
|
|
32
33
|
/** True to hide the Theme Toggle button. */
|
|
33
34
|
hideThemeItem?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Replace the default hamburger icon with a user profile representation. Set to true to render
|
|
37
|
+
* the user's initials from their `HoistUser.displayName`. Alternately, provide a custom
|
|
38
|
+
* function to render an alternate compact string or element for the current user.
|
|
39
|
+
*/
|
|
40
|
+
renderWithUserProfile?: boolean | RenderWithUserProfileCustomFn;
|
|
34
41
|
}
|
|
42
|
+
type RenderWithUserProfileCustomFn = (user: HoistUser) => ReactNode;
|
|
35
43
|
export declare const AppMenuButton: import("react").FC<AppMenuButtonProps>, appMenuButton: import("@xh/hoist/core").ElementFactory<AppMenuButtonProps>;
|
|
44
|
+
export {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { MenuItemLike } from '@xh/hoist/core';
|
|
1
|
+
import { HoistUser, MenuItemLike } from '@xh/hoist/core';
|
|
2
2
|
import { MenuButtonProps } from '@xh/hoist/mobile/cmp/menu';
|
|
3
3
|
import '@xh/hoist/mobile/register';
|
|
4
|
+
import { ReactNode } from 'react';
|
|
4
5
|
export interface AppMenuButtonProps extends MenuButtonProps {
|
|
5
6
|
/** Array of app-specific MenuItems or configs to create them. */
|
|
6
7
|
extraItems?: MenuItemLike[];
|
|
@@ -21,7 +22,14 @@ export interface AppMenuButtonProps extends MenuButtonProps {
|
|
|
21
22
|
hideThemeItem?: boolean;
|
|
22
23
|
/** True to hide the About button */
|
|
23
24
|
hideAboutItem?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Replace the default hamburger icon with a user profile representation. Set to true to render
|
|
27
|
+
* the user's initials from their `HoistUser.displayName`. Alternately, provide a custom
|
|
28
|
+
* function to render an alternate compact string or element for the current user.
|
|
29
|
+
*/
|
|
30
|
+
renderWithUserProfile?: boolean | RenderWithUserProfileCustomFn;
|
|
24
31
|
}
|
|
32
|
+
type RenderWithUserProfileCustomFn = (user: HoistUser) => ReactNode;
|
|
25
33
|
/**
|
|
26
34
|
* A top-level application drop down menu, which installs a standard set of menu items for common
|
|
27
35
|
* application actions. Application specific items can be displayed before these standard items.
|
|
@@ -30,3 +38,4 @@ export interface AppMenuButtonProps extends MenuButtonProps {
|
|
|
30
38
|
* or they can each be explicitly hidden.
|
|
31
39
|
*/
|
|
32
40
|
export declare const AppMenuButton: import("react").FC<AppMenuButtonProps>, appMenuButton: import("@xh/hoist/core").ElementFactory<AppMenuButtonProps>;
|
|
41
|
+
export {};
|
|
@@ -11,10 +11,12 @@ export declare class IdentityService extends HoistService {
|
|
|
11
11
|
private _authUser;
|
|
12
12
|
private _apparentUser;
|
|
13
13
|
initAsync(): Promise<void>;
|
|
14
|
-
/**
|
|
14
|
+
/** @returns current acting user (see authUser for notes on impersonation) */
|
|
15
15
|
get user(): HoistUser;
|
|
16
|
-
/**
|
|
16
|
+
/** @returns current acting user's username. */
|
|
17
17
|
get username(): string;
|
|
18
|
+
/** @returns current acting user's initials, based on displayName. */
|
|
19
|
+
get userInitials(): string;
|
|
18
20
|
/**
|
|
19
21
|
* Actual user who authenticated to the web application.
|
|
20
22
|
* This will be the same as the user except when an administrator is impersonation another
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import type {
|
|
8
8
|
TabSwitcherConfig,
|
|
9
9
|
IDynamicTabSwitcherModel,
|
|
10
10
|
TabContainerModelPersistOptions
|
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
managed,
|
|
15
15
|
PersistableState,
|
|
16
16
|
PersistenceProvider,
|
|
17
|
-
PersistOptions,
|
|
18
17
|
RefreshContextModel,
|
|
19
18
|
RefreshMode,
|
|
20
19
|
RenderMode,
|
|
@@ -41,7 +40,7 @@ export interface TabContainerConfig {
|
|
|
41
40
|
|
|
42
41
|
/**
|
|
43
42
|
* Base route name for this container. If set, this container will be route-enabled, with the
|
|
44
|
-
* route for each tab being "[route]/[tab.id]".
|
|
43
|
+
* route for each tab being "[route]/[tab.id]".
|
|
45
44
|
*/
|
|
46
45
|
route?: string;
|
|
47
46
|
|
|
@@ -69,8 +68,13 @@ export interface TabContainerConfig {
|
|
|
69
68
|
*/
|
|
70
69
|
refreshMode?: RefreshMode;
|
|
71
70
|
|
|
72
|
-
/**
|
|
73
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Options governing persistence. Tab containers can persist their last-active tab as well
|
|
73
|
+
* as favorite tabs for the dynamic `switcher` option. Note that this must be left unset or
|
|
74
|
+
* its nested `persistActiveTabId` option must be set to false if also using `route`, to avoid
|
|
75
|
+
* a possible conflict between an initial route and persisted last active tab.
|
|
76
|
+
*/
|
|
77
|
+
persistWith?: TabContainerModelPersistOptions;
|
|
74
78
|
|
|
75
79
|
/**
|
|
76
80
|
* Placeholder to display if no tabs are provided or all tabs have been removed via
|
package/core/XH.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
AlertBannerService,
|
|
15
15
|
AutoRefreshService,
|
|
16
16
|
ChangelogService,
|
|
17
|
+
ClientHealthService,
|
|
17
18
|
ConfigService,
|
|
18
19
|
EnvironmentService,
|
|
19
20
|
FetchOptions,
|
|
@@ -28,13 +29,13 @@ import {
|
|
|
28
29
|
PrefService,
|
|
29
30
|
SessionStorageService,
|
|
30
31
|
TrackService,
|
|
31
|
-
WebSocketService
|
|
32
|
-
ClientHealthService
|
|
32
|
+
WebSocketService
|
|
33
33
|
} from '@xh/hoist/svc';
|
|
34
|
-
import {
|
|
34
|
+
import {apiDeprecated, getLogLevel, LogLevel, setLogLevel} from '@xh/hoist/utils/js';
|
|
35
35
|
import {camelCase, flatten, isString, uniqueId} from 'lodash';
|
|
36
36
|
import {Router, State} from 'router5';
|
|
37
37
|
import {CancelFn} from 'router5/types/types/base';
|
|
38
|
+
import ShortUniqueId from 'short-unique-id';
|
|
38
39
|
import {SetOptional} from 'type-fest';
|
|
39
40
|
import {AppContainerModel} from '../appcontainer/AppContainerModel';
|
|
40
41
|
import {BannerModel} from '../appcontainer/BannerModel';
|
|
@@ -66,7 +67,6 @@ import {
|
|
|
66
67
|
import {installServicesAsync} from './impl/InstallServices';
|
|
67
68
|
import {instanceManager} from './impl/InstanceManager';
|
|
68
69
|
import {HoistModel, ModelSelector, RefreshContextModel} from './model';
|
|
69
|
-
import ShortUniqueId from 'short-unique-id';
|
|
70
70
|
|
|
71
71
|
export const MIN_HOIST_CORE_VERSION = '31.2';
|
|
72
72
|
|
|
@@ -360,6 +360,11 @@ export class XHApi {
|
|
|
360
360
|
return this.identityService?.username ?? null;
|
|
361
361
|
}
|
|
362
362
|
|
|
363
|
+
/** @returns the current acting user's initials. */
|
|
364
|
+
getUserInitials(): string {
|
|
365
|
+
return this.identityService?.userInitials ?? null;
|
|
366
|
+
}
|
|
367
|
+
|
|
363
368
|
/**
|
|
364
369
|
* Logout the current user.
|
|
365
370
|
* @see HoistAuthModel.logoutAsync
|
|
@@ -6,11 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
.xh-appbar {
|
|
9
|
-
// Menu button might be on left or right of appBar - add right margin when on left only.
|
|
10
|
-
.xh-app-menu-button:first-child {
|
|
11
|
-
margin-right: var(--xh-pad-px);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
9
|
.xh-appbar-icon {
|
|
15
10
|
margin-right: var(--xh-pad-px);
|
|
16
11
|
}
|
|
@@ -20,6 +15,30 @@
|
|
|
20
15
|
font-size: var(--xh-appbar-title-font-size-px);
|
|
21
16
|
margin-right: var(--xh-pad-px);
|
|
22
17
|
}
|
|
18
|
+
|
|
19
|
+
.xh-app-menu-button {
|
|
20
|
+
// Menu button might be on left or right of appBar - add right margin when on left only.
|
|
21
|
+
&:first-child {
|
|
22
|
+
margin-right: var(--xh-pad-px);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&__user-profile {
|
|
26
|
+
border-radius: 50%;
|
|
27
|
+
border: var(--xh-border-solid);
|
|
28
|
+
cursor: pointer;
|
|
29
|
+
height: 32px;
|
|
30
|
+
line-height: 31px;
|
|
31
|
+
text-align: center;
|
|
32
|
+
width: 32px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Trigger profile-specific hover styles on parent button hover
|
|
36
|
+
&:hover .xh-app-menu-button__user-profile {
|
|
37
|
+
color: var(--xh-appbar-user-profile-hover-color);
|
|
38
|
+
border-color: var(--xh-appbar-user-profile-hover-color);
|
|
39
|
+
background-color: transparent;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
23
42
|
}
|
|
24
43
|
|
|
25
44
|
//------------------------
|
|
@@ -4,13 +4,16 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import {div} from '@xh/hoist/cmp/layout';
|
|
8
|
+
import {hoistCmp, HoistProps, HoistUser, MenuItemLike, XH} from '@xh/hoist/core';
|
|
8
9
|
import {ButtonProps, button} from '@xh/hoist/desktop/cmp/button';
|
|
9
10
|
import '@xh/hoist/desktop/register';
|
|
10
11
|
import {Icon} from '@xh/hoist/icon';
|
|
11
12
|
import {menu, popover} from '@xh/hoist/kit/blueprint';
|
|
12
13
|
import {parseMenuItems} from '@xh/hoist/utils/impl';
|
|
13
14
|
import {withDefault} from '@xh/hoist/utils/js';
|
|
15
|
+
import {isFunction} from 'lodash';
|
|
16
|
+
import {ReactNode} from 'react';
|
|
14
17
|
|
|
15
18
|
export interface AppMenuButtonProps extends ButtonProps {
|
|
16
19
|
/**
|
|
@@ -50,8 +53,17 @@ export interface AppMenuButtonProps extends ButtonProps {
|
|
|
50
53
|
|
|
51
54
|
/** True to hide the Theme Toggle button. */
|
|
52
55
|
hideThemeItem?: boolean;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Replace the default hamburger icon with a user profile representation. Set to true to render
|
|
59
|
+
* the user's initials from their `HoistUser.displayName`. Alternately, provide a custom
|
|
60
|
+
* function to render an alternate compact string or element for the current user.
|
|
61
|
+
*/
|
|
62
|
+
renderWithUserProfile?: boolean | RenderWithUserProfileCustomFn;
|
|
53
63
|
}
|
|
54
64
|
|
|
65
|
+
type RenderWithUserProfileCustomFn = (user: HoistUser) => ReactNode;
|
|
66
|
+
|
|
55
67
|
export const [AppMenuButton, appMenuButton] = hoistCmp.withFactory<AppMenuButtonProps>({
|
|
56
68
|
displayName: 'AppMenuButton',
|
|
57
69
|
model: false,
|
|
@@ -70,6 +82,7 @@ export const [AppMenuButton, appMenuButton] = hoistCmp.withFactory<AppMenuButton
|
|
|
70
82
|
hideOptionsItem,
|
|
71
83
|
hideThemeItem,
|
|
72
84
|
disabled,
|
|
85
|
+
renderWithUserProfile,
|
|
73
86
|
...rest
|
|
74
87
|
} = props;
|
|
75
88
|
|
|
@@ -79,7 +92,8 @@ export const [AppMenuButton, appMenuButton] = hoistCmp.withFactory<AppMenuButton
|
|
|
79
92
|
position: 'bottom-right',
|
|
80
93
|
minimal: true,
|
|
81
94
|
item: button({
|
|
82
|
-
icon: Icon.menu(),
|
|
95
|
+
icon: renderWithUserProfile ? null : Icon.menu(),
|
|
96
|
+
text: renderWithUserProfile ? userProfile({renderWithUserProfile}) : null,
|
|
83
97
|
disabled,
|
|
84
98
|
...rest
|
|
85
99
|
}),
|
|
@@ -95,6 +109,20 @@ export const [AppMenuButton, appMenuButton] = hoistCmp.withFactory<AppMenuButton
|
|
|
95
109
|
//---------------------------
|
|
96
110
|
// Implementation
|
|
97
111
|
//---------------------------
|
|
112
|
+
const userProfile = hoistCmp.factory<
|
|
113
|
+
HoistProps & {renderWithUserProfile: true | RenderWithUserProfileCustomFn}
|
|
114
|
+
>({
|
|
115
|
+
model: false,
|
|
116
|
+
render({renderWithUserProfile}) {
|
|
117
|
+
return div({
|
|
118
|
+
className: 'xh-app-menu-button__user-profile',
|
|
119
|
+
item: isFunction(renderWithUserProfile)
|
|
120
|
+
? renderWithUserProfile(XH.getUser())
|
|
121
|
+
: XH.getUserInitials()
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
98
126
|
function buildMenuItems(props: AppMenuButtonProps) {
|
|
99
127
|
let {
|
|
100
128
|
hideAboutItem,
|
|
@@ -18,6 +18,17 @@
|
|
|
18
18
|
display: flex;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
.xh-app-menu-button__user-profile {
|
|
22
|
+
border-radius: 50%;
|
|
23
|
+
border: 1px solid var(--xh-appbar-title-color);
|
|
24
|
+
cursor: pointer;
|
|
25
|
+
height: 28px;
|
|
26
|
+
font-size: var(--xh-font-size-small-em);
|
|
27
|
+
line-height: 27px;
|
|
28
|
+
text-align: center;
|
|
29
|
+
width: 28px;
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
.xh-button {
|
|
22
33
|
background: transparent !important;
|
|
23
34
|
color: var(--xh-appbar-title-color) !important;
|
|
@@ -4,11 +4,14 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2026 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {
|
|
7
|
+
import {div} from '@xh/hoist/cmp/layout';
|
|
8
|
+
import {hoistCmp, HoistProps, HoistUser, MenuItemLike, XH} from '@xh/hoist/core';
|
|
8
9
|
import {Icon} from '@xh/hoist/icon';
|
|
9
10
|
import {menuButton, MenuButtonProps} from '@xh/hoist/mobile/cmp/menu';
|
|
10
11
|
import '@xh/hoist/mobile/register';
|
|
11
12
|
import {withDefault} from '@xh/hoist/utils/js';
|
|
13
|
+
import {isFunction} from 'lodash';
|
|
14
|
+
import {ReactNode} from 'react';
|
|
12
15
|
|
|
13
16
|
export interface AppMenuButtonProps extends MenuButtonProps {
|
|
14
17
|
/** Array of app-specific MenuItems or configs to create them. */
|
|
@@ -37,8 +40,17 @@ export interface AppMenuButtonProps extends MenuButtonProps {
|
|
|
37
40
|
|
|
38
41
|
/** True to hide the About button */
|
|
39
42
|
hideAboutItem?: boolean;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Replace the default hamburger icon with a user profile representation. Set to true to render
|
|
46
|
+
* the user's initials from their `HoistUser.displayName`. Alternately, provide a custom
|
|
47
|
+
* function to render an alternate compact string or element for the current user.
|
|
48
|
+
*/
|
|
49
|
+
renderWithUserProfile?: boolean | RenderWithUserProfileCustomFn;
|
|
40
50
|
}
|
|
41
51
|
|
|
52
|
+
type RenderWithUserProfileCustomFn = (user: HoistUser) => ReactNode;
|
|
53
|
+
|
|
42
54
|
/**
|
|
43
55
|
* A top-level application drop down menu, which installs a standard set of menu items for common
|
|
44
56
|
* application actions. Application specific items can be displayed before these standard items.
|
|
@@ -62,11 +74,13 @@ export const [AppMenuButton, appMenuButton] = hoistCmp.withFactory<AppMenuButton
|
|
|
62
74
|
hideOptionsItem,
|
|
63
75
|
hideThemeItem,
|
|
64
76
|
hideAboutItem,
|
|
77
|
+
renderWithUserProfile,
|
|
65
78
|
...rest
|
|
66
79
|
} = props;
|
|
67
80
|
|
|
68
81
|
return menuButton({
|
|
69
82
|
className,
|
|
83
|
+
icon: renderWithUserProfile ? userProfile({renderWithUserProfile}) : Icon.menu(),
|
|
70
84
|
menuItems: buildMenuItems(props),
|
|
71
85
|
menuClassName: 'xh-app-menu',
|
|
72
86
|
popoverProps: {popoverClassName: 'xh-app-menu-popover'},
|
|
@@ -78,6 +92,20 @@ export const [AppMenuButton, appMenuButton] = hoistCmp.withFactory<AppMenuButton
|
|
|
78
92
|
//---------------------------
|
|
79
93
|
// Implementation
|
|
80
94
|
//---------------------------
|
|
95
|
+
const userProfile = hoistCmp.factory<
|
|
96
|
+
HoistProps & {renderWithUserProfile: true | RenderWithUserProfileCustomFn}
|
|
97
|
+
>({
|
|
98
|
+
model: false,
|
|
99
|
+
render({renderWithUserProfile}) {
|
|
100
|
+
return div({
|
|
101
|
+
className: 'xh-app-menu-button__user-profile',
|
|
102
|
+
item: isFunction(renderWithUserProfile)
|
|
103
|
+
? renderWithUserProfile(XH.getUser())
|
|
104
|
+
: XH.getUserInitials()
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
81
109
|
function buildMenuItems({
|
|
82
110
|
hideOptionsItem,
|
|
83
111
|
hideFeedbackItem,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xh/hoist",
|
|
3
|
-
"version": "80.0.0-SNAPSHOT.
|
|
3
|
+
"version": "80.0.0-SNAPSHOT.1768251023007",
|
|
4
4
|
"description": "Hoist add-on for building and deploying React Applications.",
|
|
5
5
|
"repository": "github:xh/hoist-react",
|
|
6
6
|
"homepage": "https://xh.io",
|
package/styles/vars.scss
CHANGED
|
@@ -205,13 +205,14 @@ body {
|
|
|
205
205
|
//---------
|
|
206
206
|
--xh-appbar-bg: var(--appbar-bg, var(--xh-bg-alt));
|
|
207
207
|
--xh-appbar-border-color: var(--appbar-border-color, transparent);
|
|
208
|
+
--xh-appbar-box-shadow: var(--appbar-box-shadow, #{0 0 0 1px rgb(17 20 24 / 10%), 0 1px 1px rgb(17 20 24 / 20%)});
|
|
208
209
|
--xh-appbar-color: var(--appbar-color, var(--xh-text-color));
|
|
209
210
|
--xh-appbar-height: var(--appbar-height, 42);
|
|
210
211
|
--xh-appbar-height-px: calc(var(--xh-appbar-height) * 1px);
|
|
211
212
|
--xh-appbar-title-color: var(--appbar-title-color, #{mc('blue-grey', '700')});
|
|
212
213
|
--xh-appbar-title-font-size: var(--appbar-title-font-size, calc(var(--xh-font-size) * 1.9));
|
|
213
214
|
--xh-appbar-title-font-size-px: calc(var(--xh-appbar-title-font-size) * 1px);
|
|
214
|
-
--xh-appbar-
|
|
215
|
+
--xh-appbar-user-profile-hover-color: var(--appbar-user-profile-hover-color, var(--xh-orange));
|
|
215
216
|
|
|
216
217
|
&.xh-dark {
|
|
217
218
|
--xh-appbar-border-color: var(--appbar-border-color, #{mc('blue-grey', '700')});
|
package/svc/IdentityService.ts
CHANGED
|
@@ -30,16 +30,28 @@ export class IdentityService extends HoistService {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
/**
|
|
33
|
+
/** @returns current acting user (see authUser for notes on impersonation) */
|
|
34
34
|
get user(): HoistUser {
|
|
35
35
|
return this._apparentUser;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
/**
|
|
38
|
+
/** @returns current acting user's username. */
|
|
39
39
|
get username(): string {
|
|
40
40
|
return this.user?.username ?? null;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
/** @returns current acting user's initials, based on displayName. */
|
|
44
|
+
get userInitials(): string {
|
|
45
|
+
// Handle common case of displayName being left as an email address.
|
|
46
|
+
const [displayName] = this.user.displayName.split('@'),
|
|
47
|
+
nameParts = displayName.split(/[\s.]+/);
|
|
48
|
+
|
|
49
|
+
return nameParts
|
|
50
|
+
.map(part => part.charAt(0).toUpperCase())
|
|
51
|
+
.join('')
|
|
52
|
+
.substring(0, XH.isMobileApp ? 2 : 3);
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
/**
|
|
44
56
|
* Actual user who authenticated to the web application.
|
|
45
57
|
* This will be the same as the user except when an administrator is impersonation another
|