@stapel/workspaces-react 0.16.0 → 0.19.0
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 +194 -0
- package/README.md +39 -8
- package/dist/api/generated/schema.d.ts +187 -60
- package/dist/api/generated/schema.d.ts.map +1 -1
- package/dist/default/ActiveWorkspace.d.ts +43 -0
- package/dist/default/ActiveWorkspace.d.ts.map +1 -0
- package/dist/default/ActiveWorkspace.js +33 -0
- package/dist/default/ActiveWorkspace.js.map +1 -0
- package/dist/default/AuditTrailPane.d.ts +12 -0
- package/dist/default/AuditTrailPane.d.ts.map +1 -0
- package/dist/default/AuditTrailPane.js +154 -0
- package/dist/default/AuditTrailPane.js.map +1 -0
- package/dist/default/InvitationsPane.d.ts +19 -0
- package/dist/default/InvitationsPane.d.ts.map +1 -0
- package/dist/default/InvitationsPane.js +217 -0
- package/dist/default/InvitationsPane.js.map +1 -0
- package/dist/default/InviteAcceptPage.d.ts +7 -0
- package/dist/default/InviteAcceptPage.d.ts.map +1 -1
- package/dist/default/InviteAcceptPage.js +74 -38
- package/dist/default/InviteAcceptPage.js.map +1 -1
- package/dist/default/MembersManager.d.ts +12 -4
- package/dist/default/MembersManager.d.ts.map +1 -1
- package/dist/default/MembersManager.js +320 -84
- package/dist/default/MembersManager.js.map +1 -1
- package/dist/default/RoleSelectField.d.ts +64 -0
- package/dist/default/RoleSelectField.d.ts.map +1 -0
- package/dist/default/RoleSelectField.js +78 -0
- package/dist/default/RoleSelectField.js.map +1 -0
- package/dist/default/WorkspaceSettings.d.ts +7 -1
- package/dist/default/WorkspaceSettings.d.ts.map +1 -1
- package/dist/default/WorkspaceSettings.js +203 -45
- package/dist/default/WorkspaceSettings.js.map +1 -1
- package/dist/default/WorkspacesPage.d.ts +13 -0
- package/dist/default/WorkspacesPage.d.ts.map +1 -0
- package/dist/default/WorkspacesPage.js +108 -0
- package/dist/default/WorkspacesPage.js.map +1 -0
- package/dist/default/index.d.ts +28 -7
- package/dist/default/index.d.ts.map +1 -1
- package/dist/default/index.js +24 -7
- package/dist/default/index.js.map +1 -1
- package/dist/default/parts.d.ts +104 -0
- package/dist/default/parts.d.ts.map +1 -0
- package/dist/default/parts.js +140 -0
- package/dist/default/parts.js.map +1 -0
- package/dist/headless/Members.d.ts +59 -14
- package/dist/headless/Members.d.ts.map +1 -1
- package/dist/headless/Members.js +35 -10
- package/dist/headless/Members.js.map +1 -1
- package/dist/headless/RoleSelect.d.ts +9 -3
- package/dist/headless/RoleSelect.d.ts.map +1 -1
- package/dist/headless/RoleSelect.js +3 -2
- package/dist/headless/RoleSelect.js.map +1 -1
- package/dist/i18n/es.d.ts +11 -12
- package/dist/i18n/es.d.ts.map +1 -1
- package/dist/i18n/es.js +267 -14
- package/dist/i18n/es.js.map +1 -1
- package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -1
- package/dist/i18n/generated/errors.es.gen.js +5 -0
- package/dist/i18n/generated/errors.es.gen.js.map +1 -1
- package/dist/i18n/generated/errors.gen.d.ts +30 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -1
- package/dist/i18n/generated/errors.gen.js +15 -0
- package/dist/i18n/generated/errors.gen.js.map +1 -1
- package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -1
- package/dist/i18n/generated/errors.ru.gen.js +5 -0
- package/dist/i18n/generated/errors.ru.gen.js.map +1 -1
- package/dist/i18n/keys.d.ts +209 -1
- package/dist/i18n/keys.d.ts.map +1 -1
- package/dist/i18n/keys.js +410 -4
- package/dist/i18n/keys.js.map +1 -1
- package/dist/i18n/ru.d.ts +7 -2
- package/dist/i18n/ru.d.ts.map +1 -1
- package/dist/i18n/ru.js +209 -4
- package/dist/i18n/ru.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/model/format.d.ts +56 -0
- package/dist/model/format.d.ts.map +1 -0
- package/dist/model/format.js +125 -0
- package/dist/model/format.js.map +1 -0
- package/dist/model/mandate.d.ts +23 -1
- package/dist/model/mandate.d.ts.map +1 -1
- package/dist/model/mandate.js +24 -0
- package/dist/model/mandate.js.map +1 -1
- package/dist/model/selection.d.ts +15 -0
- package/dist/model/selection.d.ts.map +1 -1
- package/dist/model/selection.js +18 -1
- package/dist/model/selection.js.map +1 -1
- package/dist/nav/manifest.d.ts +45 -0
- package/dist/nav/manifest.d.ts.map +1 -0
- package/dist/nav/manifest.js +77 -0
- package/dist/nav/manifest.js.map +1 -0
- package/llms.txt +11 -10
- package/manifest.json +282 -69
- package/package.json +18 -12
- package/src/analytics/generated/events.json +1 -1
- package/src/api/generated/schema.ts +187 -60
- package/src/default/ActiveWorkspace.tsx +97 -0
- package/src/default/AuditTrailPane.tsx +309 -0
- package/src/default/InvitationsPane.tsx +501 -0
- package/src/default/InviteAcceptPage.tsx +187 -90
- package/src/default/MembersManager.tsx +832 -222
- package/src/default/RoleSelectField.tsx +187 -0
- package/src/default/WorkspaceSettings.tsx +540 -125
- package/src/default/WorkspacesPage.tsx +387 -0
- package/src/default/index.ts +28 -7
- package/src/default/parts.tsx +308 -0
- package/src/headless/Members.tsx +84 -16
- package/src/headless/RoleSelect.tsx +12 -5
- package/src/i18n/es.ts +326 -14
- package/src/i18n/generated/errors.es.gen.ts +5 -0
- package/src/i18n/generated/errors.gen.ts +15 -0
- package/src/i18n/generated/errors.json +37 -0
- package/src/i18n/generated/errors.ru.gen.ts +5 -0
- package/src/i18n/keys.ts +462 -5
- package/src/i18n/ru.ts +259 -5
- package/src/index.ts +6 -1
- package/src/model/format.ts +158 -0
- package/src/model/mandate.ts +26 -1
- package/src/model/selection.tsx +19 -1
- package/src/nav/manifest.ts +120 -0
- package/dist/default/ErrorAlert.d.ts +0 -8
- package/dist/default/ErrorAlert.d.ts.map +0 -1
- package/dist/default/ErrorAlert.js +0 -30
- package/dist/default/ErrorAlert.js.map +0 -1
- package/src/default/ErrorAlert.tsx +0 -47
package/dist/default/index.d.ts
CHANGED
|
@@ -1,20 +1,41 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@stapel/workspaces-react/default` —
|
|
3
|
-
*
|
|
4
|
-
* separate entry point so consumers who bring their
|
|
5
|
-
* `antd` into their bundle; importing this subpath is
|
|
2
|
+
* `@stapel/workspaces-react/default` — this pair's DEFAULT SKIN: the antd
|
|
3
|
+
* screens a host mounts to have a working workspace product, not a set of
|
|
4
|
+
* examples (§83, §54). A separate entry point so consumers who bring their
|
|
5
|
+
* own visuals never pull `antd` into their bundle; importing this subpath is
|
|
6
|
+
* the opt-in.
|
|
7
|
+
*
|
|
8
|
+
* Every screen self-themes through `<SkinTheme>` from
|
|
9
|
+
* `@stapel/tokens-antd/skin`, so it is correct inside a dark document with no
|
|
10
|
+
* host wiring, and every one of them needs this pair's `<WorkspacesProvider>`
|
|
11
|
+
* plus core's `<I18nProvider>` above it.
|
|
6
12
|
*
|
|
7
13
|
* ```tsx
|
|
8
|
-
* import {
|
|
9
|
-
*
|
|
10
|
-
*
|
|
14
|
+
* import {
|
|
15
|
+
* WorkspacesPage, WorkspaceSettings, MembersManager,
|
|
16
|
+
* InvitationsPane, AuditTrailPane, InviteAcceptPage,
|
|
17
|
+
* } from "@stapel/workspaces-react/default";
|
|
18
|
+
*
|
|
19
|
+
* <WorkspacesPage onOpen={(ws) => navigate(`/w/${ws.slug}`)} />
|
|
20
|
+
* <WorkspaceSettings workspaceId={id} onDeleted={() => navigate("/")} />
|
|
11
21
|
* <MembersManager workspaceId={id} />
|
|
22
|
+
* <InvitationsPane workspaceId={id} />
|
|
23
|
+
* <AuditTrailPane workspaceId={id} />
|
|
24
|
+
* <InviteAcceptPage token={token} sessionEmail={email} />
|
|
12
25
|
* ```
|
|
13
26
|
*/
|
|
27
|
+
export { WorkspacesPage } from "./WorkspacesPage.js";
|
|
28
|
+
export type { WorkspacesPageProps } from "./WorkspacesPage.js";
|
|
14
29
|
export { WorkspaceSettings } from "./WorkspaceSettings.js";
|
|
15
30
|
export type { WorkspaceSettingsProps } from "./WorkspaceSettings.js";
|
|
16
31
|
export { MembersManager } from "./MembersManager.js";
|
|
17
32
|
export type { MembersManagerProps } from "./MembersManager.js";
|
|
33
|
+
export { InvitationsPane } from "./InvitationsPane.js";
|
|
34
|
+
export type { InvitationsPaneProps } from "./InvitationsPane.js";
|
|
35
|
+
export { AuditTrailPane } from "./AuditTrailPane.js";
|
|
36
|
+
export type { AuditTrailPaneProps } from "./AuditTrailPane.js";
|
|
37
|
+
export { RoleSelectField } from "./RoleSelectField.js";
|
|
38
|
+
export type { RoleSelectFieldProps } from "./RoleSelectField.js";
|
|
18
39
|
export { InviteAcceptPage } from "./InviteAcceptPage.js";
|
|
19
40
|
export type { InviteAcceptPageProps } from "./InviteAcceptPage.js";
|
|
20
41
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AACrE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AACzD,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC"}
|
package/dist/default/index.js
CHANGED
|
@@ -1,17 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@stapel/workspaces-react/default` —
|
|
3
|
-
*
|
|
4
|
-
* separate entry point so consumers who bring their
|
|
5
|
-
* `antd` into their bundle; importing this subpath is
|
|
2
|
+
* `@stapel/workspaces-react/default` — this pair's DEFAULT SKIN: the antd
|
|
3
|
+
* screens a host mounts to have a working workspace product, not a set of
|
|
4
|
+
* examples (§83, §54). A separate entry point so consumers who bring their
|
|
5
|
+
* own visuals never pull `antd` into their bundle; importing this subpath is
|
|
6
|
+
* the opt-in.
|
|
7
|
+
*
|
|
8
|
+
* Every screen self-themes through `<SkinTheme>` from
|
|
9
|
+
* `@stapel/tokens-antd/skin`, so it is correct inside a dark document with no
|
|
10
|
+
* host wiring, and every one of them needs this pair's `<WorkspacesProvider>`
|
|
11
|
+
* plus core's `<I18nProvider>` above it.
|
|
6
12
|
*
|
|
7
13
|
* ```tsx
|
|
8
|
-
* import {
|
|
9
|
-
*
|
|
10
|
-
*
|
|
14
|
+
* import {
|
|
15
|
+
* WorkspacesPage, WorkspaceSettings, MembersManager,
|
|
16
|
+
* InvitationsPane, AuditTrailPane, InviteAcceptPage,
|
|
17
|
+
* } from "@stapel/workspaces-react/default";
|
|
18
|
+
*
|
|
19
|
+
* <WorkspacesPage onOpen={(ws) => navigate(`/w/${ws.slug}`)} />
|
|
20
|
+
* <WorkspaceSettings workspaceId={id} onDeleted={() => navigate("/")} />
|
|
11
21
|
* <MembersManager workspaceId={id} />
|
|
22
|
+
* <InvitationsPane workspaceId={id} />
|
|
23
|
+
* <AuditTrailPane workspaceId={id} />
|
|
24
|
+
* <InviteAcceptPage token={token} sessionEmail={email} />
|
|
12
25
|
* ```
|
|
13
26
|
*/
|
|
27
|
+
export { WorkspacesPage } from "./WorkspacesPage.js";
|
|
14
28
|
export { WorkspaceSettings } from "./WorkspaceSettings.js";
|
|
15
29
|
export { MembersManager } from "./MembersManager.js";
|
|
30
|
+
export { InvitationsPane } from "./InvitationsPane.js";
|
|
31
|
+
export { AuditTrailPane } from "./AuditTrailPane.js";
|
|
32
|
+
export { RoleSelectField } from "./RoleSelectField.js";
|
|
16
33
|
export { InviteAcceptPage } from "./InviteAcceptPage.js";
|
|
17
34
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/default/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAE3D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { CSSProperties, ReactElement, ReactNode } from "react";
|
|
2
|
+
import type { ActionAvailability } from "@stapel/core";
|
|
3
|
+
/** The muted sentence that sits under a control or beside a value. */
|
|
4
|
+
export declare function Muted(props: {
|
|
5
|
+
readonly children: ReactNode;
|
|
6
|
+
readonly testId?: string | undefined;
|
|
7
|
+
}): ReactElement;
|
|
8
|
+
/**
|
|
9
|
+
* A person, drawn the same way on every screen of this pair: monogram, the
|
|
10
|
+
* name they go by, the address underneath.
|
|
11
|
+
*
|
|
12
|
+
* The rule the visual pass forced: an id never reaches the glass. A member
|
|
13
|
+
* whose profile has no name shows the invited ADDRESS as the primary line and
|
|
14
|
+
* says so, rather than showing a truncated UUID or an em dash.
|
|
15
|
+
*/
|
|
16
|
+
export declare function PersonLine(props: {
|
|
17
|
+
readonly name: string | null | undefined;
|
|
18
|
+
readonly email: string | null | undefined;
|
|
19
|
+
/** Rendered under the address — "Joined 3 days ago", a suspension reason. */
|
|
20
|
+
readonly caption?: ReactNode;
|
|
21
|
+
/** Tags beside the name (suspended, provisioned, two-factor). */
|
|
22
|
+
readonly tags?: ReactNode;
|
|
23
|
+
readonly testId?: string | undefined;
|
|
24
|
+
}): ReactElement;
|
|
25
|
+
/** A neutral status pill. `tone` picks antd's semantic colour, so it survives
|
|
26
|
+
* the dark swap — a hex here would not. */
|
|
27
|
+
export declare function StatusTag(props: {
|
|
28
|
+
readonly tone: "neutral" | "success" | "warning" | "danger" | "info";
|
|
29
|
+
readonly children: ReactNode;
|
|
30
|
+
readonly testId?: string | undefined;
|
|
31
|
+
}): ReactElement;
|
|
32
|
+
/**
|
|
33
|
+
* The anchor pager three screens share.
|
|
34
|
+
*
|
|
35
|
+
* Two buttons and a position, and nothing that pretends to be a page NUMBER
|
|
36
|
+
* the contract cannot supply: the wire addresses a page by the previous
|
|
37
|
+
* page's opaque cursor, so "jump to page 7" is not a question this API can be
|
|
38
|
+
* asked. The index shown is the count of steps THIS screen has taken, which
|
|
39
|
+
* is a true statement about the walk rather than about the roster.
|
|
40
|
+
*/
|
|
41
|
+
export declare function AnchorPager(props: {
|
|
42
|
+
readonly hasPrev: boolean;
|
|
43
|
+
readonly hasNext: boolean;
|
|
44
|
+
readonly onPrev: () => void;
|
|
45
|
+
readonly onNext: () => void;
|
|
46
|
+
readonly prevLabel: string;
|
|
47
|
+
readonly nextLabel: string;
|
|
48
|
+
readonly position: string;
|
|
49
|
+
readonly testId?: string | undefined;
|
|
50
|
+
}): ReactElement | null;
|
|
51
|
+
/** One control on a list row, with the verdict that decides whether it opens. */
|
|
52
|
+
export interface RowActionSpec {
|
|
53
|
+
readonly key: string;
|
|
54
|
+
readonly gate: ActionAvailability;
|
|
55
|
+
readonly label: ReactNode;
|
|
56
|
+
readonly onClick: () => void;
|
|
57
|
+
readonly danger?: boolean;
|
|
58
|
+
readonly testId?: string | undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* A list row's controls, and the refusal behind them stated ONCE.
|
|
62
|
+
*
|
|
63
|
+
* `GatedButton` prints the reason under every control it switches off, which
|
|
64
|
+
* is right for a lone button and wrong for a row: three refused controls print
|
|
65
|
+
* the same sentence three times, and a list of such rows becomes a wall of
|
|
66
|
+
* repeated apology — six copies of "This invitation was already accepted,
|
|
67
|
+
* declined, revoked or expired" on one 390px screen. The reason is a fact
|
|
68
|
+
* about the ROW, not about each button, so it is rendered once, as a footnote
|
|
69
|
+
* spanning the row, with every disabled control's `aria-describedby` pointing
|
|
70
|
+
* at it.
|
|
71
|
+
*
|
|
72
|
+
* Two nodes come back, meant to be direct children of the row's flex
|
|
73
|
+
* container: the controls, and the full-width footnote. Keeping the footnote
|
|
74
|
+
* OUT of the action column is what stops a refused row from growing a second
|
|
75
|
+
* geometry while its neighbours stay on one line.
|
|
76
|
+
*
|
|
77
|
+
* `reason` overrides the collected sentences where the screen has a better
|
|
78
|
+
* single one — a terminal invitation refuses three controls for one reason
|
|
79
|
+
* that the row's own status tag already names.
|
|
80
|
+
*/
|
|
81
|
+
export declare function RowActions(props: {
|
|
82
|
+
readonly actions: readonly RowActionSpec[];
|
|
83
|
+
readonly reason?: string | undefined;
|
|
84
|
+
readonly testId?: string | undefined;
|
|
85
|
+
}): ReactElement;
|
|
86
|
+
/**
|
|
87
|
+
* A failed read, said once, in a shape a 390px screen can hold.
|
|
88
|
+
*
|
|
89
|
+
* The substrate's `ErrorAlert` puts its retry in antd's `action` slot, which
|
|
90
|
+
* is a column beside the message — at phone width the sentence is squeezed
|
|
91
|
+
* into ~110px and breaks a word per line. Until the substrate stacks it
|
|
92
|
+
* (VC-B6, filed), the retry goes UNDER the alert here, where it has the whole
|
|
93
|
+
* width at any viewport.
|
|
94
|
+
*/
|
|
95
|
+
export declare function LoadFailure(props: {
|
|
96
|
+
readonly error: unknown;
|
|
97
|
+
readonly onRetry?: (() => void) | undefined;
|
|
98
|
+
readonly testId?: string | undefined;
|
|
99
|
+
}): ReactElement;
|
|
100
|
+
/** The gap between the stacked blocks of a screen. One number, one place. */
|
|
101
|
+
export declare const SCREEN_STACK: CSSProperties;
|
|
102
|
+
/** The gap between the fields inside one block. */
|
|
103
|
+
export declare const FIELD_STACK: CSSProperties;
|
|
104
|
+
//# sourceMappingURL=parts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parts.d.ts","sourceRoot":"","sources":["../../src/default/parts.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The small pieces every screen in this pair's default skin is built from.
|
|
4
|
+
*
|
|
5
|
+
* Not exported from `./index.ts`: these are the skin's own vocabulary, not
|
|
6
|
+
* product surfaces a host mounts. They live in one file because the visual
|
|
7
|
+
* pass found the same three things drawn differently on each screen — a
|
|
8
|
+
* person rendered as a bare email here and a truncated id there, a page walk
|
|
9
|
+
* with no way back, a muted caption at three different sizes.
|
|
10
|
+
*
|
|
11
|
+
* Every dimension is a `@stapel/tokens` step. Colour comes from antd's token
|
|
12
|
+
* layer through `SkinTheme`, never from a literal.
|
|
13
|
+
*/
|
|
14
|
+
import { useId } from "react";
|
|
15
|
+
import { Avatar, Button, Flex, Tag, Typography } from "antd";
|
|
16
|
+
import { STAPEL_UI_KEYS, useT } from "@stapel/core";
|
|
17
|
+
import { ErrorAlert } from "@stapel/tokens-antd/skin";
|
|
18
|
+
import { spacing, fontSize } from "@stapel/tokens";
|
|
19
|
+
/** The muted sentence that sits under a control or beside a value. */
|
|
20
|
+
export function Muted(props) {
|
|
21
|
+
return (_jsx(Typography.Text, { type: "secondary", style: { fontSize: fontSize.xs.fontSize }, ...(props.testId !== undefined ? { "data-testid": props.testId } : {}), children: props.children }));
|
|
22
|
+
}
|
|
23
|
+
/** One letter for a person with no picture. Never an id: the monogram of a
|
|
24
|
+
* name, or of the mailbox part of an address, and a neutral glyph when there
|
|
25
|
+
* is neither. */
|
|
26
|
+
function monogram(name, email) {
|
|
27
|
+
const source = (name ?? email ?? "").trim();
|
|
28
|
+
const first = source.charAt(0);
|
|
29
|
+
return first === "" ? "·" : first.toUpperCase();
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* A person, drawn the same way on every screen of this pair: monogram, the
|
|
33
|
+
* name they go by, the address underneath.
|
|
34
|
+
*
|
|
35
|
+
* The rule the visual pass forced: an id never reaches the glass. A member
|
|
36
|
+
* whose profile has no name shows the invited ADDRESS as the primary line and
|
|
37
|
+
* says so, rather than showing a truncated UUID or an em dash.
|
|
38
|
+
*/
|
|
39
|
+
export function PersonLine(props) {
|
|
40
|
+
const name = props.name?.trim() ? props.name.trim() : null;
|
|
41
|
+
const email = props.email?.trim() ? props.email.trim() : null;
|
|
42
|
+
const primary = name ?? email;
|
|
43
|
+
return (_jsxs(Flex, { gap: spacing["3"], align: "flex-start", ...(props.testId !== undefined ? { "data-testid": props.testId } : {}), children: [_jsx(Avatar, { size: "small", "aria-hidden": true, children: monogram(name, email) }), _jsxs(Flex, { vertical: true, gap: spacing["0"], style: { minWidth: 0 }, children: [_jsxs(Flex, { gap: spacing["2"], align: "center", wrap: true, children: [_jsx(Typography.Text, { strong: true, children: primary }), props.tags] }), name !== null && email !== null && _jsx(Muted, { children: email }), props.caption !== undefined && _jsx(Muted, { children: props.caption })] })] }));
|
|
44
|
+
}
|
|
45
|
+
/** A neutral status pill. `tone` picks antd's semantic colour, so it survives
|
|
46
|
+
* the dark swap — a hex here would not. */
|
|
47
|
+
export function StatusTag(props) {
|
|
48
|
+
const color = props.tone === "success"
|
|
49
|
+
? "success"
|
|
50
|
+
: props.tone === "warning"
|
|
51
|
+
? "warning"
|
|
52
|
+
: props.tone === "danger"
|
|
53
|
+
? "error"
|
|
54
|
+
: props.tone === "info"
|
|
55
|
+
? "processing"
|
|
56
|
+
: "default";
|
|
57
|
+
return (_jsx(Tag, { color: color, ...(props.testId !== undefined ? { "data-testid": props.testId } : {}), children: props.children }));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* The anchor pager three screens share.
|
|
61
|
+
*
|
|
62
|
+
* Two buttons and a position, and nothing that pretends to be a page NUMBER
|
|
63
|
+
* the contract cannot supply: the wire addresses a page by the previous
|
|
64
|
+
* page's opaque cursor, so "jump to page 7" is not a question this API can be
|
|
65
|
+
* asked. The index shown is the count of steps THIS screen has taken, which
|
|
66
|
+
* is a true statement about the walk rather than about the roster.
|
|
67
|
+
*/
|
|
68
|
+
export function AnchorPager(props) {
|
|
69
|
+
if (!props.hasPrev && !props.hasNext)
|
|
70
|
+
return null;
|
|
71
|
+
return (_jsxs(Flex, { justify: "space-between", align: "center", gap: spacing["3"], style: { marginTop: spacing["4"] }, ...(props.testId !== undefined ? { "data-testid": props.testId } : {}), children: [_jsx(Button, { size: "small", onClick: props.onPrev, disabled: !props.hasPrev, "data-disabled-reason": "first page of the walk \u2014 there is nothing before it", "data-analytics": "none", "data-analytics-reason": "local-ui-page-walk", "data-testid": "workspaces-pager-prev", children: props.prevLabel }), _jsx(Muted, { children: props.position }), _jsx(Button, { size: "small", onClick: props.onNext, disabled: !props.hasNext, "data-disabled-reason": "the server says this page is the last one", "data-analytics": "none", "data-analytics-reason": "local-ui-page-walk", "data-testid": "workspaces-pager-next", children: props.nextLabel })] }));
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* A list row's controls, and the refusal behind them stated ONCE.
|
|
75
|
+
*
|
|
76
|
+
* `GatedButton` prints the reason under every control it switches off, which
|
|
77
|
+
* is right for a lone button and wrong for a row: three refused controls print
|
|
78
|
+
* the same sentence three times, and a list of such rows becomes a wall of
|
|
79
|
+
* repeated apology — six copies of "This invitation was already accepted,
|
|
80
|
+
* declined, revoked or expired" on one 390px screen. The reason is a fact
|
|
81
|
+
* about the ROW, not about each button, so it is rendered once, as a footnote
|
|
82
|
+
* spanning the row, with every disabled control's `aria-describedby` pointing
|
|
83
|
+
* at it.
|
|
84
|
+
*
|
|
85
|
+
* Two nodes come back, meant to be direct children of the row's flex
|
|
86
|
+
* container: the controls, and the full-width footnote. Keeping the footnote
|
|
87
|
+
* OUT of the action column is what stops a refused row from growing a second
|
|
88
|
+
* geometry while its neighbours stay on one line.
|
|
89
|
+
*
|
|
90
|
+
* `reason` overrides the collected sentences where the screen has a better
|
|
91
|
+
* single one — a terminal invitation refuses three controls for one reason
|
|
92
|
+
* that the row's own status tag already names.
|
|
93
|
+
*/
|
|
94
|
+
export function RowActions(props) {
|
|
95
|
+
const t = useT();
|
|
96
|
+
const reasonId = useId();
|
|
97
|
+
const collected = [];
|
|
98
|
+
if (props.reason !== undefined) {
|
|
99
|
+
collected.push(props.reason);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const seen = new Set();
|
|
103
|
+
for (const action of props.actions) {
|
|
104
|
+
if (action.gate.available)
|
|
105
|
+
continue;
|
|
106
|
+
if (seen.has(action.gate.block.code))
|
|
107
|
+
continue;
|
|
108
|
+
seen.add(action.gate.block.code);
|
|
109
|
+
collected.push(t(action.gate.block.code, action.gate.block.params));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const anyBlocked = props.actions.some((action) => !action.gate.available);
|
|
113
|
+
return (_jsxs(_Fragment, { children: [_jsx(Flex, { gap: spacing["2"], align: "center", wrap: true, children: props.actions.map((action) => (_jsx(Button, { type: "link", size: "small", danger: action.danger === true, disabled: !action.gate.available, ...(anyBlocked && !action.gate.available
|
|
114
|
+
? { "aria-describedby": reasonId }
|
|
115
|
+
: {}), onClick: action.onClick, "data-analytics": "none", "data-analytics-reason": "passthrough \u2014 the caller's onClick carries the tracked action", ...(action.testId !== undefined ? { "data-testid": action.testId } : {}), children: action.label }, action.key))) }), collected.length > 0 && (_jsx("div", { id: reasonId, "data-stapel-gated-reason": "", style: { flexBasis: "100%", minWidth: 0 }, ...(props.testId !== undefined ? { "data-testid": props.testId } : {}), children: collected.map((sentence) => (_jsx("div", { children: _jsx(Muted, { children: sentence }) }, sentence))) }))] }));
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* A failed read, said once, in a shape a 390px screen can hold.
|
|
119
|
+
*
|
|
120
|
+
* The substrate's `ErrorAlert` puts its retry in antd's `action` slot, which
|
|
121
|
+
* is a column beside the message — at phone width the sentence is squeezed
|
|
122
|
+
* into ~110px and breaks a word per line. Until the substrate stacks it
|
|
123
|
+
* (VC-B6, filed), the retry goes UNDER the alert here, where it has the whole
|
|
124
|
+
* width at any viewport.
|
|
125
|
+
*/
|
|
126
|
+
export function LoadFailure(props) {
|
|
127
|
+
const t = useT();
|
|
128
|
+
return (_jsxs(Flex, { vertical: true, gap: spacing["3"], align: "flex-start", children: [_jsx(ErrorAlert, { thrown: props.error, style: { width: "100%" }, ...(props.testId !== undefined ? { testId: props.testId } : {}) }), props.onRetry !== undefined && (_jsx(Button, { onClick: props.onRetry, "data-analytics": "none", "data-analytics-reason": "retry of a failed read; the read hook reports its own outcome", "data-testid": props.testId !== undefined ? `${props.testId}-retry` : undefined, children: t(STAPEL_UI_KEYS.retry) }))] }));
|
|
129
|
+
}
|
|
130
|
+
/** The gap between the stacked blocks of a screen. One number, one place. */
|
|
131
|
+
export const SCREEN_STACK = {
|
|
132
|
+
display: "grid",
|
|
133
|
+
gap: spacing["4"],
|
|
134
|
+
};
|
|
135
|
+
/** The gap between the fields inside one block. */
|
|
136
|
+
export const FIELD_STACK = {
|
|
137
|
+
display: "grid",
|
|
138
|
+
gap: spacing["3"],
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=parts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parts.js","sourceRoot":"","sources":["../../src/default/parts.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,22 +1,56 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import type { LoadState, StapelApiError } from "@stapel/core";
|
|
3
|
-
import type { Member, MemberInvite } from "../api/types.js";
|
|
4
|
-
import type { MemberRoleChange } from "../model/mutations.js";
|
|
3
|
+
import type { Member, MemberInvite, MembersParams } from "../api/types.js";
|
|
4
|
+
import type { MemberRename, MemberRoleChange } from "../model/mutations.js";
|
|
5
|
+
/**
|
|
6
|
+
* Where in the anchor walk the current page sits — the half of the roster a
|
|
7
|
+
* pager needs and a count of rows cannot supply.
|
|
8
|
+
*
|
|
9
|
+
* ANCHOR, not offset: a page is addressed by the opaque `next_anchor` of the
|
|
10
|
+
* page before it, so a member removed while an admin reads never skews the
|
|
11
|
+
* next page. There is no "page 3" to jump to, which is why the shape carries
|
|
12
|
+
* two cursors and no index.
|
|
13
|
+
*/
|
|
14
|
+
export interface MembersPageInfo {
|
|
15
|
+
readonly hasNext: boolean;
|
|
16
|
+
readonly hasPrev: boolean;
|
|
17
|
+
/** Pass as `params.anchor` with `direction: "next"` to walk forward. */
|
|
18
|
+
readonly nextAnchor: string | null;
|
|
19
|
+
/** Pass as `params.anchor` with `direction: "prev"` to walk back. */
|
|
20
|
+
readonly prevAnchor: string | null;
|
|
21
|
+
/** Rows on THIS page. Never a count of the roster — see `rosterComplete`. */
|
|
22
|
+
readonly count: number;
|
|
23
|
+
}
|
|
5
24
|
/** Render-prop bag for {@link Members}. */
|
|
6
25
|
export interface MembersBag {
|
|
7
26
|
/**
|
|
8
27
|
* The roster read, as a state a skin cannot flatten — render it with core's
|
|
9
|
-
* `matchList`. This is
|
|
10
|
-
*
|
|
11
|
-
*
|
|
28
|
+
* `matchList`. This is ONE page: which one is decided by the `params` the
|
|
29
|
+
* caller passes (default: the newest page, limit 100), and {@link page}
|
|
30
|
+
* says whether there is another.
|
|
12
31
|
*
|
|
13
32
|
* Separate from {@link writeError} on purpose: "the roster could not be
|
|
14
33
|
* loaded" and "your invite was rejected" are different sentences, and the
|
|
15
34
|
* old merged `isError` could only produce one of them.
|
|
16
35
|
*/
|
|
17
36
|
readonly state: LoadState<readonly Member[]>;
|
|
18
|
-
/**
|
|
19
|
-
* {@link state}
|
|
37
|
+
/**
|
|
38
|
+
* Whether {@link state} holds the WHOLE roster — the read succeeded and the
|
|
39
|
+
* page reports no `has_next`.
|
|
40
|
+
*
|
|
41
|
+
* A skin may only reason about the roster as a set — "is this the last
|
|
42
|
+
* owner?", "how many admins are there?" — while this is `true`. On one page
|
|
43
|
+
* of a longer roster those counts are counts of a page, and a control gated
|
|
44
|
+
* on one would refuse a removal the backend would have allowed, which is a
|
|
45
|
+
* worse defect than the ungated button it replaced. `false` while the read
|
|
46
|
+
* is loading or failed: an unknown roster is never a complete one.
|
|
47
|
+
*/
|
|
48
|
+
readonly rosterComplete: boolean;
|
|
49
|
+
/** The walk position, or `null` while the page has not arrived. A pager
|
|
50
|
+
* with no page cannot claim there is a next one. */
|
|
51
|
+
readonly page: MembersPageInfo | null;
|
|
52
|
+
/** An invite, a role change, a rename or a removal failed. Never the read —
|
|
53
|
+
* that is {@link state}. */
|
|
20
54
|
readonly writeError: StapelApiError | null;
|
|
21
55
|
/** Invite one or more emails at a role. */
|
|
22
56
|
invite(body: MemberInvite): void;
|
|
@@ -26,6 +60,11 @@ export interface MembersBag {
|
|
|
26
60
|
updateRole(change: MemberRoleChange): void;
|
|
27
61
|
/** A role-change call is in flight. */
|
|
28
62
|
readonly isUpdatingRole: boolean;
|
|
63
|
+
/** Correct a member's display name — the CANONICAL one (stapel-profiles),
|
|
64
|
+
* not a workspace-local note. `displayName: null` clears it. */
|
|
65
|
+
rename(change: MemberRename): void;
|
|
66
|
+
/** A rename call is in flight. */
|
|
67
|
+
readonly isRenaming: boolean;
|
|
29
68
|
/** Remove a member by `userId`. */
|
|
30
69
|
remove(userId: string): void;
|
|
31
70
|
/** A removal call is in flight. */
|
|
@@ -34,20 +73,26 @@ export interface MembersBag {
|
|
|
34
73
|
refetch(): void;
|
|
35
74
|
}
|
|
36
75
|
/**
|
|
37
|
-
* Headless member roster — a renderless view + invite / role-change /
|
|
38
|
-
* controls over one workspace's members. Wires {@link useMembers},
|
|
39
|
-
* {@link useInviteMembers}, {@link useUpdateMemberRole},
|
|
40
|
-
* {@link
|
|
41
|
-
* own roster / invite form / role
|
|
76
|
+
* Headless member roster — a renderless view + invite / role-change / rename /
|
|
77
|
+
* removal controls over one workspace's members. Wires {@link useMembers},
|
|
78
|
+
* {@link useInviteMembers}, {@link useUpdateMemberRole},
|
|
79
|
+
* {@link useRenameMember} and {@link useRemoveMember} and hands a
|
|
80
|
+
* {@link MembersBag} to `children`; bring your own roster / invite form / role
|
|
81
|
+
* menu. Zero visual opinion (frontend-standard §2).
|
|
82
|
+
*
|
|
83
|
+
* `params` is the anchor page to read — the caller owns the walk, because the
|
|
84
|
+
* cursor belongs to the screen's state, not to this component's. Omitted, it
|
|
85
|
+
* reads the newest page, exactly as before.
|
|
42
86
|
*
|
|
43
87
|
* ```tsx
|
|
44
|
-
* <Members workspaceId={id}>
|
|
45
|
-
* {({ state, invite, updateRole, remove }) => ( ... )}
|
|
88
|
+
* <Members workspaceId={id} params={{ anchor, direction: "next", search }}>
|
|
89
|
+
* {({ state, page, invite, updateRole, remove }) => ( ... )}
|
|
46
90
|
* </Members>
|
|
47
91
|
* ```
|
|
48
92
|
*/
|
|
49
93
|
export declare function Members(props: {
|
|
50
94
|
workspaceId: string;
|
|
95
|
+
params?: MembersParams;
|
|
51
96
|
children: (bag: MembersBag) => ReactNode;
|
|
52
97
|
}): ReactNode;
|
|
53
98
|
//# sourceMappingURL=Members.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Members.d.ts","sourceRoot":"","sources":["../../src/headless/Members.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"Members.d.ts","sourceRoot":"","sources":["../../src/headless/Members.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAQ3E,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE5E;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,qEAAqE;IACrE,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,2CAA2C;AAC3C,MAAM,WAAW,UAAU;IACzB;;;;;;;;;OASG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IAC7C;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;wDACoD;IACpD,QAAQ,CAAC,IAAI,EAAE,eAAe,GAAG,IAAI,CAAC;IACtC;gCAC4B;IAC5B,QAAQ,CAAC,UAAU,EAAE,cAAc,GAAG,IAAI,CAAC;IAC3C,2CAA2C;IAC3C,MAAM,CAAC,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;IACjC,mCAAmC;IACnC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,8BAA8B;IAC9B,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC3C,uCAAuC;IACvC,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;oEACgE;IAChE,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IACnC,kCAAkC;IAClC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,mCAAmC;IACnC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,mCAAmC;IACnC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,+CAA+C;IAC/C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,SAAS,CAAC;CAC1C,GAAG,SAAS,CA8CZ"}
|
package/dist/headless/Members.js
CHANGED
|
@@ -1,27 +1,48 @@
|
|
|
1
1
|
import { loadStateFromQuery, mapLoad } from "@stapel/core";
|
|
2
2
|
import { useMembers } from "../model/queries.js";
|
|
3
|
-
import { useInviteMembers, useRemoveMember, useUpdateMemberRole, } from "../model/mutations.js";
|
|
3
|
+
import { useInviteMembers, useRemoveMember, useRenameMember, useUpdateMemberRole, } from "../model/mutations.js";
|
|
4
4
|
/**
|
|
5
|
-
* Headless member roster — a renderless view + invite / role-change /
|
|
6
|
-
* controls over one workspace's members. Wires {@link useMembers},
|
|
7
|
-
* {@link useInviteMembers}, {@link useUpdateMemberRole},
|
|
8
|
-
* {@link
|
|
9
|
-
* own roster / invite form / role
|
|
5
|
+
* Headless member roster — a renderless view + invite / role-change / rename /
|
|
6
|
+
* removal controls over one workspace's members. Wires {@link useMembers},
|
|
7
|
+
* {@link useInviteMembers}, {@link useUpdateMemberRole},
|
|
8
|
+
* {@link useRenameMember} and {@link useRemoveMember} and hands a
|
|
9
|
+
* {@link MembersBag} to `children`; bring your own roster / invite form / role
|
|
10
|
+
* menu. Zero visual opinion (frontend-standard §2).
|
|
11
|
+
*
|
|
12
|
+
* `params` is the anchor page to read — the caller owns the walk, because the
|
|
13
|
+
* cursor belongs to the screen's state, not to this component's. Omitted, it
|
|
14
|
+
* reads the newest page, exactly as before.
|
|
10
15
|
*
|
|
11
16
|
* ```tsx
|
|
12
|
-
* <Members workspaceId={id}>
|
|
13
|
-
* {({ state, invite, updateRole, remove }) => ( ... )}
|
|
17
|
+
* <Members workspaceId={id} params={{ anchor, direction: "next", search }}>
|
|
18
|
+
* {({ state, page, invite, updateRole, remove }) => ( ... )}
|
|
14
19
|
* </Members>
|
|
15
20
|
* ```
|
|
16
21
|
*/
|
|
17
22
|
export function Members(props) {
|
|
18
|
-
const query = useMembers(props.workspaceId);
|
|
23
|
+
const query = useMembers(props.workspaceId, props.params);
|
|
19
24
|
const inviteMutation = useInviteMembers(props.workspaceId);
|
|
20
25
|
const roleMutation = useUpdateMemberRole(props.workspaceId);
|
|
26
|
+
const renameMutation = useRenameMember(props.workspaceId);
|
|
21
27
|
const removeMutation = useRemoveMember(props.workspaceId);
|
|
28
|
+
const data = query.data;
|
|
22
29
|
return props.children({
|
|
23
30
|
state: mapLoad(loadStateFromQuery(query), (page) => page.items),
|
|
24
|
-
|
|
31
|
+
rosterComplete: data !== undefined && !data.has_next && !data.has_prev,
|
|
32
|
+
page: data === undefined
|
|
33
|
+
? null
|
|
34
|
+
: {
|
|
35
|
+
hasNext: data.has_next,
|
|
36
|
+
hasPrev: data.has_prev,
|
|
37
|
+
nextAnchor: data.next_anchor ?? null,
|
|
38
|
+
prevAnchor: data.prev_anchor ?? null,
|
|
39
|
+
count: data.count,
|
|
40
|
+
},
|
|
41
|
+
writeError: inviteMutation.error ??
|
|
42
|
+
roleMutation.error ??
|
|
43
|
+
renameMutation.error ??
|
|
44
|
+
removeMutation.error ??
|
|
45
|
+
null,
|
|
25
46
|
invite: (body) => {
|
|
26
47
|
inviteMutation.mutate(body);
|
|
27
48
|
},
|
|
@@ -30,6 +51,10 @@ export function Members(props) {
|
|
|
30
51
|
roleMutation.mutate(change);
|
|
31
52
|
},
|
|
32
53
|
isUpdatingRole: roleMutation.isPending,
|
|
54
|
+
rename: (change) => {
|
|
55
|
+
renameMutation.mutate(change);
|
|
56
|
+
},
|
|
57
|
+
isRenaming: renameMutation.isPending,
|
|
33
58
|
remove: (userId) => {
|
|
34
59
|
removeMutation.mutate(userId);
|
|
35
60
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Members.js","sourceRoot":"","sources":["../../src/headless/Members.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"Members.js","sourceRoot":"","sources":["../../src/headless/Members.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EACL,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AA2E/B;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,OAAO,CAAC,KAIvB;IACC,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,WAAW,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC1D,MAAM,cAAc,GAAG,gBAAgB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC3D,MAAM,YAAY,GAAG,mBAAmB,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5D,MAAM,cAAc,GAAG,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC1D,MAAM,cAAc,GAAG,eAAe,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC;IACxB,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,KAAK,EAAE,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC;QAC/D,cAAc,EAAE,IAAI,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI,CAAC,IAAI,CAAC,QAAQ;QACtE,IAAI,EACF,IAAI,KAAK,SAAS;YAChB,CAAC,CAAC,IAAI;YACN,CAAC,CAAC;gBACE,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,OAAO,EAAE,IAAI,CAAC,QAAQ;gBACtB,UAAU,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;gBACpC,UAAU,EAAE,IAAI,CAAC,WAAW,IAAI,IAAI;gBACpC,KAAK,EAAE,IAAI,CAAC,KAAK;aAClB;QACP,UAAU,EACR,cAAc,CAAC,KAAK;YACpB,YAAY,CAAC,KAAK;YAClB,cAAc,CAAC,KAAK;YACpB,cAAc,CAAC,KAAK;YACpB,IAAI;QACN,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACf,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9B,CAAC;QACD,UAAU,EAAE,cAAc,CAAC,SAAS;QACpC,UAAU,EAAE,CAAC,MAAM,EAAE,EAAE;YACrB,YAAY,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;QACD,cAAc,EAAE,YAAY,CAAC,SAAS;QACtC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;YACjB,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;QACD,UAAU,EAAE,cAAc,CAAC,SAAS;QACpC,MAAM,EAAE,CAAC,MAAM,EAAE,EAAE;YACjB,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChC,CAAC;QACD,UAAU,EAAE,cAAc,CAAC,SAAS;QACpC,OAAO,EAAE,GAAG,EAAE;YACZ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -12,9 +12,15 @@ export interface RoleSelectBag {
|
|
|
12
12
|
* Display label for a role key: the i18n key `workspaces.role.<key>` when
|
|
13
13
|
* the current locale's merged bundle carries it (the pair ships the builtin
|
|
14
14
|
* four; a client bundle merges its own — e.g. `workspaces.role.secretary`),
|
|
15
|
-
* else the
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* else the role key TITLE-CASED (`secretary` → `Secretary`, `site_admin` →
|
|
16
|
+
* `Site admin`).
|
|
17
|
+
*
|
|
18
|
+
* Two things this fallback is not. It is never the dotted i18n key: a
|
|
19
|
+
* deployment-defined role without a translation must still be pickable. And
|
|
20
|
+
* since the 2026-08-24 visual pass it is no longer the raw token either —
|
|
21
|
+
* `secretary` rendered lowercase in a list of title-cased builtin roles,
|
|
22
|
+
* which reads as broken data rather than as a missing translation. The
|
|
23
|
+
* label is cosmetic; the VALUE stays the registry key everywhere it is sent.
|
|
18
24
|
*/
|
|
19
25
|
labelFor(role: string): string;
|
|
20
26
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RoleSelect.d.ts","sourceRoot":"","sources":["../../src/headless/RoleSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"RoleSelect.d.ts","sourceRoot":"","sources":["../../src/headless/RoleSelect.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAIhD,8CAA8C;AAC9C,MAAM,WAAW,aAAa;IAC5B;;;gEAG4D;IAC5D,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,QAAQ,EAAE,CAAC,CAAC;IAC/C;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE;IAChC,QAAQ,EAAE,CAAC,GAAG,EAAE,aAAa,KAAK,SAAS,CAAC;CAC7C,GAAG,SAAS,CAWZ"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { loadStateFromQuery, useI18n } from "@stapel/core";
|
|
2
|
+
import { titleCaseKey } from "../model/format.js";
|
|
2
3
|
import { useRoles } from "../model/queries.js";
|
|
3
4
|
/**
|
|
4
5
|
* Headless role picker over the effective role registry (org-program §A2) —
|
|
@@ -28,11 +29,11 @@ export function RoleSelect(props) {
|
|
|
28
29
|
const i18n = useI18n();
|
|
29
30
|
// Raw bundle lookup, NOT `t()`: `t` falls back to the key itself, which is
|
|
30
31
|
// exactly the wrong fallback here (a deployment role without a translation
|
|
31
|
-
// must show
|
|
32
|
+
// must show a word, not `workspaces.role.secretary`).
|
|
32
33
|
const bundle = i18n.getBundle();
|
|
33
34
|
return props.children({
|
|
34
35
|
state: loadStateFromQuery(query),
|
|
35
|
-
labelFor: (role) => bundle[`workspaces.role.${role}`] ?? role,
|
|
36
|
+
labelFor: (role) => bundle[`workspaces.role.${role}`] ?? titleCaseKey(role),
|
|
36
37
|
});
|
|
37
38
|
}
|
|
38
39
|
//# sourceMappingURL=RoleSelect.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RoleSelect.js","sourceRoot":"","sources":["../../src/headless/RoleSelect.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"RoleSelect.js","sourceRoot":"","sources":["../../src/headless/RoleSelect.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AA0B/C;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,UAAU,CAAC,KAE1B;IACC,MAAM,KAAK,GAAG,QAAQ,EAAE,CAAC;IACzB,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,2EAA2E;IAC3E,2EAA2E;IAC3E,sDAAsD;IACtD,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;IAChC,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,KAAK,EAAE,kBAAkB,CAAC,KAAK,CAAC;QAChC,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,mBAAmB,IAAI,EAAE,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC;KAC5E,CAAC,CAAC;AACL,CAAC"}
|