@datalayer/core 1.0.21 → 1.0.22

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 (43) hide show
  1. package/lib/components/billing/BillableAccountSelect.d.ts +35 -0
  2. package/lib/components/billing/BillableAccountSelect.js +340 -0
  3. package/lib/components/billing/index.d.ts +1 -0
  4. package/lib/components/billing/index.js +5 -0
  5. package/lib/components/index.d.ts +2 -0
  6. package/lib/components/index.js +2 -0
  7. package/lib/components/principal/Principal.d.ts +37 -0
  8. package/lib/components/principal/Principal.js +54 -0
  9. package/lib/components/principal/PrincipalAvatar.d.ts +10 -0
  10. package/lib/components/principal/PrincipalAvatar.js +55 -0
  11. package/lib/components/principal/PrincipalBadge.d.ts +20 -0
  12. package/lib/components/principal/PrincipalBadge.js +208 -0
  13. package/lib/components/principal/PrincipalBanner.d.ts +7 -0
  14. package/lib/components/principal/PrincipalBanner.js +84 -0
  15. package/lib/components/principal/PrincipalDetailsOverlay.d.ts +23 -0
  16. package/lib/components/principal/PrincipalDetailsOverlay.js +86 -0
  17. package/lib/components/principal/PrincipalSwitcherMenu.d.ts +14 -0
  18. package/lib/components/principal/PrincipalSwitcherMenu.js +287 -0
  19. package/lib/components/principal/index.d.ts +7 -0
  20. package/lib/components/principal/index.js +10 -0
  21. package/lib/components/sharing/ShareAccessComponent.d.ts +18 -0
  22. package/lib/components/sharing/ShareAccessComponent.js +1368 -0
  23. package/lib/components/sharing/SharingEditor.d.ts +20 -0
  24. package/lib/components/sharing/SharingEditor.js +65 -0
  25. package/lib/components/sharing/index.d.ts +9 -0
  26. package/lib/components/sharing/index.js +12 -0
  27. package/lib/hooks/index.d.ts +5 -0
  28. package/lib/hooks/index.js +5 -0
  29. package/lib/hooks/useBillableAccountStore.d.ts +31 -0
  30. package/lib/hooks/useBillableAccountStore.js +53 -0
  31. package/lib/hooks/usePrincipalStore.d.ts +41 -0
  32. package/lib/hooks/usePrincipalStore.js +101 -0
  33. package/lib/hooks/useSelectedBillableAccount.d.ts +14 -0
  34. package/lib/hooks/useSelectedBillableAccount.js +36 -0
  35. package/lib/hooks/useSelectedPrincipal.d.ts +17 -0
  36. package/lib/hooks/useSelectedPrincipal.js +29 -0
  37. package/lib/hooks/useUsageRefreshStore.d.ts +8 -0
  38. package/lib/hooks/useUsageRefreshStore.js +16 -0
  39. package/lib/utils/Handles.d.ts +3 -0
  40. package/lib/utils/Handles.js +53 -0
  41. package/lib/utils/index.d.ts +1 -0
  42. package/lib/utils/index.js +1 -0
  43. package/package.json +1 -3
