@datalayer/core 1.1.57 → 1.1.60
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/lib/components/avatars/UserAvatar.d.ts +9 -1
- package/lib/components/avatars/UserAvatar.js +18 -4
- package/lib/components/collaboration/LiveEditorCollaborators.js +23 -1
- package/lib/components/labels/StatusLabels.d.ts +9 -0
- package/lib/components/labels/StatusLabels.js +30 -1
- package/lib/components/principal/PrincipalAppearance.d.ts +12 -0
- package/lib/components/principal/PrincipalAppearance.js +526 -48
- package/lib/components/principal/PrincipalAvatar.d.ts +8 -1
- package/lib/components/principal/PrincipalAvatar.js +10 -3
- package/lib/components/principal/PrincipalSwitcherMenu.js +2 -1
- package/lib/hooks/useCache.js +11 -2
- package/lib/hooks/useUser.js +42 -0
- package/lib/state/substates/ProfileState.js +4 -0
- package/lib/views/datasources/Datasources.js +1 -1
- package/package.json +1 -1
|
@@ -16,7 +16,15 @@ export type UserAvatarProps = {
|
|
|
16
16
|
fallbackBackground?: string;
|
|
17
17
|
/** Optional icon foreground color override for the default avatar. */
|
|
18
18
|
fallbackForeground?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Whether a ring is drawn around the avatar.
|
|
21
|
+
*
|
|
22
|
+
* Off by default: an avatar sits on its own in most places, and a ring
|
|
23
|
+
* there is one more line for nothing. On, it gives the avatar an edge of
|
|
24
|
+
* its own against a background of the same colour — a profile page, a card.
|
|
25
|
+
*/
|
|
26
|
+
ring?: boolean;
|
|
19
27
|
className?: string;
|
|
20
28
|
};
|
|
21
|
-
export declare const UserAvatar: ({ avatarUrl, avatarIcon, size, square, iconSize, fallbackBackground, fallbackForeground, className, }: UserAvatarProps) => JSX.Element;
|
|
29
|
+
export declare const UserAvatar: ({ avatarUrl, avatarIcon, size, square, iconSize, fallbackBackground, fallbackForeground, ring, className, }: UserAvatarProps) => JSX.Element;
|
|
22
30
|
export default UserAvatar;
|
|
@@ -28,8 +28,18 @@ export function hasRealAvatar(url) {
|
|
|
28
28
|
}
|
|
29
29
|
return true;
|
|
30
30
|
}
|
|
31
|
-
export const UserAvatar = ({ avatarUrl, avatarIcon, size = 100, square = true, iconSize, fallbackBackground, fallbackForeground, className, }) => {
|
|
31
|
+
export const UserAvatar = ({ avatarUrl, avatarIcon, size = 100, square = true, iconSize, fallbackBackground, fallbackForeground, ring = false, className, }) => {
|
|
32
32
|
const palette = useColorPalette();
|
|
33
|
+
/*
|
|
34
|
+
* The ring, as a shadow rather than a border.
|
|
35
|
+
*
|
|
36
|
+
* A border would take from the size asked for — the avatar is laid out
|
|
37
|
+
* against others of the same measure — so it is drawn just outside the
|
|
38
|
+
* edge instead, following whatever shape the avatar has.
|
|
39
|
+
*/
|
|
40
|
+
const ringSx = ring
|
|
41
|
+
? { boxShadow: '0 0 0 1px var(--borderColor-default, currentColor)' }
|
|
42
|
+
: undefined;
|
|
33
43
|
const SelectedIcon = getPrincipalAvatarIcon(avatarIcon);
|
|
34
44
|
if (SelectedIcon) {
|
|
35
45
|
return (_jsx(Box, { className: className, sx: {
|
|
@@ -37,13 +47,16 @@ export const UserAvatar = ({ avatarUrl, avatarIcon, size = 100, square = true, i
|
|
|
37
47
|
height: size,
|
|
38
48
|
borderRadius: square ? 2 : '50%',
|
|
39
49
|
overflow: 'hidden',
|
|
40
|
-
display: '
|
|
50
|
+
display: 'flex',
|
|
41
51
|
alignItems: 'center',
|
|
42
52
|
justifyContent: 'center',
|
|
43
|
-
|
|
53
|
+
bg: fallbackBackground || 'accent.subtle',
|
|
54
|
+
'--datalayer-icon-fg': fallbackForeground || palette.primary,
|
|
55
|
+
...ringSx,
|
|
56
|
+
}, children: _jsx(SelectedIcon, { size: iconSize ?? Math.round(size * 0.62), themed: true, colormode: true }) }));
|
|
44
57
|
}
|
|
45
58
|
if (hasRealAvatar(avatarUrl)) {
|
|
46
|
-
return _jsx(DLAvatar, { className: className, square: square, src: avatarUrl, size: size });
|
|
59
|
+
return (_jsx(DLAvatar, { className: className, square: square, src: avatarUrl, size: size, sx: ringSx }));
|
|
47
60
|
}
|
|
48
61
|
const resolvedIconSize = iconSize ?? Math.round(size * 0.48);
|
|
49
62
|
return (_jsx(Box, { className: className, sx: {
|
|
@@ -55,6 +68,7 @@ export const UserAvatar = ({ avatarUrl, avatarIcon, size = 100, square = true, i
|
|
|
55
68
|
alignItems: 'center',
|
|
56
69
|
justifyContent: 'center',
|
|
57
70
|
'--datalayer-icon-fg': fallbackForeground || palette.primary,
|
|
71
|
+
...ringSx,
|
|
58
72
|
}, children: _jsx(AlienIcon, { size: resolvedIconSize, themed: true, colormode: true }) }));
|
|
59
73
|
};
|
|
60
74
|
export default UserAvatar;
|
|
@@ -35,10 +35,22 @@ function collaboratorOfState(clientId, state) {
|
|
|
35
35
|
if (!user) {
|
|
36
36
|
return undefined;
|
|
37
37
|
}
|
|
38
|
+
// Not a person: the presence of the collaboration service itself — the
|
|
39
|
+
// spacer joins its own rooms, wearing an `agent` field.
|
|
40
|
+
if (user.agent) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
38
43
|
const name = user.display_name || user.displayName || user.name || user.username || '';
|
|
39
44
|
if (!name) {
|
|
40
45
|
return undefined;
|
|
41
46
|
}
|
|
47
|
+
// Nobody either: the seed identity of a Jupyter Server that does not
|
|
48
|
+
// authenticate its users — "Anonymous Hegemone" and kin. A face for it
|
|
49
|
+
// would be one avatar too many, wearing the default fallback, next to the
|
|
50
|
+
// real identity written over it a moment later.
|
|
51
|
+
if (user.anonymous === true || /^anonymous(\s|$)/i.test(name)) {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
42
54
|
return {
|
|
43
55
|
id: String(user.uid ?? user.id ?? user.username ?? clientId),
|
|
44
56
|
name,
|
|
@@ -102,6 +114,16 @@ export function LiveEditorCollaborators(props) {
|
|
|
102
114
|
if (!faces.length) {
|
|
103
115
|
return null;
|
|
104
116
|
}
|
|
105
|
-
return (
|
|
117
|
+
return (
|
|
118
|
+
/*
|
|
119
|
+
* Expanding to the LEFT.
|
|
120
|
+
*
|
|
121
|
+
* A stack of faces spreads out on hover, and it spreads from where it is
|
|
122
|
+
* anchored: anchored on its left, it grew rightwards over whatever the
|
|
123
|
+
* toolbar holds next to it. `alignRight` anchors it on its right, so it
|
|
124
|
+
* opens back over the space it came from — empty by construction, since
|
|
125
|
+
* the stack was collapsed there a moment before.
|
|
126
|
+
*/
|
|
127
|
+
_jsx(AvatarStack, { alignRight: true, size: size, children: faces.map(face => (_jsx(PrincipalAvatar, { kind: "personal", size: size, alt: face.name, avatarUrl: face.avatarUrl, avatarIcon: face.avatarIcon, square: false }, face.id))) }));
|
|
106
128
|
}
|
|
107
129
|
export default LiveEditorCollaborators;
|
|
@@ -39,4 +39,13 @@ export declare function AdminLabel(props?: StatusLabelProps): JSX.Element;
|
|
|
39
39
|
* The one that is taken when none is chosen — the default space of an account.
|
|
40
40
|
*/
|
|
41
41
|
export declare function DefaultLabel(props?: StatusLabelProps): JSX.Element;
|
|
42
|
+
/**
|
|
43
|
+
* Something announced and not there yet.
|
|
44
|
+
*
|
|
45
|
+
* A tone of its own, and deliberately not the accent an alpha wears: one says
|
|
46
|
+
* "you may try this, it may bite", the other "there is nothing to try". Read
|
|
47
|
+
* side by side in a navigation, two labels of one colour say the same thing
|
|
48
|
+
* about two entries that mean different things.
|
|
49
|
+
*/
|
|
50
|
+
export declare function SoonLabel(props?: StatusLabelProps): JSX.Element;
|
|
42
51
|
export default StatusLabel;
|
|
@@ -33,7 +33,24 @@ export function StatusLabel(props) {
|
|
|
33
33
|
return (_jsx(Label, { variant: "secondary", size: size, sx: {
|
|
34
34
|
textTransform: 'lowercase',
|
|
35
35
|
lineHeight: 1.2,
|
|
36
|
-
|
|
36
|
+
/*
|
|
37
|
+
* The tone, under a doubled selector.
|
|
38
|
+
*
|
|
39
|
+
* Primer ships `NavList` and `ActionList` as CSS modules, and the
|
|
40
|
+
* colour they set on the contents of an item ties with the one Emotion
|
|
41
|
+
* generates for `sx` — so the same label came out in the colour of the
|
|
42
|
+
* navigation in one place and in its own colour in another, depending
|
|
43
|
+
* on which stylesheet happened to come last. `&&` raises the
|
|
44
|
+
* specificity enough that the tone always lands, wherever the label is
|
|
45
|
+
* put.
|
|
46
|
+
*/
|
|
47
|
+
'&&': {
|
|
48
|
+
...TONES[tone],
|
|
49
|
+
// The navigation of Primer colours the contents of its items, and
|
|
50
|
+
// its stylesheet is loaded after ours: a doubled selector was not
|
|
51
|
+
// enough on its own, so the colour of the tone is stated as final.
|
|
52
|
+
color: `${TONES[tone].color} !important`,
|
|
53
|
+
},
|
|
37
54
|
...sx,
|
|
38
55
|
}, children: children }));
|
|
39
56
|
}
|
|
@@ -58,4 +75,16 @@ export function DefaultLabel(props = {}) {
|
|
|
58
75
|
const { children = 'default', ...rest } = props;
|
|
59
76
|
return (_jsx(StatusLabel, { tone: "accent", ...rest, children: children }));
|
|
60
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* Something announced and not there yet.
|
|
80
|
+
*
|
|
81
|
+
* A tone of its own, and deliberately not the accent an alpha wears: one says
|
|
82
|
+
* "you may try this, it may bite", the other "there is nothing to try". Read
|
|
83
|
+
* side by side in a navigation, two labels of one colour say the same thing
|
|
84
|
+
* about two entries that mean different things.
|
|
85
|
+
*/
|
|
86
|
+
export function SoonLabel(props = {}) {
|
|
87
|
+
const { children = 'soon', ...rest } = props;
|
|
88
|
+
return (_jsx(StatusLabel, { tone: "neutral", ...rest, children: children }));
|
|
89
|
+
}
|
|
61
90
|
export default StatusLabel;
|
|
@@ -23,11 +23,23 @@ import { SvgUsecasesHero } from '@datalayer/design/lib/svg/SvgUsecasesHero';
|
|
|
23
23
|
export type PrincipalType = 'personal' | 'organization' | 'team';
|
|
24
24
|
type AvatarComponent = ComponentType<SVGProps<SVGSVGElement> & {
|
|
25
25
|
size?: number | 'small' | 'medium' | 'large';
|
|
26
|
+
/**
|
|
27
|
+
* The multi-colour variant, of the emoji the icon is drawn from.
|
|
28
|
+
*
|
|
29
|
+
* The avatars are never drawn with it — see the picker: they follow the
|
|
30
|
+
* colour of the theme instead, through `themed` and `colormode`.
|
|
31
|
+
*/
|
|
26
32
|
colored?: boolean;
|
|
33
|
+
/** Take the colour from `--datalayer-icon-fg`, or from `currentColor`. */
|
|
34
|
+
themed?: boolean;
|
|
35
|
+
/** Follow the colour mode of the page. */
|
|
36
|
+
colormode?: boolean | 'light' | 'dark';
|
|
27
37
|
}>;
|
|
28
38
|
export declare const PRINCIPAL_AVATAR_ICONS: ReadonlyArray<{
|
|
29
39
|
name: string;
|
|
30
40
|
label: string;
|
|
41
|
+
/** What the picture is of, as the emoji catalogue describes it. */
|
|
42
|
+
description?: string;
|
|
31
43
|
Icon: AvatarComponent;
|
|
32
44
|
}>;
|
|
33
45
|
export declare const PRINCIPAL_BANNERS: readonly [{
|
|
@@ -1,40 +1,80 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/*
|
|
3
|
+
* Copyright (c) 2023-2025 Datalayer, Inc.
|
|
4
|
+
* Distributed under the terms of the Modified BSD License.
|
|
5
|
+
*/
|
|
2
6
|
/*
|
|
3
7
|
* Copyright (c) 2023-2026 Datalayer, Inc.
|
|
4
8
|
* Distributed under the terms of the Modified BSD License.
|
|
5
9
|
*/
|
|
6
10
|
import { useEffect, useState } from 'react';
|
|
7
|
-
import { Box, Button, Text } from '@primer/react';
|
|
11
|
+
import { Box, Button, Text, Tooltip } from '@primer/react';
|
|
8
12
|
import { Dialog } from '@primer/react/experimental';
|
|
13
|
+
import AlienMaskIcon from '@datalayer/icons-react/data2/AlienMaskIcon';
|
|
9
14
|
import AlienMonsterIcon from '@datalayer/icons-react/data2/AlienMonsterIcon';
|
|
10
15
|
import AstronautIcon from '@datalayer/icons-react/data2/AstronautIcon';
|
|
16
|
+
import AtomSymbolIcon from '@datalayer/icons-react/data2/AtomSymbolIcon';
|
|
17
|
+
import BankIcon from '@datalayer/icons-react/data2/BankIcon';
|
|
18
|
+
import BlackNibIcon from '@datalayer/icons-react/data2/BlackNibIcon';
|
|
19
|
+
import BriefcaseIcon from '@datalayer/icons-react/data2/BriefcaseIcon';
|
|
20
|
+
import BuildingClassicIcon from '@datalayer/icons-react/data2/BuildingClassicIcon';
|
|
21
|
+
import BuildingConstructionIcon from '@datalayer/icons-react/data2/BuildingConstructionIcon';
|
|
22
|
+
import BuildingOfficeIcon from '@datalayer/icons-react/data2/BuildingOfficeIcon';
|
|
23
|
+
import BullseyeIcon from '@datalayer/icons-react/data2/BullseyeIcon';
|
|
24
|
+
import CloudGreyIcon from '@datalayer/icons-react/data2/CloudGreyIcon';
|
|
25
|
+
import ConstructionIcon from '@datalayer/icons-react/data2/ConstructionIcon';
|
|
11
26
|
import ConstructionWorkerIcon from '@datalayer/icons-react/data2/ConstructionWorkerIcon';
|
|
12
27
|
import CowboyHatFaceIcon from '@datalayer/icons-react/data2/CowboyHatFaceIcon';
|
|
28
|
+
import DashboardGreyIcon from '@datalayer/icons-react/data1/DashboardGreyIcon';
|
|
29
|
+
import DnaIcon from '@datalayer/icons-react/data2/DnaIcon';
|
|
30
|
+
import DraftIcon from '@datalayer/icons-react/data1/DraftIcon';
|
|
13
31
|
import DragonFaceIcon from '@datalayer/icons-react/data2/DragonFaceIcon';
|
|
32
|
+
import DragonIcon from '@datalayer/icons-react/data2/DragonIcon';
|
|
14
33
|
import ElfManIcon from '@datalayer/icons-react/data2/ElfManIcon';
|
|
34
|
+
import FireIcon from '@datalayer/icons-react/data2/FireIcon';
|
|
35
|
+
import FireworksIcon from '@datalayer/icons-react/data2/FireworksIcon';
|
|
15
36
|
import FlyingSaucerIcon from '@datalayer/icons-react/data2/FlyingSaucerIcon';
|
|
37
|
+
import FourLeafCloverIcon from '@datalayer/icons-react/data2/FourLeafCloverIcon';
|
|
38
|
+
import GraduationCapIcon from '@datalayer/icons-react/data2/GraduationCapIcon';
|
|
16
39
|
import GremlinIcon from '@datalayer/icons-react/data2/GremlinIcon';
|
|
17
40
|
import GrinningFaceIcon from '@datalayer/icons-react/data2/GrinningFaceIcon';
|
|
41
|
+
import HouseIcon from '@datalayer/icons-react/data2/HouseIcon';
|
|
18
42
|
import LizardIcon from '@datalayer/icons-react/data2/LizardIcon';
|
|
19
43
|
import MagicWandIcon from '@datalayer/icons-react/data2/MagicWandIcon';
|
|
20
44
|
import ManOfficeWorkerIcon from '@datalayer/icons-react/data2/ManOfficeWorkerIcon';
|
|
21
45
|
import ManTechnologistIcon from '@datalayer/icons-react/data2/ManTechnologistIcon';
|
|
46
|
+
import MusicalNoteIcon from '@datalayer/icons-react/data2/MusicalNoteIcon';
|
|
22
47
|
import NinjaIcon from '@datalayer/icons-react/data2/NinjaIcon';
|
|
48
|
+
import OpenHandsIcon from '@datalayer/icons-react/data2/OpenHandsIcon';
|
|
49
|
+
import PenIcon from '@datalayer/icons-react/data2/PenIcon';
|
|
23
50
|
import PenguinIcon from '@datalayer/icons-react/data2/PenguinIcon';
|
|
51
|
+
import PictureIcon from '@datalayer/icons-react/data2/PictureIcon';
|
|
52
|
+
import PlaneDepartureIcon from '@datalayer/icons-react/data2/PlaneDepartureIcon';
|
|
24
53
|
import PersonSurfingIcon from '@datalayer/icons-react/data2/PersonSurfingIcon';
|
|
25
54
|
import PersonSwimmingIcon from '@datalayer/icons-react/data2/PersonSwimmingIcon';
|
|
55
|
+
import PictureFramedIcon from '@datalayer/icons-react/data2/PictureFramedIcon';
|
|
26
56
|
import RingedPlanetIcon from '@datalayer/icons-react/data2/RingedPlanetIcon';
|
|
27
57
|
import RobotIcon from '@datalayer/icons-react/data2/RobotIcon';
|
|
28
58
|
import RocketIcon from '@datalayer/icons-react/data2/RocketIcon';
|
|
29
59
|
import SantaClausIcon from '@datalayer/icons-react/data2/SantaClausIcon';
|
|
60
|
+
import SatelliteIcon from '@datalayer/icons-react/data2/SatelliteIcon';
|
|
30
61
|
import ScientistIcon from '@datalayer/icons-react/data2/ScientistIcon';
|
|
31
62
|
import SharkIcon from '@datalayer/icons-react/data2/SharkIcon';
|
|
32
63
|
import SnowmanIcon from '@datalayer/icons-react/data2/SnowmanIcon';
|
|
64
|
+
import SpaceInvadersAlien1Icon from '@datalayer/icons-react/eggs/SpaceInvadersAlien1Icon';
|
|
65
|
+
import SpaceInvadersAlien2Icon from '@datalayer/icons-react/eggs/SpaceInvadersAlien2Icon';
|
|
66
|
+
import SpaceInvadersAlien3Icon from '@datalayer/icons-react/eggs/SpaceInvadersAlien3Icon';
|
|
67
|
+
import SparklerIcon from '@datalayer/icons-react/data2/SparklerIcon';
|
|
33
68
|
import StarIcon from '@datalayer/icons-react/data2/StarIcon';
|
|
34
|
-
import
|
|
69
|
+
import StudentIcon from '@datalayer/icons-react/data2/StudentIcon';
|
|
70
|
+
import StudioMicrophoneIcon from '@datalayer/icons-react/data2/StudioMicrophoneIcon';
|
|
35
71
|
import SunIcon from '@datalayer/icons-react/data2/SunIcon';
|
|
72
|
+
import WavingHandIcon from '@datalayer/icons-react/data2/WavingHandIcon';
|
|
36
73
|
import WhaleSpoutingIcon from '@datalayer/icons-react/data2/WhaleSpoutingIcon';
|
|
37
74
|
import WomanTechnologistIcon from '@datalayer/icons-react/data2/WomanTechnologistIcon';
|
|
75
|
+
import WrappedGiftIcon from '@datalayer/icons-react/data2/WrappedGiftIcon';
|
|
76
|
+
import WritingHandIcon from '@datalayer/icons-react/data2/WritingHandIcon';
|
|
77
|
+
import YinYangIcon from '@datalayer/icons-react/data2/YinYangIcon';
|
|
38
78
|
import { SvgAboutHero } from '@datalayer/design/lib/svg/SvgAboutHero';
|
|
39
79
|
import { SvgAgentsHero } from '@datalayer/design/lib/svg/SvgAgentsHero';
|
|
40
80
|
import { SvgAgentsHomeHero } from '@datalayer/design/lib/svg/SvgAgentsHomeHero';
|
|
@@ -57,40 +97,405 @@ import { SvgTermsHero } from '@datalayer/design/lib/svg/SvgTermsHero';
|
|
|
57
97
|
import { SvgTutorialsHero } from '@datalayer/design/lib/svg/SvgTutorialsHero';
|
|
58
98
|
import { SvgUsecasesHero } from '@datalayer/design/lib/svg/SvgUsecasesHero';
|
|
59
99
|
export const PRINCIPAL_AVATAR_ICONS = [
|
|
60
|
-
{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
{
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
{
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
{
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
100
|
+
{
|
|
101
|
+
name: 'AlienMaskIcon',
|
|
102
|
+
label: 'Alien mask',
|
|
103
|
+
description: 'A mask of an alien face, worn for disguise.',
|
|
104
|
+
Icon: AlienMaskIcon,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'AlienMonsterIcon',
|
|
108
|
+
label: 'Alien monster',
|
|
109
|
+
description: 'The pixelated alien of the arcade cabinets.',
|
|
110
|
+
Icon: AlienMonsterIcon,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'AstronautIcon',
|
|
114
|
+
label: 'Astronaut',
|
|
115
|
+
description: 'A person in a spacesuit, ready for orbit.',
|
|
116
|
+
Icon: AstronautIcon,
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'AtomSymbolIcon',
|
|
120
|
+
label: 'Atom',
|
|
121
|
+
description: 'A nucleus circled by electrons, the mark of physics.',
|
|
122
|
+
Icon: AtomSymbolIcon,
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: 'BankIcon',
|
|
126
|
+
label: 'Bank',
|
|
127
|
+
description: 'A columned building where money is kept.',
|
|
128
|
+
Icon: BankIcon,
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: 'BlackNibIcon',
|
|
132
|
+
label: 'Black nib',
|
|
133
|
+
description: 'The nib of a fountain pen, poised to write.',
|
|
134
|
+
Icon: BlackNibIcon,
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: 'BriefcaseIcon',
|
|
138
|
+
label: 'Briefcase',
|
|
139
|
+
description: 'A case for papers, carried to work.',
|
|
140
|
+
Icon: BriefcaseIcon,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'BuildingClassicIcon',
|
|
144
|
+
label: 'Classic building',
|
|
145
|
+
description: 'A columned building of the classical order.',
|
|
146
|
+
Icon: BuildingClassicIcon,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'BuildingConstructionIcon',
|
|
150
|
+
label: 'Building construction',
|
|
151
|
+
description: 'A building going up, crane and all.',
|
|
152
|
+
Icon: BuildingConstructionIcon,
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: 'BuildingOfficeIcon',
|
|
156
|
+
label: 'Office building',
|
|
157
|
+
description: 'An office block of many identical windows.',
|
|
158
|
+
Icon: BuildingOfficeIcon,
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
name: 'BullseyeIcon',
|
|
162
|
+
label: 'Bullseye',
|
|
163
|
+
description: 'A dart in the centre of the target.',
|
|
164
|
+
Icon: BullseyeIcon,
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'CloudGreyIcon',
|
|
168
|
+
label: 'Cloud',
|
|
169
|
+
description: 'A cloud, of the sky or of the servers.',
|
|
170
|
+
Icon: CloudGreyIcon,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'ConstructionIcon',
|
|
174
|
+
label: 'Construction',
|
|
175
|
+
description: 'A striped barrier: work in progress.',
|
|
176
|
+
Icon: ConstructionIcon,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: 'ConstructionWorkerIcon',
|
|
180
|
+
label: 'Construction worker',
|
|
181
|
+
description: 'A worker in a hard hat.',
|
|
182
|
+
Icon: ConstructionWorkerIcon,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'CowboyHatFaceIcon',
|
|
186
|
+
label: 'Cowboy',
|
|
187
|
+
description: 'A grinning face under a cowboy hat.',
|
|
188
|
+
Icon: CowboyHatFaceIcon,
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
name: 'DashboardGreyIcon',
|
|
192
|
+
label: 'Dashboard',
|
|
193
|
+
description: 'A gauge with its needle in the red.',
|
|
194
|
+
Icon: DashboardGreyIcon,
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: 'DnaIcon',
|
|
198
|
+
label: 'DNA',
|
|
199
|
+
description: 'The double helix that carries the code of life.',
|
|
200
|
+
Icon: DnaIcon,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'DraftIcon',
|
|
204
|
+
label: 'Draft',
|
|
205
|
+
description: 'A sheet still being drawn up.',
|
|
206
|
+
Icon: DraftIcon,
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
name: 'DragonFaceIcon',
|
|
210
|
+
label: 'Dragon face',
|
|
211
|
+
description: 'The face of a dragon, whiskers and horns.',
|
|
212
|
+
Icon: DragonFaceIcon,
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
name: 'DragonIcon',
|
|
216
|
+
label: 'Dragon',
|
|
217
|
+
description: 'A dragon in full, coiled and winged.',
|
|
218
|
+
Icon: DragonIcon,
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'ElfManIcon',
|
|
222
|
+
label: 'Elf',
|
|
223
|
+
description: 'A pointy-eared elf of the folk tales.',
|
|
224
|
+
Icon: ElfManIcon,
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: 'FireIcon',
|
|
228
|
+
label: 'Fire',
|
|
229
|
+
description: 'A flame — hot, fast, or simply on fire.',
|
|
230
|
+
Icon: FireIcon,
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: 'FireworksIcon',
|
|
234
|
+
label: 'Fireworks',
|
|
235
|
+
description: 'Fireworks bursting over a night sky.',
|
|
236
|
+
Icon: FireworksIcon,
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
name: 'FlyingSaucerIcon',
|
|
240
|
+
label: 'Flying saucer',
|
|
241
|
+
description: 'A saucer from elsewhere, beam and all.',
|
|
242
|
+
Icon: FlyingSaucerIcon,
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
name: 'FourLeafCloverIcon',
|
|
246
|
+
label: 'Four-leaf clover',
|
|
247
|
+
description: 'The rare fourth leaf, for luck.',
|
|
248
|
+
Icon: FourLeafCloverIcon,
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: 'GraduationCapIcon',
|
|
252
|
+
label: 'Graduation cap',
|
|
253
|
+
description: 'The square cap thrown on graduation day.',
|
|
254
|
+
Icon: GraduationCapIcon,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: 'GremlinIcon',
|
|
258
|
+
label: 'Gremlin',
|
|
259
|
+
description: 'A small mischief-maker, blamed for the bugs.',
|
|
260
|
+
Icon: GremlinIcon,
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
name: 'GrinningFaceIcon',
|
|
264
|
+
label: 'Grinning face',
|
|
265
|
+
description: 'A face grinning from ear to ear.',
|
|
266
|
+
Icon: GrinningFaceIcon,
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
name: 'HouseIcon',
|
|
270
|
+
label: 'House',
|
|
271
|
+
description: 'A house with its roof and door.',
|
|
272
|
+
Icon: HouseIcon,
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
name: 'LizardIcon',
|
|
276
|
+
label: 'Lizard',
|
|
277
|
+
description: 'A lizard, still and watchful.',
|
|
278
|
+
Icon: LizardIcon,
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
name: 'MagicWandIcon',
|
|
282
|
+
label: 'Magic wand',
|
|
283
|
+
description: 'A wand trailing sparks.',
|
|
284
|
+
Icon: MagicWandIcon,
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
name: 'ManOfficeWorkerIcon',
|
|
288
|
+
label: 'Office worker',
|
|
289
|
+
description: 'A worker at a desk in an office.',
|
|
290
|
+
Icon: ManOfficeWorkerIcon,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
name: 'ManTechnologistIcon',
|
|
294
|
+
label: 'Man technologist',
|
|
295
|
+
description: 'A man at a laptop, writing code.',
|
|
296
|
+
Icon: ManTechnologistIcon,
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: 'MusicalNoteIcon',
|
|
300
|
+
label: 'Musical note',
|
|
301
|
+
description: 'A single note off a stave.',
|
|
302
|
+
Icon: MusicalNoteIcon,
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: 'NinjaIcon',
|
|
306
|
+
label: 'Ninja',
|
|
307
|
+
description: 'A masked figure, quick and unseen.',
|
|
308
|
+
Icon: NinjaIcon,
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
name: 'OpenHandsIcon',
|
|
312
|
+
label: 'Open hands',
|
|
313
|
+
description: 'Two open hands, offered or welcoming.',
|
|
314
|
+
Icon: OpenHandsIcon,
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
name: 'PenIcon',
|
|
318
|
+
label: 'Pen',
|
|
319
|
+
description: 'A ballpoint pen for everyday writing.',
|
|
320
|
+
Icon: PenIcon,
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: 'PenguinIcon',
|
|
324
|
+
label: 'Penguin',
|
|
325
|
+
description: 'A penguin in its black and white.',
|
|
326
|
+
Icon: PenguinIcon,
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
name: 'PersonSurfingIcon',
|
|
330
|
+
label: 'Surfer',
|
|
331
|
+
description: 'A surfer riding the face of a wave.',
|
|
332
|
+
Icon: PersonSurfingIcon,
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
name: 'PersonSwimmingIcon',
|
|
336
|
+
label: 'Swimmer',
|
|
337
|
+
description: 'A swimmer mid-stroke.',
|
|
338
|
+
Icon: PersonSwimmingIcon,
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
name: 'PictureFramedIcon',
|
|
342
|
+
label: 'Framed picture',
|
|
343
|
+
description: 'A painting hung in its frame.',
|
|
344
|
+
Icon: PictureFramedIcon,
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: 'PictureIcon',
|
|
348
|
+
label: 'Picture',
|
|
349
|
+
description: 'A photograph of mountains and sun.',
|
|
350
|
+
Icon: PictureIcon,
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
name: 'PlaneDepartureIcon',
|
|
354
|
+
label: 'Plane',
|
|
355
|
+
description: 'An aeroplane lifting off the runway.',
|
|
356
|
+
Icon: PlaneDepartureIcon,
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
name: 'RingedPlanetIcon',
|
|
360
|
+
label: 'Planet',
|
|
361
|
+
description: 'A planet circled by its rings.',
|
|
362
|
+
Icon: RingedPlanetIcon,
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
name: 'RobotIcon',
|
|
366
|
+
label: 'Robot',
|
|
367
|
+
description: 'The square face of a robot.',
|
|
368
|
+
Icon: RobotIcon,
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: 'RocketIcon',
|
|
372
|
+
label: 'Rocket',
|
|
373
|
+
description: 'A rocket climbing on its exhaust.',
|
|
374
|
+
Icon: RocketIcon,
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: 'SantaClausIcon',
|
|
378
|
+
label: 'Santa',
|
|
379
|
+
description: 'Santa Claus, red hat and white beard.',
|
|
380
|
+
Icon: SantaClausIcon,
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
name: 'SatelliteIcon',
|
|
384
|
+
label: 'Satellite',
|
|
385
|
+
description: 'A satellite with its panels spread.',
|
|
386
|
+
Icon: SatelliteIcon,
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
name: 'ScientistIcon',
|
|
390
|
+
label: 'Scientist',
|
|
391
|
+
description: 'A scientist at the microscope.',
|
|
392
|
+
Icon: ScientistIcon,
|
|
393
|
+
},
|
|
394
|
+
{
|
|
395
|
+
name: 'SharkIcon',
|
|
396
|
+
label: 'Shark',
|
|
397
|
+
description: 'A shark, fin first.',
|
|
398
|
+
Icon: SharkIcon,
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
name: 'SnowmanIcon',
|
|
402
|
+
label: 'Snowman',
|
|
403
|
+
description: 'A snowman built up in the cold.',
|
|
404
|
+
Icon: SnowmanIcon,
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
name: 'SpaceInvadersAlien1Icon',
|
|
408
|
+
label: 'Space invader 1',
|
|
409
|
+
description: 'The first invader of the arcade fleet.',
|
|
410
|
+
Icon: SpaceInvadersAlien1Icon,
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
name: 'SpaceInvadersAlien2Icon',
|
|
414
|
+
label: 'Space invader 2',
|
|
415
|
+
description: 'The second invader of the arcade fleet.',
|
|
416
|
+
Icon: SpaceInvadersAlien2Icon,
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
name: 'SpaceInvadersAlien3Icon',
|
|
420
|
+
label: 'Space invader 3',
|
|
421
|
+
description: 'The third invader of the arcade fleet.',
|
|
422
|
+
Icon: SpaceInvadersAlien3Icon,
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
name: 'SparklerIcon',
|
|
426
|
+
label: 'Sparkler',
|
|
427
|
+
description: 'A hand-held sparkler throwing light.',
|
|
428
|
+
Icon: SparklerIcon,
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
name: 'StarIcon',
|
|
432
|
+
label: 'Star',
|
|
433
|
+
description: 'A five-pointed star.',
|
|
434
|
+
Icon: StarIcon,
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
name: 'StudentIcon',
|
|
438
|
+
label: 'Student',
|
|
439
|
+
description: 'A student with book and cap.',
|
|
440
|
+
Icon: StudentIcon,
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
name: 'StudioMicrophoneIcon',
|
|
444
|
+
label: 'Studio microphone',
|
|
445
|
+
description: 'The microphone of a recording studio.',
|
|
446
|
+
Icon: StudioMicrophoneIcon,
|
|
447
|
+
},
|
|
448
|
+
{
|
|
449
|
+
name: 'SunIcon',
|
|
450
|
+
label: 'Sun',
|
|
451
|
+
description: 'The sun at full strength.',
|
|
452
|
+
Icon: SunIcon,
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
name: 'WavingHandIcon',
|
|
456
|
+
label: 'Waving hand',
|
|
457
|
+
description: 'A hand raised in hello.',
|
|
458
|
+
Icon: WavingHandIcon,
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
name: 'WhaleSpoutingIcon',
|
|
462
|
+
label: 'Whale',
|
|
463
|
+
description: 'A whale blowing a spout of water.',
|
|
464
|
+
Icon: WhaleSpoutingIcon,
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
name: 'WomanTechnologistIcon',
|
|
468
|
+
label: 'Woman technologist',
|
|
469
|
+
description: 'A woman at a laptop, writing code.',
|
|
470
|
+
Icon: WomanTechnologistIcon,
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
name: 'WrappedGiftIcon',
|
|
474
|
+
label: 'Wrapped gift',
|
|
475
|
+
description: 'A present tied with a ribbon.',
|
|
476
|
+
Icon: WrappedGiftIcon,
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
name: 'WritingHandIcon',
|
|
480
|
+
label: 'Writing hand',
|
|
481
|
+
description: 'A hand writing with a pen.',
|
|
482
|
+
Icon: WritingHandIcon,
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: 'YinYangIcon',
|
|
486
|
+
label: 'Yin yang',
|
|
487
|
+
description: 'The two halves that make a whole.',
|
|
488
|
+
Icon: YinYangIcon,
|
|
489
|
+
},
|
|
89
490
|
];
|
|
90
491
|
export const PRINCIPAL_BANNERS = [
|
|
91
492
|
{ name: 'SvgAboutHero', label: 'About', Component: SvgAboutHero },
|
|
92
493
|
{ name: 'SvgAgentsHero', label: 'Agents', Component: SvgAgentsHero },
|
|
93
|
-
{
|
|
494
|
+
{
|
|
495
|
+
name: 'SvgAgentsHomeHero',
|
|
496
|
+
label: 'Agents home',
|
|
497
|
+
Component: SvgAgentsHomeHero,
|
|
498
|
+
},
|
|
94
499
|
{ name: 'SvgBlogHero', label: 'Blog', Component: SvgBlogHero },
|
|
95
500
|
{ name: 'SvgCareersHero', label: 'Careers', Component: SvgCareersHero },
|
|
96
501
|
{ name: 'SvgChangelogHero', label: 'Changelog', Component: SvgChangelogHero },
|
|
@@ -99,7 +504,11 @@ export const PRINCIPAL_BANNERS = [
|
|
|
99
504
|
{ name: 'SvgEarthHero', label: 'Earth', Component: SvgEarthHero },
|
|
100
505
|
{ name: 'SvgEvalsHero', label: 'Evaluations', Component: SvgEvalsHero },
|
|
101
506
|
{ name: 'SvgEventsHero', label: 'Events', Component: SvgEventsHero },
|
|
102
|
-
{
|
|
507
|
+
{
|
|
508
|
+
name: 'SvgIntegrationsHero',
|
|
509
|
+
label: 'Integrations',
|
|
510
|
+
Component: SvgIntegrationsHero,
|
|
511
|
+
},
|
|
103
512
|
{ name: 'SvgLoginHero', label: 'Login', Component: SvgLoginHero },
|
|
104
513
|
{ name: 'SvgPartnersHero', label: 'Partners', Component: SvgPartnersHero },
|
|
105
514
|
{ name: 'SvgPricingHero', label: 'Pricing', Component: SvgPricingHero },
|
|
@@ -145,28 +554,95 @@ export function PrincipalBannerImage({ banner, height = 180, }) {
|
|
|
145
554
|
export function PrincipalAvatarPicker({ value, onChange, disabled = false, }) {
|
|
146
555
|
const [open, setOpen] = useState(false);
|
|
147
556
|
const selected = PRINCIPAL_AVATAR_ICONS.find(option => option.name === value);
|
|
148
|
-
return (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", disabled: disabled, onClick: () => setOpen(true), children: selected ? `Change Avatar (${selected.label})` : 'Choose Avatar' }), open ? (_jsx(Dialog, { title: "Choose an Avatar", onClose: () => setOpen(false),
|
|
557
|
+
return (_jsxs(_Fragment, { children: [_jsx(Button, { type: "button", disabled: disabled, onClick: () => setOpen(true), children: selected ? `Change Avatar (${selected.label})` : 'Choose Avatar' }), open ? (_jsx(Dialog, { title: "Choose an Avatar", onClose: () => setOpen(false),
|
|
558
|
+
// The widest the dialog names — 640px — which is what it falls
|
|
559
|
+
// back to should the width below ever stop applying.
|
|
560
|
+
width: "xlarge",
|
|
561
|
+
/*
|
|
562
|
+
* Wider than any name of the scale, for the twelve avatars of a
|
|
563
|
+
* line to have room, and bounded by the window.
|
|
564
|
+
*
|
|
565
|
+
* Under `&&`, which doubles the specificity of the class it
|
|
566
|
+
* generates: the width of a named size is written
|
|
567
|
+
* `.prc-Dialog-Dialog:where([data-width=xlarge])`, whose `:where`
|
|
568
|
+
* weighs nothing, so the rule and a plain `sx` are of the very
|
|
569
|
+
* same specificity and the one applied is whichever the
|
|
570
|
+
* stylesheets happen to order last.
|
|
571
|
+
*/
|
|
572
|
+
sx: {
|
|
573
|
+
'&&': {
|
|
574
|
+
width: 'min(1080px, calc(100vw - 32px))',
|
|
575
|
+
maxWidth: 'none',
|
|
576
|
+
},
|
|
577
|
+
}, children: _jsx(Box, { sx: {
|
|
149
578
|
display: 'grid',
|
|
150
|
-
|
|
579
|
+
// Twelve to a line, whatever the dialog is wide: the columns
|
|
580
|
+
// share it evenly and `minmax(0, …)` lets them shrink under
|
|
581
|
+
// their content, which a long name would otherwise widen.
|
|
582
|
+
gridTemplateColumns: [
|
|
583
|
+
'repeat(6, minmax(0, 1fr))',
|
|
584
|
+
'repeat(12, minmax(0, 1fr))',
|
|
585
|
+
],
|
|
151
586
|
gap: 2,
|
|
152
|
-
}, children: PRINCIPAL_AVATAR_ICONS.map(({ name, label, Icon }) => (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
587
|
+
}, children: PRINCIPAL_AVATAR_ICONS.map(({ name, label, description, Icon }) => (
|
|
588
|
+
// A button drawn as a card rather than a Primer `Button`: the
|
|
589
|
+
// avatar is named under it, as the banners are under theirs.
|
|
590
|
+
// The name under a card is clipped when it is long, and a
|
|
591
|
+
// picture says only so much: the tooltip gives the whole name
|
|
592
|
+
// and what the picture is of.
|
|
593
|
+
_jsx(Tooltip, { text: description ? `${label} — ${description}` : label, direction: "n", children: _jsxs(Box, { as: "button", type: "button", "aria-label": description ? `${label} — ${description}` : label, "aria-pressed": value === name, onClick: () => {
|
|
594
|
+
onChange(name);
|
|
595
|
+
setOpen(false);
|
|
596
|
+
}, sx: {
|
|
597
|
+
display: 'block',
|
|
598
|
+
width: '100%',
|
|
599
|
+
p: 0,
|
|
600
|
+
overflow: 'hidden',
|
|
601
|
+
appearance: 'none',
|
|
602
|
+
color: 'fg.default',
|
|
603
|
+
cursor: 'pointer',
|
|
604
|
+
border: '1px solid',
|
|
605
|
+
borderRadius: 2,
|
|
606
|
+
borderColor: value === name ? 'accent.emphasis' : 'border.default',
|
|
607
|
+
bg: value === name ? 'accent.subtle' : 'canvas.default',
|
|
608
|
+
':hover': { borderColor: 'accent.emphasis' },
|
|
609
|
+
}, children: [_jsx(Box, { sx: {
|
|
610
|
+
height: 72,
|
|
611
|
+
display: 'flex',
|
|
612
|
+
alignItems: 'center',
|
|
613
|
+
justifyContent: 'center',
|
|
614
|
+
}, children: _jsx(Icon, { size: 52, themed: true, colormode: true }) }), _jsx(Text, { sx: {
|
|
615
|
+
display: 'block',
|
|
616
|
+
px: 2,
|
|
617
|
+
py: 1,
|
|
618
|
+
fontSize: 0,
|
|
619
|
+
textAlign: 'center',
|
|
620
|
+
// A name too long for the card is cut rather than
|
|
621
|
+
// stretching the grid it sits in.
|
|
622
|
+
overflow: 'hidden',
|
|
623
|
+
textOverflow: 'ellipsis',
|
|
624
|
+
whiteSpace: 'nowrap',
|
|
625
|
+
}, children: label })] }) }, name))) }) })) : null] }));
|
|
161
626
|
}
|
|
162
627
|
export function PrincipalBannerPicker({ value, onChange, disabled = false, showPreview = true, }) {
|
|
163
628
|
const [open, setOpen] = useState(false);
|
|
164
629
|
const [selectedName, setSelectedName] = useState(value);
|
|
165
630
|
useEffect(() => setSelectedName(value), [value]);
|
|
166
631
|
const selected = PRINCIPAL_BANNERS.find(option => option.name === selectedName);
|
|
167
|
-
return (_jsxs(_Fragment, { children: [showPreview && selected ? (_jsx(Box, { sx: { mb: 2 }, children: _jsx(PrincipalBannerImage, { banner: selected.name, height: 120 }) })) : null, _jsx(Button, { type: "button", disabled: disabled, onClick: () => setOpen(true), children: selected ? `Change Banner (${selected.label})` : 'Choose Banner' }), open ? (_jsx(Dialog, { title: "Choose a Banner", onClose: () => setOpen(false), width: "xlarge",
|
|
632
|
+
return (_jsxs(_Fragment, { children: [showPreview && selected ? (_jsx(Box, { sx: { mb: 2 }, children: _jsx(PrincipalBannerImage, { banner: selected.name, height: 120 }) })) : null, _jsx(Button, { type: "button", disabled: disabled, onClick: () => setOpen(true), children: selected ? `Change Banner (${selected.label})` : 'Choose Banner' }), open ? (_jsx(Dialog, { title: "Choose a Banner", onClose: () => setOpen(false), width: "xlarge",
|
|
633
|
+
// Under `&&` for the reason given on the avatar picker: a plain
|
|
634
|
+
// `sx` ties with the named width and may lose to it.
|
|
635
|
+
sx: {
|
|
636
|
+
'&&': {
|
|
637
|
+
width: 'min(960px, calc(100vw - 32px))',
|
|
638
|
+
maxWidth: 'none',
|
|
639
|
+
},
|
|
640
|
+
}, children: _jsx(Box, { sx: {
|
|
168
641
|
display: 'grid',
|
|
169
|
-
|
|
642
|
+
// Four to a row, so a banner is seen against its neighbours
|
|
643
|
+
// rather than one at a time; one column on a narrow overlay,
|
|
644
|
+
// where four would be four slivers.
|
|
645
|
+
gridTemplateColumns: ['1fr', 'repeat(4, minmax(0, 1fr))'],
|
|
170
646
|
gap: 3,
|
|
171
647
|
}, children: PRINCIPAL_BANNERS.map(({ name, label, Component }) => (_jsxs(Box, { as: "button", type: "button", "aria-label": label, "aria-pressed": selectedName === name, onClick: () => {
|
|
172
648
|
setSelectedName(name);
|
|
@@ -182,7 +658,9 @@ export function PrincipalBannerPicker({ value, onChange, disabled = false, showP
|
|
|
182
658
|
cursor: 'pointer',
|
|
183
659
|
border: '1px solid',
|
|
184
660
|
borderRadius: 2,
|
|
185
|
-
borderColor: selectedName === name
|
|
661
|
+
borderColor: selectedName === name
|
|
662
|
+
? 'accent.emphasis'
|
|
663
|
+
: 'border.default',
|
|
186
664
|
bg: selectedName === name ? 'accent.subtle' : 'canvas.default',
|
|
187
665
|
textAlign: 'left',
|
|
188
666
|
':hover': { borderColor: 'accent.emphasis' },
|
|
@@ -7,7 +7,14 @@ export type PrincipalAvatarProps = {
|
|
|
7
7
|
alt?: string;
|
|
8
8
|
size?: number;
|
|
9
9
|
square?: boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Whether a ring is drawn around the avatar.
|
|
12
|
+
*
|
|
13
|
+
* Off by default, and the same option `UserAvatar` carries — a public
|
|
14
|
+
* profile wants the edge, a menu entry does not.
|
|
15
|
+
*/
|
|
16
|
+
ring?: boolean;
|
|
10
17
|
className?: string;
|
|
11
18
|
};
|
|
12
|
-
export declare function PrincipalAvatar({ kind, avatarUrl, avatarIcon, alt, size, square, className, }: PrincipalAvatarProps): JSX.Element;
|
|
19
|
+
export declare function PrincipalAvatar({ kind, avatarUrl, avatarIcon, alt, size, square, ring, className, }: PrincipalAvatarProps): JSX.Element;
|
|
13
20
|
export default PrincipalAvatar;
|
|
@@ -10,8 +10,13 @@ import { getPrincipalAvatarIcon } from './PrincipalAppearance.js';
|
|
|
10
10
|
function getFallbackIconSize(size) {
|
|
11
11
|
return Math.max(12, Math.round(size * 0.62));
|
|
12
12
|
}
|
|
13
|
-
export function PrincipalAvatar({ kind, avatarUrl, avatarIcon, alt, size = 20, square = false, className, }) {
|
|
13
|
+
export function PrincipalAvatar({ kind, avatarUrl, avatarIcon, alt, size = 20, square = false, ring = false, className, }) {
|
|
14
14
|
const SelectedIcon = getPrincipalAvatarIcon(avatarIcon);
|
|
15
|
+
// Outside the edge rather than a border, so the avatar keeps the size it
|
|
16
|
+
// was asked for — see `UserAvatar`, which draws its ring the same way.
|
|
17
|
+
const ringSx = ring
|
|
18
|
+
? { boxShadow: '0 0 0 1px var(--borderColor-default, currentColor)' }
|
|
19
|
+
: undefined;
|
|
15
20
|
if (SelectedIcon) {
|
|
16
21
|
return (_jsx(Box, { className: className, sx: {
|
|
17
22
|
width: size,
|
|
@@ -21,10 +26,11 @@ export function PrincipalAvatar({ kind, avatarUrl, avatarIcon, alt, size = 20, s
|
|
|
21
26
|
justifyContent: 'center',
|
|
22
27
|
overflow: 'hidden',
|
|
23
28
|
borderRadius: square ? 2 : '50%',
|
|
24
|
-
|
|
29
|
+
...ringSx,
|
|
30
|
+
}, "aria-label": alt || `${kind} avatar`, children: _jsx(SelectedIcon, { size: size, themed: true, colormode: true }) }));
|
|
25
31
|
}
|
|
26
32
|
if (kind === 'personal') {
|
|
27
|
-
return (_jsx(UserAvatar, { avatarUrl: avatarUrl, avatarIcon: avatarIcon, size: size, square: square, iconSize: getFallbackIconSize(size), className: className }));
|
|
33
|
+
return (_jsx(UserAvatar, { avatarUrl: avatarUrl, avatarIcon: avatarIcon, size: size, square: square, iconSize: getFallbackIconSize(size), ring: ring, className: className }));
|
|
28
34
|
}
|
|
29
35
|
const iconSize = getFallbackIconSize(size);
|
|
30
36
|
const borderRadius = square ? 2 : '50%';
|
|
@@ -39,6 +45,7 @@ export function PrincipalAvatar({ kind, avatarUrl, avatarIcon, alt, size = 20, s
|
|
|
39
45
|
borderRadius,
|
|
40
46
|
border: '1px solid',
|
|
41
47
|
borderColor: 'border.default',
|
|
48
|
+
...ringSx,
|
|
42
49
|
}, "aria-label": alt || (kind === 'team' ? 'Team' : 'Organization'), children: _jsx(Icon, { size: iconSize }) }));
|
|
43
50
|
}
|
|
44
51
|
export default PrincipalAvatar;
|
|
@@ -9,6 +9,7 @@ import { OrganizationIcon, PeopleIcon, PersonIcon, TriangleDownIcon, } from '@pr
|
|
|
9
9
|
import { useCache, useAuthorization } from '../../hooks/index.js';
|
|
10
10
|
import { useCoreStore } from '../../state/index.js';
|
|
11
11
|
import { useIAMStore } from '../../state/substates/index.js';
|
|
12
|
+
import { UserAvatar } from '../avatars/index.js';
|
|
12
13
|
import { memberships as fetchMemberships } from '../../api/iam/profile.js';
|
|
13
14
|
import { usePrincipalStore } from '../../hooks/usePrincipalStore.js';
|
|
14
15
|
import { useBillingEntityStore } from '../../hooks/useBillingEntityStore.js';
|
|
@@ -284,7 +285,7 @@ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, sh
|
|
|
284
285
|
if (personalUid && personalHandle) {
|
|
285
286
|
selectUser(personalUid, personalHandle);
|
|
286
287
|
}
|
|
287
|
-
}, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(
|
|
288
|
+
}, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(UserAvatar, { avatarUrl: user?.avatarUrl, avatarIcon: user?.avatarIcon, size: 16, square: false, ring: true }) }), _jsxs(Box, { as: "span", sx: {
|
|
288
289
|
display: 'block',
|
|
289
290
|
minWidth: 0,
|
|
290
291
|
overflow: 'hidden',
|
package/lib/hooks/useCache.js
CHANGED
|
@@ -2021,8 +2021,17 @@ export const useCache = ({ loginRoute = '/login' } = {}) => {
|
|
|
2021
2021
|
onSuccess: (resp, _variables) => {
|
|
2022
2022
|
if (resp.success && resp.document) {
|
|
2023
2023
|
const document = toDocument(resp.document);
|
|
2024
|
-
|
|
2025
|
-
|
|
2024
|
+
/*
|
|
2025
|
+
* The creation answer carries no model — the spacer keeps the
|
|
2026
|
+
* model with the content, and only the GET of a document answers
|
|
2027
|
+
* with it. Seeding the detail cache here would hand the editor a
|
|
2028
|
+
* document without a model, shown as a blank page: the cache is
|
|
2029
|
+
* invalidated instead, so an editor opening on the creation reads
|
|
2030
|
+
* the document from the server, model included.
|
|
2031
|
+
*/
|
|
2032
|
+
queryClient.invalidateQueries({
|
|
2033
|
+
queryKey: queryKeys.documents.detail(document.id),
|
|
2034
|
+
});
|
|
2026
2035
|
// Refetch all document queries immediately (including bySpace)
|
|
2027
2036
|
queryClient.refetchQueries({
|
|
2028
2037
|
queryKey: queryKeys.documents.all(),
|
package/lib/hooks/useUser.js
CHANGED
|
@@ -15,6 +15,28 @@ const isOnPublicPath = () => {
|
|
|
15
15
|
}
|
|
16
16
|
return isPublicPath(window.location?.pathname || '');
|
|
17
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Whether these views are running inside JupyterLab.
|
|
20
|
+
*
|
|
21
|
+
* Read from the configuration JupyterLab writes into its own page, so nothing
|
|
22
|
+
* has to be imported from it: this module is used by the web application as
|
|
23
|
+
* well, which has no such configuration.
|
|
24
|
+
*/
|
|
25
|
+
const isInsideJupyterLab = () => {
|
|
26
|
+
if (typeof document === 'undefined') {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
const config = document.getElementById('jupyter-config-data');
|
|
30
|
+
if (!config) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(config.textContent || '{}').appName === 'JupyterLab';
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
18
40
|
export const useUser = (role) => {
|
|
19
41
|
const { user } = useIAMStore();
|
|
20
42
|
if (role) {
|
|
@@ -22,6 +44,10 @@ export const useUser = (role) => {
|
|
|
22
44
|
if (isOnPublicPath()) {
|
|
23
45
|
return user;
|
|
24
46
|
}
|
|
47
|
+
if (isInsideJupyterLab()) {
|
|
48
|
+
// Same reason as below: the router of these views answers this.
|
|
49
|
+
return user;
|
|
50
|
+
}
|
|
25
51
|
console.warn(`User should have role ${role.handle} - Forcing navigation to login page...`);
|
|
26
52
|
window.location.href = LOGIN_HREF;
|
|
27
53
|
throw new Error(`User should have role ${role.handle}`);
|
|
@@ -31,6 +57,22 @@ export const useUser = (role) => {
|
|
|
31
57
|
if (isOnPublicPath()) {
|
|
32
58
|
return user;
|
|
33
59
|
}
|
|
60
|
+
/*
|
|
61
|
+
* Inside JupyterLab, nobody leaves the page.
|
|
62
|
+
*
|
|
63
|
+
* `/login` is a page of the web application, at the root of the origin —
|
|
64
|
+
* and the origin of a JupyterLab is the Jupyter Server, which serves
|
|
65
|
+
* something else entirely there. Signing out of a view thus threw the user
|
|
66
|
+
* out of JupyterLab and onto a page of the server, which is not even a
|
|
67
|
+
* sign-in form.
|
|
68
|
+
*
|
|
69
|
+
* The views of Datalayer carry their own sign-in route under the base of
|
|
70
|
+
* JupyterLab, and their router shows it as soon as there is no user — so
|
|
71
|
+
* the way to reach it is to let that render, not to navigate away from it.
|
|
72
|
+
*/
|
|
73
|
+
if (isInsideJupyterLab()) {
|
|
74
|
+
return user;
|
|
75
|
+
}
|
|
34
76
|
console.warn('No user found... Forcing navigation to /login');
|
|
35
77
|
window.location.href = LOGIN_HREF;
|
|
36
78
|
}
|
|
@@ -63,6 +63,6 @@ export const Datasources = ({ newDatasourceRoute = '/new/datasource', datasource
|
|
|
63
63
|
gap: 3,
|
|
64
64
|
flexWrap: 'wrap',
|
|
65
65
|
mb: 4,
|
|
66
|
-
}, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Datasources" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Configure external data providers available to your workspaces and agents." })] }), _jsx(Button, { size: "small", variant: "primary", leadingVisual: DatabaseIcon, onClick: e => navigate(newDatasourceRoute, e), children: "New
|
|
66
|
+
}, children: [_jsxs(Box, { children: [_jsx(Heading, { as: "h2", sx: { fontSize: 3, mb: 1 }, children: "Datasources" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Configure external data providers available to your workspaces and agents." })] }), _jsx(Button, { size: "small", variant: "primary", leadingVisual: DatabaseIcon, onClick: e => navigate(newDatasourceRoute, e), children: "New Datasource" })] }), _jsx(DatasourcesTable, { datasourcesListRoute: datasourcesListRoute, principalUid: principalUid, principalKind: principalKind, showInlineLoadingIndicator: showInlineLoadingIndicator })] }) }) }));
|
|
67
67
|
};
|
|
68
68
|
export default Datasources;
|