@parallelworks/ui 0.54.0 → 0.56.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/README.md +65 -26
- package/dist/access/AccessDrawer.d.ts +30 -19
- package/dist/access/AccessDrawer.js +92 -67
- package/dist/access/AccessList.d.ts +17 -16
- package/dist/access/AccessList.js +218 -114
- package/dist/access/GrantView.d.ts +9 -8
- package/dist/access/GrantView.js +34 -36
- package/dist/access/PrincipalPicker.d.ts +38 -0
- package/dist/access/{GroupPicker.js → PrincipalPicker.js} +173 -80
- package/dist/access/index.d.ts +1 -1
- package/dist/access/model.d.ts +67 -24
- package/dist/access/model.js +111 -47
- package/dist/access/strings.d.ts +63 -39
- package/dist/access/strings.js +114 -84
- package/dist/access/types.d.ts +32 -14
- package/dist/ai/adapter/mock/mockAdapter.js +1 -1
- package/dist/ai/core/useSSEStream.d.ts +1 -1
- package/dist/components/Checkbox.js +1 -1
- package/dist/components/Combobox.js +25 -20
- package/dist/components/DatePicker.js +62 -58
- package/dist/components/Drawer.js +3 -1
- package/dist/components/Field.d.ts +8 -3
- package/dist/components/Field.js +32 -12
- package/dist/components/HoverCard.js +15 -10
- package/dist/components/InfoTip.d.ts +3 -1
- package/dist/components/InfoTip.js +2 -1
- package/dist/components/Menu.js +3 -0
- package/dist/components/Modal.js +32 -27
- package/dist/components/Popover.d.ts +4 -1
- package/dist/components/Popover.js +31 -26
- package/dist/components/Radio.js +1 -1
- package/dist/components/Select.js +47 -42
- package/dist/components/Slider.js +3 -3
- package/dist/components/Switch.js +1 -1
- package/dist/components/Tooltip.js +16 -11
- package/dist/components/fieldBox.js +1 -1
- package/dist/components/fullscreen.d.ts +4 -0
- package/dist/components/fullscreen.js +18 -0
- package/dist/editor/EditorField.js +1 -2
- package/dist/editor/Monaco.js +26 -2
- package/dist/editor/themes.js +1 -1
- package/dist/graph/GraphEditorDialogs.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/styles/theme.css +10 -10
- package/dist/styles.css +1 -1
- package/dist/theme/derive.d.ts +2 -0
- package/dist/theme/derive.js +25 -14
- package/package.json +2 -2
- package/dist/access/GroupPicker.d.ts +0 -35
package/dist/access/strings.js
CHANGED
|
@@ -4,86 +4,118 @@ import { useMemo } from "react";
|
|
|
4
4
|
//#region src/access/strings.tsx
|
|
5
5
|
var plural = new Intl.PluralRules("en");
|
|
6
6
|
var list = new Intl.ListFormat("en", { type: "conjunction" });
|
|
7
|
+
/** Counts side by side, as a label lists them: "Everyone, 3 people, 7 groups". */
|
|
8
|
+
var units = new Intl.ListFormat("en", {
|
|
9
|
+
type: "unit",
|
|
10
|
+
style: "short"
|
|
11
|
+
});
|
|
7
12
|
/** A count and its noun: "1 group", "1,024 groups". */
|
|
8
13
|
var countOf = (n, one, other) => `${n.toLocaleString()} ${plural.select(n) === "one" ? one : other}`;
|
|
9
14
|
var and = (names) => list.format(names);
|
|
10
15
|
var has = (names) => names.length === 1 ? "has" : "have";
|
|
16
|
+
var who = (people, groups, everyone = false) => [
|
|
17
|
+
everyone && "Everyone",
|
|
18
|
+
people > 0 && countOf(people, "person", "people"),
|
|
19
|
+
groups > 0 && countOf(groups, "group", "groups")
|
|
20
|
+
].filter((part) => typeof part === "string");
|
|
11
21
|
var ACCESS_STRINGS = {
|
|
12
22
|
title: "Access",
|
|
13
|
-
/** Under the title: the resource, and
|
|
14
|
-
summary: (resource, groups, everyone) =>
|
|
23
|
+
/** Under the title: the resource, and who has a role on it. */
|
|
24
|
+
summary: (resource, people, groups, everyone) => {
|
|
25
|
+
const parts = who(people, groups, everyone);
|
|
26
|
+
return `${resource} · ${parts.length === 0 ? "No access granted" : units.format(parts)}`;
|
|
27
|
+
},
|
|
15
28
|
/** Names the switch between the two ways of listing who has access. */
|
|
16
29
|
arrange: "List access",
|
|
17
30
|
byRole: "By role",
|
|
18
|
-
|
|
19
|
-
filter: "Filter by role or group",
|
|
31
|
+
byPrincipal: "By person or group",
|
|
32
|
+
filter: "Filter by role, person or group",
|
|
20
33
|
grantAccess: "Grant access",
|
|
21
|
-
|
|
22
|
-
loading: "Loading
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
34
|
+
privileged: "Privileged",
|
|
35
|
+
loading: "Loading access",
|
|
36
|
+
/** Who holds a role: "1 person, 2 groups". */
|
|
37
|
+
holders: (people, groups) => units.format(who(people, groups)),
|
|
38
|
+
/** How many of a role's holders a filter left. */
|
|
39
|
+
holdersShown: (shown, total) => `${shown.toLocaleString()} of ${total.toLocaleString()}`,
|
|
26
40
|
members: (n) => countOf(n, "member", "members"),
|
|
27
41
|
people: (n) => countOf(n, "person", "people"),
|
|
28
42
|
/** Everyone, where it is short: a tag or a picker row. */
|
|
29
43
|
everyone: "Everyone",
|
|
30
44
|
/** Everyone, where it stands as a row of its own. */
|
|
31
45
|
everyoneInOrganization: "Everyone in your organization",
|
|
32
|
-
everyoneJoinLater: "
|
|
33
|
-
|
|
46
|
+
everyoneJoinLater: "Includes people who join later",
|
|
47
|
+
owner: "Owner",
|
|
48
|
+
/** Under the owner section and row. */
|
|
49
|
+
ownerDescription: "All permissions, can’t be removed",
|
|
50
|
+
/** Read with the badges a row folds away: "+2". */
|
|
34
51
|
moreRoles: (n) => `+${n.toLocaleString()}`,
|
|
35
52
|
moreRolesLabel: (roles) => `Also ${and(roles)}`,
|
|
36
|
-
/** Beside
|
|
37
|
-
coveredBy: (roles) =>
|
|
38
|
-
/** Beside
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
53
|
+
/** Beside someone whose other role gives everything this one does. */
|
|
54
|
+
coveredBy: (roles) => `Included in ${and(roles)}`,
|
|
55
|
+
/** Beside the owner, who has every role's permissions anyway. */
|
|
56
|
+
ownerHasAll: "Included in Owner",
|
|
57
|
+
/** Beside someone who has this through everyone as well. */
|
|
58
|
+
everyoneHasThisToo: "Also via Everyone",
|
|
59
|
+
/** Beside someone who has this through their groups, or an inherited role, as well. */
|
|
60
|
+
throughToo: (from) => `Also via ${and(from)}`,
|
|
61
|
+
/** A role someone holds that another of their roles already gives. */
|
|
62
|
+
redundant: (roles) => `Redundant, included in ${and(roles)}`,
|
|
63
|
+
redundantOwner: "Redundant, included in Owner",
|
|
64
|
+
/** A role someone has only because everyone does. */
|
|
65
|
+
throughEveryone: "Via Everyone",
|
|
66
|
+
/** A role someone has through one of their groups, or an inherited role. */
|
|
67
|
+
through: (from) => `Via ${from}`,
|
|
68
|
+
/** A role granted somewhere else, which can't be removed here. */
|
|
69
|
+
inheritedFrom: (from) => `Inherited from ${from}`,
|
|
70
|
+
removeRole: (principal, role) => `Remove ${principal} from ${role}`,
|
|
71
|
+
grantMoreRoles: "Grant roles",
|
|
46
72
|
removeAllAccess: "Remove all access",
|
|
47
|
-
removeTitle: (
|
|
48
|
-
removeAllTitle: (
|
|
49
|
-
/**
|
|
50
|
-
losesIntro: (
|
|
51
|
-
/** After the list:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
removeTitle: (principal, roles) => `Remove ${principal} from ${and(roles)}?`,
|
|
74
|
+
removeAllTitle: (principal) => `Remove all access for ${principal}?`,
|
|
75
|
+
/** Heads the list of what is lost; `members` is undefined when a group's size isn't known. */
|
|
76
|
+
losesIntro: (kind, members) => kind === "everyone" ? "Permissions lost by people without other access" : kind === "group" && members !== void 0 ? `Permissions lost by ${countOf(members, "member", "members")}` : "Permissions lost",
|
|
77
|
+
/** After the list: what they keep, their own roles and those through their groups, inherited
|
|
78
|
+
* roles or everyone. Empty when they keep nothing. */
|
|
79
|
+
keeps: (own, throughEveryone, through) => {
|
|
80
|
+
const kept = [
|
|
81
|
+
...own,
|
|
82
|
+
...through.flatMap(({ from, roles }) => roles.map((role) => `${role} via ${from}`)),
|
|
83
|
+
...throughEveryone.map((role) => `${role} via Everyone`)
|
|
84
|
+
];
|
|
85
|
+
return kept.length > 0 ? `Still has ${and(kept)}` : "";
|
|
86
|
+
},
|
|
87
|
+
/** When the roles they keep give everything the removed ones did. */
|
|
88
|
+
losesNothing: (roles) => `No permissions lost. Still covered by ${and(roles)}.`,
|
|
89
|
+
ownerLosesNothing: "No permissions lost. Owners have all permissions.",
|
|
59
90
|
confirmRemove: (roles) => roles.length === 1 ? `Remove ${roles[0]}` : "Remove all access",
|
|
60
|
-
removeFailed: (
|
|
61
|
-
grantFailed: (
|
|
91
|
+
removeFailed: (principal, roles) => `Couldn’t remove ${principal} from ${and(roles)}`,
|
|
92
|
+
grantFailed: (principals, roles) => `Couldn’t grant ${and(roles)} to ${and(principals)}`,
|
|
62
93
|
undoFailed: "Couldn’t undo the change",
|
|
63
94
|
/** Under a failed save; `reason` is the host's error message, when it gave one. */
|
|
64
95
|
nothingChanged: (reason) => reason ? `${reason.replace(/\.?$/, ".")} Nothing changed.` : "Nothing changed.",
|
|
65
96
|
/** The toast after a grant. */
|
|
66
|
-
granted: (
|
|
97
|
+
granted: (principals, roles) => `Granted ${and(roles)} to ${and(principals)}`,
|
|
67
98
|
/** The toast after a removal. */
|
|
68
|
-
removed: (
|
|
69
|
-
undone: "Change undone
|
|
70
|
-
readOnly: "
|
|
99
|
+
removed: (principals, roles) => `Removed ${and(principals)} from ${and(roles)}`,
|
|
100
|
+
undone: "Change undone",
|
|
101
|
+
readOnly: "View only.",
|
|
71
102
|
/** After `readOnly` when the host gives no reason. */
|
|
72
|
-
readOnlyReason: "Only people who manage access
|
|
73
|
-
emptyTitle: "No
|
|
74
|
-
emptyDescription: (resource) => `
|
|
75
|
-
noMatches: "No
|
|
103
|
+
readOnlyReason: "Only people who manage access can make changes.",
|
|
104
|
+
emptyTitle: "No access granted",
|
|
105
|
+
emptyDescription: (resource) => `Grant a role to a person or group to give them access to ${resource}.`,
|
|
106
|
+
noMatches: "No matches",
|
|
76
107
|
clearFilter: "Clear filter",
|
|
77
|
-
back: "Back to
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
/** Under the groups field;
|
|
81
|
-
|
|
82
|
-
everyoneCoversGroups: "Everyone
|
|
108
|
+
back: "Back to access",
|
|
109
|
+
principalsLabel: "People and groups",
|
|
110
|
+
addPrincipal: "Add people or groups",
|
|
111
|
+
/** Under the people and groups field; a count is undefined when the host doesn't give it. */
|
|
112
|
+
pickerHint: (people, groups, everyone) => `Search ${and([people === void 0 ? "people" : countOf(people, "person", "people"), groups === void 0 ? "groups" : countOf(groups, "group", "groups")])}${everyone ? ", or choose Everyone" : ""}`,
|
|
113
|
+
everyoneCoversGroups: "Everyone includes all people and groups",
|
|
83
114
|
organization: "Organization",
|
|
84
115
|
recent: "Recent",
|
|
85
|
-
|
|
86
|
-
|
|
116
|
+
peopleSection: "People",
|
|
117
|
+
groupsSection: "Groups",
|
|
118
|
+
/** On a row in the picker: the roles it holds here. */
|
|
87
119
|
hasRoles: (roles) => `has ${and(roles)}`,
|
|
88
120
|
/** Read with a group's row: its size, where it comes from and its roles here. */
|
|
89
121
|
groupSummary: (members, everyone, source, roles) => [
|
|
@@ -91,52 +123,50 @@ var ACCESS_STRINGS = {
|
|
|
91
123
|
source === "synced" && "synced",
|
|
92
124
|
roles.length > 0 ? `has ${and(roles)} here` : "no roles here"
|
|
93
125
|
].filter(Boolean).join(", "),
|
|
126
|
+
/** Read with a person's row: what tells them apart, and their roles here. */
|
|
127
|
+
personSummary: (detail, owner, roles) => [detail, owner ? "owner" : roles.length > 0 ? `has ${and(roles)} here` : "no roles here"].filter(Boolean).join(", "),
|
|
94
128
|
rolesLabel: "Roles",
|
|
95
129
|
searchRoles: "Search roles",
|
|
96
130
|
/** On a role's row in the picker. */
|
|
97
|
-
alreadyHave: (
|
|
98
|
-
alreadyCovered: (roles) =>
|
|
99
|
-
everyoneHasIt: "
|
|
131
|
+
alreadyHave: (principals) => `Already granted to ${and(principals)}`,
|
|
132
|
+
alreadyCovered: (roles) => `Included in ${and(roles)}`,
|
|
133
|
+
everyoneHasIt: "Already granted to Everyone",
|
|
100
134
|
/** The footer's account of what pressing Grant does. */
|
|
101
|
-
grantSummary: (
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
"."
|
|
111
|
-
] }),
|
|
112
|
-
/** Why Grant can't be pressed: every group has every role already. */
|
|
113
|
-
nothingToGrant: (groups, roles) => `${and(groups)} already ${has(groups)} ${and(roles)}, so there’s nothing to grant.`,
|
|
114
|
-
pickGroupsAndRoles: "Pick the groups and the roles to grant.",
|
|
115
|
-
pickGroups: "Pick a group to grant to.",
|
|
116
|
-
pickRoles: "Pick a role to grant.",
|
|
135
|
+
grantSummary: (principals, roles) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("strong", {
|
|
136
|
+
className: "font-medium text-(--theme-app)",
|
|
137
|
+
children: and(roles)
|
|
138
|
+
}), ` for ${and(principals)}`] }),
|
|
139
|
+
/** Why Grant can't be pressed: everyone picked has every role picked already. */
|
|
140
|
+
nothingToGrant: (principals, roles) => `${and(principals)} already ${has(principals)} ${and(roles)}`,
|
|
141
|
+
pickPrincipalsAndRoles: "Choose people or groups and roles",
|
|
142
|
+
pickPrincipals: "Choose people or groups",
|
|
143
|
+
pickRoles: "Choose roles",
|
|
117
144
|
grant: (roles) => roles.length === 0 ? "Grant" : roles.length === 1 ? `Grant ${roles[0]}` : `Grant ${countOf(roles.length, "role", "roles")}`,
|
|
118
145
|
grantToEveryone: "Grant to everyone",
|
|
119
146
|
everyoneWarningTitle: (roles, people) => people === void 0 ? `${and(roles)} for everyone` : `${and(roles)} for all ${countOf(people, "person", "people")}`,
|
|
120
|
-
everyoneWarning: (resource) => `Everyone in your organization
|
|
147
|
+
everyoneWarning: (resource) => `Everyone in your organization gets access to ${resource}, including people who join later.`,
|
|
121
148
|
confirmEveryoneTitle: (roles, people) => people === void 0 ? `Grant ${and(roles)} to everyone?` : `Grant ${and(roles)} to all ${countOf(people, "person", "people")}?`,
|
|
122
|
-
synced: "Synced from
|
|
149
|
+
synced: "Synced from identity provider",
|
|
123
150
|
syncedShort: "Synced",
|
|
124
|
-
manual: "
|
|
125
|
-
wholeOrganization: "
|
|
126
|
-
joinLater: "
|
|
151
|
+
manual: "Managed manually",
|
|
152
|
+
wholeOrganization: "Entire organization",
|
|
153
|
+
joinLater: "Includes people who join later",
|
|
154
|
+
/** Heads the groups a person is in that hold a role here. */
|
|
155
|
+
inGroups: "Groups with access",
|
|
127
156
|
/** The first few members by name, and how many more there are. */
|
|
128
157
|
memberNames: (names, more) => more > 0 ? and([...names, `${more.toLocaleString()} more`]) : and(names),
|
|
129
158
|
moreMembers: (n) => `+${n.toLocaleString()}`,
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
159
|
+
/** Heads the roles the highlighted one holds on this resource. */
|
|
160
|
+
currentRoles: "Current roles",
|
|
161
|
+
noRolesHere: "No roles",
|
|
162
|
+
/** When a role being granted gives everything one already held does. */
|
|
163
|
+
becomesRedundant: (granting, held) => `${and(held)} will be redundant, included in ${and(granting)}`,
|
|
164
|
+
alreadyHas: (roles) => `Already has ${and(roles)}`,
|
|
165
|
+
everyoneReach: (roles, people) => `Grants ${and(roles)} to ${people === void 0 ? "everyone" : `all ${countOf(people, "person", "people")}`}, including people who join later. Requires confirmation.`,
|
|
136
166
|
updated: (ago) => `Membership updated ${ago}`,
|
|
137
|
-
detailsFailed: "Couldn’t load
|
|
138
|
-
loadingDetails: "Loading
|
|
139
|
-
noHighlight: "Highlight a group to see
|
|
167
|
+
detailsFailed: "Couldn’t load group details",
|
|
168
|
+
loadingDetails: "Loading group details",
|
|
169
|
+
noHighlight: "Highlight a person or group to see details"
|
|
140
170
|
};
|
|
141
171
|
/** The access drawer's strings: the host's translations over the English defaults. */
|
|
142
172
|
function useAccessStrings() {
|
package/dist/access/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { TimeValue } from '../format.js';
|
|
2
|
-
/** A named set of permissions.
|
|
2
|
+
/** A named set of permissions. People and groups are granted roles, never permissions one by
|
|
3
|
+
* one. */
|
|
3
4
|
export interface AccessRole {
|
|
4
5
|
id: string;
|
|
5
6
|
name: string;
|
|
@@ -8,31 +9,48 @@ export interface AccessRole {
|
|
|
8
9
|
/** The permissions it gives. Two roles that share some are allowed, and only redundant: the
|
|
9
10
|
* drawer works out the overlap from these and says so as a hint. */
|
|
10
11
|
permissions: readonly string[];
|
|
11
|
-
/** Marks a role
|
|
12
|
-
* it to everyone
|
|
13
|
-
|
|
12
|
+
/** Marks a role with administrative or root-level control. The role picker labels it, and
|
|
13
|
+
* granting it to everyone confirms with a destructive button. */
|
|
14
|
+
privileged?: boolean | undefined;
|
|
15
|
+
/** False for a role held here only through somewhere else, such as an organization's
|
|
16
|
+
* administrator role: it shows where someone holds it, and isn't offered to grant. */
|
|
17
|
+
grantable?: boolean | undefined;
|
|
14
18
|
}
|
|
15
|
-
/** A permission's name in words, for what
|
|
19
|
+
/** A permission's name in words, for what someone loses when a role is taken away. */
|
|
16
20
|
export interface AccessPermission {
|
|
17
21
|
id: string;
|
|
18
22
|
/** What it lets someone do, as a phrase that follows "will no longer be able to": "Sign in". */
|
|
19
23
|
label: string;
|
|
20
24
|
}
|
|
21
|
-
/** Who a role can be granted to. */
|
|
22
|
-
export interface
|
|
25
|
+
/** Who a role can be granted to: a person, a group, or everyone. */
|
|
26
|
+
export interface AccessPrincipal {
|
|
23
27
|
id: string;
|
|
24
28
|
name: string;
|
|
25
|
-
/**
|
|
29
|
+
/** `everyone` is the whole organization, people who join later included. At most one
|
|
30
|
+
* principal is; leave it out where a resource can't be granted to everyone. */
|
|
31
|
+
kind: 'user' | 'group' | 'everyone';
|
|
32
|
+
/** For a group, how many people are in it; for everyone, the organization's size. */
|
|
26
33
|
memberCount?: number | undefined;
|
|
27
|
-
/**
|
|
28
|
-
|
|
29
|
-
|
|
34
|
+
/** For a person, their picture. Until it loads, their initials show. */
|
|
35
|
+
avatarUrl?: string | undefined;
|
|
36
|
+
/** For a person, what tells them apart from someone with the same name: an email address. */
|
|
37
|
+
detail?: string | undefined;
|
|
38
|
+
/** For a person, the groups they are in. Only groups holding a role here matter, so a host
|
|
39
|
+
* may pass just those; with them, taking a role from the person says what they keep through
|
|
40
|
+
* their groups. */
|
|
41
|
+
groupIds?: readonly string[] | undefined;
|
|
30
42
|
}
|
|
31
|
-
/** A role held by a group. */
|
|
43
|
+
/** A role held by a person or group. */
|
|
32
44
|
export interface AccessGrant {
|
|
33
|
-
|
|
45
|
+
principalId: string;
|
|
34
46
|
roleId: string;
|
|
35
47
|
}
|
|
48
|
+
/** A role held through something the resource is part of, such as its organization. It shows,
|
|
49
|
+
* and counts toward what someone keeps, but changes only where it was granted. */
|
|
50
|
+
export interface AccessInheritedGrant extends AccessGrant {
|
|
51
|
+
/** Where it was granted, as a phrase that follows "through": "the organization". */
|
|
52
|
+
from: string;
|
|
53
|
+
}
|
|
36
54
|
/** What one change grants and takes away; neither list holds a grant that changes nothing. */
|
|
37
55
|
export interface AccessChange {
|
|
38
56
|
grant: AccessGrant[];
|
|
@@ -43,7 +61,7 @@ export interface AccessMember {
|
|
|
43
61
|
name: string;
|
|
44
62
|
avatarUrl?: string | undefined;
|
|
45
63
|
}
|
|
46
|
-
/** More about a group than the list needs, loaded when it is highlighted in the
|
|
64
|
+
/** More about a group than the list needs, loaded when it is highlighted in the picker. */
|
|
47
65
|
export interface AccessGroupDetails {
|
|
48
66
|
description?: string | undefined;
|
|
49
67
|
/** `synced` comes from an identity provider; `manual` is kept by hand. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createMemoryConversationStore } from "../memoryStore.js";
|
|
2
2
|
//#region src/ai/adapter/mock/mockAdapter.ts
|
|
3
3
|
var CANNED_REPLIES = [
|
|
4
|
-
"Here's a canned reply from the mock adapter. It streams word by word so the typewriter animation, cancel behavior, and background streams all exercise the same code paths as a real backend.\n\n- No network requests are made\n- Conversations persist in localStorage\n- Try
|
|
4
|
+
"Here's a canned reply from the mock adapter. It streams word by word so the typewriter animation, cancel behavior, and background streams all exercise the same code paths as a real backend.\n\n- No network requests are made\n- Conversations persist in localStorage\n- Try canceling mid-stream: the partial reply is kept",
|
|
5
5
|
"Everything you see is generated locally. The mock adapter implements the same `ChatAdapter` interface as the OpenAI-compatible adapter and any backend's own, so the UI can't tell the difference.\n\n```ts\nconst adapter = createMockChatAdapter()\n```\n\nMarkdown, `inline code`, and code blocks render through the same pipeline.",
|
|
6
6
|
"Branching works too: edit one of your earlier messages or hit regenerate on a reply, then use the arrows to flip between branches."
|
|
7
7
|
];
|
|
@@ -26,7 +26,7 @@ interface UseSSEStreamResult {
|
|
|
26
26
|
streamingConversationIdRef: React.MutableRefObject<string | null>;
|
|
27
27
|
/** Per-conversation background stream storage */
|
|
28
28
|
backgroundStreamsRef: React.MutableRefObject<Map<string, BackgroundStream>>;
|
|
29
|
-
/** AbortController ref for
|
|
29
|
+
/** AbortController ref for canceling fetch requests */
|
|
30
30
|
abortControllerRef: React.MutableRefObject<AbortController | null>;
|
|
31
31
|
}
|
|
32
32
|
export declare function useSSEStream(dispatch: React.Dispatch<ChatAction>): UseSSEStreamResult;
|
|
@@ -11,7 +11,7 @@ import { CheckboxGroup } from "@base-ui/react/checkbox-group";
|
|
|
11
11
|
function Checkbox$1({ className, ...props }) {
|
|
12
12
|
return /* @__PURE__ */ jsx(Checkbox.Root, {
|
|
13
13
|
...props,
|
|
14
|
-
className: cx("flex size-4 shrink-0 items-center justify-center rounded border border-(--theme-border-strong) bg-(--theme-input-bg) text-(--theme-accent-text) cursor-pointer transition-colors motion-reduce:transition-none", focusOutline, "data-checked:border-(--theme-
|
|
14
|
+
className: cx("flex size-4 shrink-0 items-center justify-center rounded border border-(--theme-border-strong) bg-(--theme-input-bg) text-(--theme-accent-text) cursor-pointer transition-colors motion-reduce:transition-none", focusOutline, "data-checked:border-(--theme-focus) data-checked:bg-(--theme-accent)", "data-indeterminate:border-(--theme-focus) data-indeterminate:bg-(--theme-accent)", "aria-invalid:border-(--theme-error)", "data-disabled:cursor-not-allowed data-disabled:opacity-50", className),
|
|
15
15
|
children: /* @__PURE__ */ jsx(Checkbox.Indicator, {
|
|
16
16
|
className: "flex",
|
|
17
17
|
render: (indicatorProps, state) => /* @__PURE__ */ jsx("span", {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
2
|
import { AddIcon, CheckIcon, ChevronDownIcon, RemoveIcon } from "../icons.js";
|
|
3
3
|
import { panelClassName, positionerClassName } from "./floating.js";
|
|
4
|
+
import { useFullscreenContainer } from "./fullscreen.js";
|
|
4
5
|
import { Loader } from "./Loader.js";
|
|
5
6
|
import { Tooltip } from "./Tooltip.js";
|
|
6
7
|
import { useStrings } from "./Provider.js";
|
|
@@ -361,6 +362,7 @@ function GroupHeading({ group }) {
|
|
|
361
362
|
}
|
|
362
363
|
/** Portal, positioner and popup shared by Combobox and Autocomplete; both use the same parts. */
|
|
363
364
|
function ListPopup({ listRef, empty, statusGroups = [], query = "", header, className = "w-(--anchor-width) max-w-(--available-width)", loading, details, collisionBoundary, renderItem }) {
|
|
365
|
+
const fullscreen = useFullscreenContainer();
|
|
364
366
|
const withDetails = details !== void 0;
|
|
365
367
|
const list = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
366
368
|
loading && /* @__PURE__ */ jsxs("div", {
|
|
@@ -387,27 +389,30 @@ function ListPopup({ listRef, empty, statusGroups = [], query = "", header, clas
|
|
|
387
389
|
})
|
|
388
390
|
})
|
|
389
391
|
] });
|
|
390
|
-
return /* @__PURE__ */ jsx(Combobox.Portal, {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
children:
|
|
398
|
-
className: "flex min-
|
|
399
|
-
children: [/* @__PURE__ */
|
|
400
|
-
className: "flex min-
|
|
401
|
-
children:
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
392
|
+
return /* @__PURE__ */ jsx(Combobox.Portal, {
|
|
393
|
+
container: fullscreen,
|
|
394
|
+
children: /* @__PURE__ */ jsx(Combobox.Positioner, {
|
|
395
|
+
align: "start",
|
|
396
|
+
sideOffset: 4,
|
|
397
|
+
className: positionerClassName,
|
|
398
|
+
...collisionBoundary && { collisionBoundary },
|
|
399
|
+
children: /* @__PURE__ */ jsxs(Combobox.Popup, {
|
|
400
|
+
className: cx("overflow-hidden", withDetails && "@container/picker flex h-[min(40rem,var(--available-height))] flex-col", className, panelClassName, !empty && !loading && statusGroups.length === 0 && !header && "data-empty:hidden"),
|
|
401
|
+
children: [header, withDetails ? /* @__PURE__ */ jsxs("div", {
|
|
402
|
+
className: "flex min-h-0 flex-1",
|
|
403
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
404
|
+
className: "flex min-w-0 flex-1 flex-col",
|
|
405
|
+
children: list
|
|
406
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
407
|
+
"aria-hidden": "true",
|
|
408
|
+
"data-testid": "combobox-details",
|
|
409
|
+
className: "hidden w-1/2 shrink-0 overflow-y-auto overscroll-contain border-l border-(--theme-border) bg-(--theme-panel-bg) @min-[30rem]/picker:block",
|
|
410
|
+
children: [details, /* @__PURE__ */ jsx("div", { className: "pointer-events-none sticky bottom-0 -mt-6 h-6 bg-linear-to-t from-(--theme-panel-bg) to-transparent" })]
|
|
411
|
+
})]
|
|
412
|
+
}) : list]
|
|
413
|
+
})
|
|
409
414
|
})
|
|
410
|
-
})
|
|
415
|
+
});
|
|
411
416
|
}
|
|
412
417
|
function toRows(filtered, statusGroups) {
|
|
413
418
|
const nodes = statusGroups.length ? [...filtered, ...statusGroups].sort((a, b) => ("order" in a ? a.order : 0) - ("order" in b ? b.order : 0)) : filtered;
|
|
@@ -2,9 +2,10 @@ import { cx } from "../cx.js";
|
|
|
2
2
|
import { buttonClasses } from "./buttonClasses.js";
|
|
3
3
|
import { CalendarIcon, ChevronLeftIcon, ChevronRightIcon } from "../icons.js";
|
|
4
4
|
import { panelClassName, positionerClassName } from "./floating.js";
|
|
5
|
+
import { useFullscreenContainer } from "./fullscreen.js";
|
|
5
6
|
import { useStrings } from "./Provider.js";
|
|
6
7
|
import { fieldBox } from "./fieldBox.js";
|
|
7
|
-
import {
|
|
8
|
+
import { useFieldLabelledControl } from "./Field.js";
|
|
8
9
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
9
10
|
import { useState } from "react";
|
|
10
11
|
import { Popover } from "@base-ui/react/popover";
|
|
@@ -74,10 +75,10 @@ var triggerClassName = (size) => cx(fieldBox({ size }), "flex flex-1 cursor-poin
|
|
|
74
75
|
* and the shown date follow the reader's locale.
|
|
75
76
|
*/
|
|
76
77
|
function DatePicker({ value, onValueChange, withTime = false, min, max, timeStep = 60, required = false, disabled = false, placeholder, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, size = "md", className }) {
|
|
78
|
+
const fullscreen = useFullscreenContainer();
|
|
77
79
|
const t = useStrings().datePicker;
|
|
78
80
|
const { common } = useStrings();
|
|
79
|
-
const field =
|
|
80
|
-
const fieldLabelId = useFieldLabelId();
|
|
81
|
+
const field = useFieldLabelledControl();
|
|
81
82
|
const [open, setOpen] = useState(false);
|
|
82
83
|
const [weekStartsOn] = useState(firstWeekday);
|
|
83
84
|
const day = parseDay(value);
|
|
@@ -104,10 +105,10 @@ function DatePicker({ value, onValueChange, withTime = false, min, max, timeStep
|
|
|
104
105
|
open,
|
|
105
106
|
onOpenChange: setOpen,
|
|
106
107
|
children: [/* @__PURE__ */ jsxs(Popover.Trigger, {
|
|
107
|
-
id,
|
|
108
|
+
id: id ?? field?.id,
|
|
108
109
|
disabled,
|
|
109
110
|
"aria-label": ariaLabel,
|
|
110
|
-
"aria-labelledby": ariaLabelledBy ?? (ariaLabel ? void 0 :
|
|
111
|
+
"aria-labelledby": ariaLabelledBy ?? (ariaLabel ? void 0 : field?.["aria-labelledby"]),
|
|
111
112
|
"aria-describedby": ariaDescribedBy ?? field?.["aria-describedby"],
|
|
112
113
|
"aria-invalid": ariaInvalid || field?.["aria-invalid"],
|
|
113
114
|
"aria-required": ariaRequired || field?.["aria-required"],
|
|
@@ -119,61 +120,64 @@ function DatePicker({ value, onValueChange, withTime = false, min, max, timeStep
|
|
|
119
120
|
className: cx("truncate", !day && "text-(--theme-placeholder)"),
|
|
120
121
|
children: day ? shownDay.format(day) : placeholder ?? t.pickDate
|
|
121
122
|
})]
|
|
122
|
-
}), /* @__PURE__ */ jsx(Popover.Portal, {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
className:
|
|
128
|
-
children:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
required
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
...earliest && { startMonth: earliest },
|
|
141
|
-
...latest && { endMonth: latest },
|
|
142
|
-
disabled: outOfRange,
|
|
143
|
-
weekStartsOn,
|
|
144
|
-
autoFocus: true,
|
|
145
|
-
showOutsideDays: true,
|
|
146
|
-
fixedWeeks: true,
|
|
147
|
-
classNames: calendarClassNames,
|
|
148
|
-
formatters: {
|
|
149
|
-
formatCaption: (month) => caption.format(month),
|
|
150
|
-
formatWeekdayName: (date) => weekday.format(date)
|
|
151
|
-
},
|
|
152
|
-
labels: {
|
|
153
|
-
labelDayButton: (date) => longDay.format(date),
|
|
154
|
-
labelPrevious: () => t.previousMonth,
|
|
155
|
-
labelNext: () => t.nextMonth,
|
|
156
|
-
labelGrid: (month) => caption.format(month),
|
|
157
|
-
labelWeekday: (date) => longWeekday.format(date)
|
|
158
|
-
},
|
|
159
|
-
components: { Chevron: ({ orientation }) => orientation === "left" ? /* @__PURE__ */ jsx(ChevronLeftIcon, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { "aria-hidden": "true" }) }
|
|
160
|
-
}), !required && day && /* @__PURE__ */ jsx("div", {
|
|
161
|
-
className: "flex justify-end border-t border-(--theme-border) px-3 py-2",
|
|
162
|
-
children: /* @__PURE__ */ jsx("button", {
|
|
163
|
-
type: "button",
|
|
164
|
-
className: buttonClasses({
|
|
165
|
-
variant: "ghost",
|
|
166
|
-
size: "xs"
|
|
167
|
-
}),
|
|
168
|
-
onClick: () => {
|
|
169
|
-
emit(void 0);
|
|
170
|
-
setOpen(false);
|
|
123
|
+
}), /* @__PURE__ */ jsx(Popover.Portal, {
|
|
124
|
+
container: fullscreen,
|
|
125
|
+
children: /* @__PURE__ */ jsx(Popover.Positioner, {
|
|
126
|
+
align: "start",
|
|
127
|
+
sideOffset: 4,
|
|
128
|
+
className: positionerClassName,
|
|
129
|
+
children: /* @__PURE__ */ jsxs(Popover.Popup, {
|
|
130
|
+
className: panelClassName,
|
|
131
|
+
children: [/* @__PURE__ */ jsx(DayPicker, {
|
|
132
|
+
...required ? {
|
|
133
|
+
mode: "single",
|
|
134
|
+
required: true,
|
|
135
|
+
selected: day,
|
|
136
|
+
onSelect: pick
|
|
137
|
+
} : {
|
|
138
|
+
mode: "single",
|
|
139
|
+
selected: day,
|
|
140
|
+
onSelect: pick
|
|
171
141
|
},
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
142
|
+
...opensOn && { defaultMonth: opensOn },
|
|
143
|
+
...earliest && { startMonth: earliest },
|
|
144
|
+
...latest && { endMonth: latest },
|
|
145
|
+
disabled: outOfRange,
|
|
146
|
+
weekStartsOn,
|
|
147
|
+
autoFocus: true,
|
|
148
|
+
showOutsideDays: true,
|
|
149
|
+
fixedWeeks: true,
|
|
150
|
+
classNames: calendarClassNames,
|
|
151
|
+
formatters: {
|
|
152
|
+
formatCaption: (month) => caption.format(month),
|
|
153
|
+
formatWeekdayName: (date) => weekday.format(date)
|
|
154
|
+
},
|
|
155
|
+
labels: {
|
|
156
|
+
labelDayButton: (date) => longDay.format(date),
|
|
157
|
+
labelPrevious: () => t.previousMonth,
|
|
158
|
+
labelNext: () => t.nextMonth,
|
|
159
|
+
labelGrid: (month) => caption.format(month),
|
|
160
|
+
labelWeekday: (date) => longWeekday.format(date)
|
|
161
|
+
},
|
|
162
|
+
components: { Chevron: ({ orientation }) => orientation === "left" ? /* @__PURE__ */ jsx(ChevronLeftIcon, { "aria-hidden": "true" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { "aria-hidden": "true" }) }
|
|
163
|
+
}), !required && day && /* @__PURE__ */ jsx("div", {
|
|
164
|
+
className: "flex justify-end border-t border-(--theme-border) px-3 py-2",
|
|
165
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
166
|
+
type: "button",
|
|
167
|
+
className: buttonClasses({
|
|
168
|
+
variant: "ghost",
|
|
169
|
+
size: "xs"
|
|
170
|
+
}),
|
|
171
|
+
onClick: () => {
|
|
172
|
+
emit(void 0);
|
|
173
|
+
setOpen(false);
|
|
174
|
+
},
|
|
175
|
+
children: common.clear
|
|
176
|
+
})
|
|
177
|
+
})]
|
|
178
|
+
})
|
|
175
179
|
})
|
|
176
|
-
})
|
|
180
|
+
})]
|
|
177
181
|
}), withTime && /* @__PURE__ */ jsx("input", {
|
|
178
182
|
type: "time",
|
|
179
183
|
"aria-label": t.time,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { cx } from "../cx.js";
|
|
2
2
|
import { ArrowLeftIcon, CloseIcon } from "../icons.js";
|
|
3
|
+
import { useFullscreenContainer } from "./fullscreen.js";
|
|
3
4
|
import { useStrings } from "./Provider.js";
|
|
4
5
|
import { IconButton } from "./IconButton.js";
|
|
5
6
|
import { closeHandler } from "./dismiss.js";
|
|
@@ -16,6 +17,7 @@ import { Drawer } from "@base-ui/react/drawer";
|
|
|
16
17
|
* `--app-inset-start` (a start-side sidebar's width, at least 2.5rem). Set both
|
|
17
18
|
* on the document root. */
|
|
18
19
|
function Drawer$1({ open, onClose, title, description, onBack, backLabel, footer, side = "right", width = 480, resizable = false, minWidth = 320, dismissible = true, modal = true, closeButton = true, contained = false, keepMounted = false, padded = true, className, children, ...popupProps }) {
|
|
20
|
+
const fullscreen = useFullscreenContainer();
|
|
19
21
|
const { common } = useStrings();
|
|
20
22
|
const container = useRef(null);
|
|
21
23
|
const viewport = useRef(null);
|
|
@@ -42,7 +44,7 @@ function Drawer$1({ open, onClose, title, description, onBack, backLabel, footer
|
|
|
42
44
|
ref: container,
|
|
43
45
|
className: "contents"
|
|
44
46
|
}), /* @__PURE__ */ jsxs(Drawer.Portal, {
|
|
45
|
-
|
|
47
|
+
container: contained ? container : fullscreen,
|
|
46
48
|
keepMounted,
|
|
47
49
|
className: contained ? "contents" : void 0,
|
|
48
50
|
children: [modal !== false && /* @__PURE__ */ jsx(Drawer.Backdrop, { className: cx(area, "z-(--layer-drawer) bg-black/40 opacity-[calc(1-var(--drawer-swipe-progress))] transition-opacity duration-300 data-ending-style:opacity-0 data-starting-style:opacity-0 data-swiping:duration-0 motion-reduce:transition-none") }), /* @__PURE__ */ jsx(Drawer.Viewport, {
|