@@ -0,0 +1,287 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2023-2025 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ import { useEffect, useMemo, useState } from 'react';
7
+ import { ActionList, ActionMenu, Box, Text } from '@primer/react';
8
+ import { OrganizationIcon, PeopleIcon, PersonIcon, } from '@primer/octicons-react';
9
+ import { useCache, useAuthorization } from '../../hooks/index.js';
10
+ import { useCoreStore } from '../../state/index.js';
11
+ import { useIAMStore } from '../../state/substates/index.js';
12
+ import { memberships as fetchMemberships } from '../../api/iam/profile.js';
13
+ import { usePrincipalStore } from '../../hooks/usePrincipalStore.js';
14
+ import { useBillableAccountStore } from '../../hooks/useBillableAccountStore.js';
15
+ import { useSelectedPrincipal } from '../../hooks/useSelectedPrincipal.js';
16
+ import { formatFriendlyHandle } from '../../utils/Handles.js';
17
+ function truncatePrincipalLabel(label, maxChars) {
18
+ const trimmed = (label || '').trim();
19
+ if (!trimmed) {
20
+ return '';
21
+ }
22
+ if (trimmed.length <= maxChars) {
23
+ return trimmed;
24
+ }
25
+ return `${trimmed.slice(0, Math.max(0, maxChars - 1))}…`;
26
+ }
27
+ /**
28
+ * PrincipalSwitcherMenu — the *only* component allowed to write to the
29
+ * principal store and the billable account store. It keeps both stores in
30
+ * sync per the rule:
31
+ * - selecting a user/org principal → billable account = same user/org
32
+ * - selecting a team principal → billable account = the team's parent org
33
+ */
34
+ export function PrincipalSwitcherMenu({ maxLabelChars = 48, fullWidth = true, showClosedBorder = true, }) {
35
+ const { user, token, iamRunUrl } = useIAMStore();
36
+ const { configuration } = useCoreStore();
37
+ const { checkIsPlatformAdmin } = useAuthorization();
38
+ const { useUserOrganizations } = useCache();
39
+ const organizationsQuery = useUserOrganizations();
40
+ const organizations = organizationsQuery.data || [];
41
+ const isOrganizationsLoading = organizationsQuery.isLoading;
42
+ const isPlatformAdmin = user ? checkIsPlatformAdmin(user) : false;
43
+ const [teams, setTeams] = useState([]);
44
+ const [teamsLoading, setTeamsLoading] = useState(false);
45
+ const selectUserPrincipal = usePrincipalStore(state => state.selectUserPrincipal);
46
+ const selectOrganizationPrincipal = usePrincipalStore(state => state.selectOrganizationPrincipal);
47
+ const selectTeamPrincipal = usePrincipalStore(state => state.selectTeamPrincipal);
48
+ const setBillableAccount = useBillableAccountStore(state => state.setBillableAccount);
49
+ const { selectedPrincipalKind, selectedPrincipalUid, selectedPrincipalHandle, selectedTeamParentOrganizationHandle, } = useSelectedPrincipal();
50
+ const personalUid = user?.uid || user?.id || '';
51
+ const personalHandle = user?.handle || '';
52
+ const selectUser = (uid, handle) => {
53
+ selectUserPrincipal(uid, handle);
54
+ setBillableAccount({ kind: 'user', uid, handle });
55
+ };
56
+ const selectOrganization = (uid, handle) => {
57
+ selectOrganizationPrincipal(uid, handle);
58
+ setBillableAccount({ kind: 'organization', uid, handle });
59
+ };
60
+ const selectTeam = (team, orgHandle) => {
61
+ if (!team.organizationUid) {
62
+ return;
63
+ }
64
+ selectTeamPrincipal({
65
+ teamUid: team.uid,
66
+ teamHandle: team.handle,
67
+ organizationUid: team.organizationUid,
68
+ organizationHandle: orgHandle,
69
+ });
70
+ setBillableAccount({
71
+ kind: 'organization',
72
+ uid: team.organizationUid,
73
+ handle: orgHandle,
74
+ });
75
+ };
76
+ const getOrganizationUid = (organization) => String(organization?.uid || organization?.id || '');
77
+ const selectedOrganization = useMemo(() => organizations.find((org) => getOrganizationUid(org) === selectedPrincipalUid), [organizations, selectedPrincipalUid]);
78
+ const selectedTeam = useMemo(() => teams.find(team => team.uid === selectedPrincipalUid), [teams, selectedPrincipalUid]);
79
+ useEffect(() => {
80
+ let cancelled = false;
81
+ const loadTeams = async () => {
82
+ if (!token) {
83
+ setTeams([]);
84
+ return;
85
+ }
86
+ setTeamsLoading(true);
87
+ try {
88
+ const baseUrl = iamRunUrl || configuration.iamRunUrl;
89
+ const response = await fetchMemberships(token, baseUrl);
90
+ const rawMemberships = Array.isArray(response?.memberships)
91
+ ? response.memberships
92
+ : [];
93
+ const mappedTeams = rawMemberships
94
+ .filter((membership) => membership?.type === 'team')
95
+ .map((membership) => ({
96
+ uid: String(membership?.uid || membership?.id || '').trim(),
97
+ handle: String(membership?.handle || '').trim(),
98
+ organizationUid: String(membership?.organization_uid || '').trim() || undefined,
99
+ organizationHandle: String(membership?.organization_handle ||
100
+ membership?.organization?.handle ||
101
+ '').trim() || undefined,
102
+ }))
103
+ .filter((team) => Boolean(team.uid && team.handle));
104
+ if (!cancelled) {
105
+ setTeams(mappedTeams);
106
+ }
107
+ }
108
+ catch {
109
+ if (!cancelled) {
110
+ setTeams([]);
111
+ }
112
+ }
113
+ finally {
114
+ if (!cancelled) {
115
+ setTeamsLoading(false);
116
+ }
117
+ }
118
+ };
119
+ void loadTeams();
120
+ return () => {
121
+ cancelled = true;
122
+ };
123
+ }, [token, iamRunUrl, configuration.iamRunUrl]);
124
+ useEffect(() => {
125
+ if (!personalUid || !personalHandle) {
126
+ return;
127
+ }
128
+ if (!selectedPrincipalUid) {
129
+ selectUser(personalUid, personalHandle);
130
+ return;
131
+ }
132
+ if (selectedPrincipalKind === 'organization' && isOrganizationsLoading) {
133
+ return;
134
+ }
135
+ if (selectedPrincipalKind === 'organization' && !selectedOrganization) {
136
+ selectUser(personalUid, personalHandle);
137
+ return;
138
+ }
139
+ if (selectedPrincipalKind === 'team' && teamsLoading) {
140
+ return;
141
+ }
142
+ if (selectedPrincipalKind === 'team' && !selectedTeam) {
143
+ selectUser(personalUid, personalHandle);
144
+ return;
145
+ }
146
+ if (selectedPrincipalKind === 'user' &&
147
+ selectedPrincipalUid !== personalUid) {
148
+ selectUser(personalUid, personalHandle);
149
+ }
150
+ // eslint-disable-next-line react-hooks/exhaustive-deps
151
+ }, [
152
+ personalUid,
153
+ personalHandle,
154
+ selectedPrincipalUid,
155
+ selectedPrincipalKind,
156
+ isOrganizationsLoading,
157
+ teamsLoading,
158
+ selectedOrganization,
159
+ selectedTeam,
160
+ ]);
161
+ const effectiveHandle = selectedPrincipalKind === 'organization'
162
+ ? selectedOrganization?.handle ||
163
+ selectedPrincipalHandle ||
164
+ personalHandle
165
+ : selectedPrincipalKind === 'team'
166
+ ? selectedTeam?.handle || selectedPrincipalHandle || personalHandle
167
+ : personalHandle;
168
+ const organizationHandleByUid = useMemo(() => {
169
+ const byUid = new Map();
170
+ for (const organization of organizations) {
171
+ const uid = getOrganizationUid(organization);
172
+ const handle = String(organization?.handle || '').trim();
173
+ if (uid && handle) {
174
+ byUid.set(uid, handle);
175
+ }
176
+ }
177
+ return byUid;
178
+ }, [organizations]);
179
+ const resolveTeamOrganizationHandle = (team) => {
180
+ if (!team) {
181
+ return '';
182
+ }
183
+ const directHandle = String(team.organizationHandle || '').trim();
184
+ if (directHandle) {
185
+ return directHandle;
186
+ }
187
+ const fromOrganizations = team.organizationUid
188
+ ? organizationHandleByUid.get(team.organizationUid) || ''
189
+ : '';
190
+ return fromOrganizations.trim();
191
+ };
192
+ const effectiveOrganizationHandle = selectedPrincipalKind === 'team'
193
+ ? resolveTeamOrganizationHandle(selectedTeam) ||
194
+ selectedTeamParentOrganizationHandle ||
195
+ ''
196
+ : '';
197
+ const selectedPrincipalLabel = selectedPrincipalKind === 'team'
198
+ ? `@${formatFriendlyHandle(effectiveOrganizationHandle || personalHandle || 'organization')}/${formatFriendlyHandle(effectiveHandle)}`
199
+ : `@${formatFriendlyHandle(effectiveHandle)}`;
200
+ const selectedPrincipalLabelClosed = truncatePrincipalLabel(selectedPrincipalLabel, maxLabelChars);
201
+ const isCurrentUserPrincipal = selectedPrincipalKind === 'user';
202
+ const selectedItemSx = {
203
+ bg: 'accent.subtle',
204
+ borderColor: 'accent.muted',
205
+ color: 'accent.fg',
206
+ fontWeight: 'semibold',
207
+ };
208
+ const adminBadgeSx = {
209
+ ml: 'auto',
210
+ px: 1,
211
+ py: '2px',
212
+ borderRadius: 999,
213
+ bg: 'attention.subtle',
214
+ color: 'attention.fg',
215
+ fontSize: 0,
216
+ fontWeight: 'semibold',
217
+ lineHeight: 1.2,
218
+ textTransform: 'lowercase',
219
+ };
220
+ return (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsxs(Box, { as: "button", type: "button", "aria-label": "Switch principal", sx: {
221
+ width: fullWidth ? '100%' : 'auto',
222
+ p: 2,
223
+ display: 'flex',
224
+ alignItems: 'center',
225
+ justifyContent: 'space-between',
226
+ gap: 2,
227
+ bg: showClosedBorder ? 'canvas.subtle' : 'transparent',
228
+ border: showClosedBorder ? '1px solid' : 'none',
229
+ borderColor: showClosedBorder ? 'border.default' : 'transparent',
230
+ borderRadius: 2,
231
+ cursor: 'pointer',
232
+ textAlign: 'left',
233
+ }, children: [_jsx(Box, { sx: {
234
+ display: 'inline-flex',
235
+ alignItems: 'center',
236
+ color: 'fg.muted',
237
+ flexShrink: 0,
238
+ }, children: selectedPrincipalKind === 'organization' ? (_jsx(OrganizationIcon, { size: 16 })) : selectedPrincipalKind === 'team' ? (_jsx(PeopleIcon, { size: 16 })) : (_jsx(PersonIcon, { size: 16 })) }), _jsx(Box, { sx: { minWidth: 0, flex: 1 }, children: _jsx(Text, { sx: {
239
+ display: 'block',
240
+ color: 'accent.fg',
241
+ fontWeight: 'semibold',
242
+ fontSize: 1,
243
+ whiteSpace: 'nowrap',
244
+ overflow: 'hidden',
245
+ textOverflow: 'ellipsis',
246
+ maxWidth: '100%',
247
+ }, children: selectedPrincipalLabelClosed }) }), isPlatformAdmin && isCurrentUserPrincipal ? (_jsx(Box, { sx: {
248
+ ml: 'auto',
249
+ flexShrink: 0,
250
+ display: 'inline-flex',
251
+ alignItems: 'center',
252
+ }, children: _jsx(Box, { as: "span", sx: adminBadgeSx, children: "admin" }) })) : null] }) }), _jsx(ActionMenu.Overlay, { width: "medium", children: _jsxs(ActionList, { children: [_jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "User" }), _jsxs(ActionList.Item, { disabled: isCurrentUserPrincipal, selected: isCurrentUserPrincipal, sx: isCurrentUserPrincipal ? selectedItemSx : undefined, onSelect: () => {
253
+ if (isCurrentUserPrincipal) {
254
+ return;
255
+ }
256
+ if (personalUid && personalHandle) {
257
+ selectUser(personalUid, personalHandle);
258
+ }
259
+ }, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(PersonIcon, {}) }), "@", formatFriendlyHandle(personalHandle || 'me'), isPlatformAdmin ? (_jsx(ActionList.TrailingVisual, { children: _jsx(Box, { as: "span", sx: adminBadgeSx, children: "admin" }) })) : null] })] }), _jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "Organizations" }), organizations.length === 0 ? (_jsx(ActionList.Item, { disabled: true, children: "No organizations" })) : (organizations.map((organization) => {
260
+ const organizationUid = getOrganizationUid(organization);
261
+ const isCurrentOrganizationPrincipal = selectedPrincipalKind === 'organization' &&
262
+ selectedPrincipalUid === organizationUid;
263
+ return (_jsxs(ActionList.Item, { disabled: isCurrentOrganizationPrincipal, selected: isCurrentOrganizationPrincipal, sx: isCurrentOrganizationPrincipal
264
+ ? selectedItemSx
265
+ : undefined, onSelect: () => {
266
+ if (isCurrentOrganizationPrincipal) {
267
+ return;
268
+ }
269
+ if (organizationUid && organization.handle) {
270
+ selectOrganization(organizationUid, organization.handle);
271
+ }
272
+ }, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(OrganizationIcon, {}) }), "@", organization.handle] }, organizationUid));
273
+ }))] }), _jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: "Teams" }), teams.length === 0 ? (_jsx(ActionList.Item, { disabled: true, children: "No teams" })) : (teams.map(team => {
274
+ const isCurrentTeamPrincipal = selectedPrincipalKind === 'team' &&
275
+ selectedPrincipalUid === team.uid;
276
+ const orgHandle = resolveTeamOrganizationHandle(team) ||
277
+ personalHandle ||
278
+ 'organization';
279
+ return (_jsxs(ActionList.Item, { disabled: isCurrentTeamPrincipal, selected: isCurrentTeamPrincipal, sx: isCurrentTeamPrincipal ? selectedItemSx : undefined, onSelect: () => {
280
+ if (isCurrentTeamPrincipal) {
281
+ return;
282
+ }
283
+ selectTeam(team, orgHandle);
284
+ }, children: [_jsx(ActionList.LeadingVisual, { children: _jsx(PeopleIcon, {}) }), "@", formatFriendlyHandle(orgHandle), "/", formatFriendlyHandle(team.handle)] }, team.uid));
285
+ }))] })] }) })] }));
286
+ }
287
+ export default PrincipalSwitcherMenu;
@@ -0,0 +1,7 @@
1
+ export * from './Principal';
2
+ export * from './PrincipalAvatar';
3
+ export * from './PrincipalBadge';
4
+ export * from './PrincipalBanner';
5
+ export { buildPrincipalProfilePath, PrincipalDetailsOverlay, } from './PrincipalDetailsOverlay';
6
+ export type { PrincipalDetailsOverlayProps } from './PrincipalDetailsOverlay';
7
+ export * from './PrincipalSwitcherMenu';
@@ -0,0 +1,10 @@
1
+ /*
2
+ * Copyright (c) 2023-2025 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export * from './Principal.js';
6
+ export * from './PrincipalAvatar.js';
7
+ export * from './PrincipalBadge.js';
8
+ export * from './PrincipalBanner.js';
9
+ export { buildPrincipalProfilePath, PrincipalDetailsOverlay, } from './PrincipalDetailsOverlay.js';
10
+ export * from './PrincipalSwitcherMenu.js';
@@ -0,0 +1,18 @@
1
+ export type ItemAccessLevel = 'view' | 'update' | 'execute';
2
+ type PrincipalKind = 'user' | 'team' | 'organization';
3
+ export type ShareAccessComponentProps = {
4
+ isOpen: boolean;
5
+ requestUrl?: string;
6
+ resourceLabel: string;
7
+ resourceName?: string;
8
+ resourceDescription?: string;
9
+ onSharingAccessRestrictedChange?: (restricted: boolean, message?: string) => void;
10
+ defaultAccessLevel?: ItemAccessLevel;
11
+ principalKinds?: readonly PrincipalKind[];
12
+ displayMode?: 'dialog' | 'inline';
13
+ onClose: () => void;
14
+ };
15
+ export declare function ShareAccessComponent({ isOpen, requestUrl, resourceLabel, resourceName, resourceDescription: _resourceDescription, onSharingAccessRestrictedChange, defaultAccessLevel, principalKinds, displayMode, onClose, }: ShareAccessComponentProps): JSX.Element | null;
16
+ export default ShareAccessComponent;
17
+ export declare const ShareAccessDialog: typeof ShareAccessComponent;
18
+ export type ShareAccessDialogProps = ShareAccessComponentProps;