@parallelworks/ui 0.76.0 → 0.78.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.
Files changed (270) hide show
  1. package/README.md +30 -5
  2. package/dist/access/AccessDrawer.d.ts +28 -7
  3. package/dist/access/AccessDrawer.js +216 -35
  4. package/dist/access/AccessList.d.ts +19 -1
  5. package/dist/access/AccessList.js +220 -9
  6. package/dist/access/GrantView.d.ts +3 -1
  7. package/dist/access/GrantView.js +17 -4
  8. package/dist/access/PrincipalPicker.js +5 -5
  9. package/dist/access/focus.js +1 -1
  10. package/dist/access/index.d.ts +2 -2
  11. package/dist/access/index.js +2 -2
  12. package/dist/access/strings.d.ts +18 -0
  13. package/dist/access/strings.js +20 -2
  14. package/dist/access/types.d.ts +26 -0
  15. package/dist/ai/adapter/types.d.ts +1 -0
  16. package/dist/ai/components/AllocationSelector.js +1 -1
  17. package/dist/ai/components/AttachmentManager.js +5 -5
  18. package/dist/ai/components/AttachmentUpload.js +2 -2
  19. package/dist/ai/components/BranchNavigator.js +1 -1
  20. package/dist/ai/components/ChatActivity.d.ts +2 -0
  21. package/dist/ai/components/ChatActivity.js +31 -0
  22. package/dist/ai/components/ChatEmptyState.js +3 -3
  23. package/dist/ai/components/ChatInput.js +3 -5
  24. package/dist/ai/components/ChatLayout.js +1 -1
  25. package/dist/ai/components/ChatMessage.js +1 -1
  26. package/dist/ai/components/ChatMessageList.js +2 -2
  27. package/dist/ai/components/ChatSidebar.js +3 -3
  28. package/dist/ai/components/ChatThread.js +4 -4
  29. package/dist/ai/components/ComposerAddMenu.js +9 -20
  30. package/dist/ai/components/ModelSelector.js +3 -3
  31. package/dist/ai/components/PastedText.js +5 -5
  32. package/dist/ai/components/SessionView.js +5 -2
  33. package/dist/ai/components/ShareDialog.js +3 -3
  34. package/dist/ai/components/SlashMenu.d.ts +31 -4
  35. package/dist/ai/components/SlashMenu.js +36 -63
  36. package/dist/ai/components/agent/ActivityLine.js +1 -1
  37. package/dist/ai/components/agent/AgentMessageParts.d.ts +1 -1
  38. package/dist/ai/components/agent/AgentMessageParts.js +44 -18
  39. package/dist/ai/components/conversationActions.js +3 -3
  40. package/dist/ai/core/ChatProvider.js +11 -2
  41. package/dist/ai/core/config.d.ts +10 -1
  42. package/dist/ai/core/config.js +1 -1
  43. package/dist/ai/index.d.ts +1 -1
  44. package/dist/ai/strings.d.ts +2 -0
  45. package/dist/ai/strings.js +2 -1
  46. package/dist/ai/types.d.ts +3 -0
  47. package/dist/ai/ui/ConversationSidebar.js +3 -3
  48. package/dist/ai/ui/sidebar.js +3 -3
  49. package/dist/assets/yaml.worker-DOBV2lND.js +884 -0
  50. package/dist/components/ActionMenu.d.ts +38 -9
  51. package/dist/components/ActionMenu.js +48 -22
  52. package/dist/components/Avatar.d.ts +66 -6
  53. package/dist/components/Avatar.js +130 -18
  54. package/dist/components/Breadcrumbs.d.ts +4 -0
  55. package/dist/components/Breadcrumbs.js +26 -10
  56. package/dist/components/Button.d.ts +3 -2
  57. package/dist/components/Button.js +11 -1
  58. package/dist/components/Callout.d.ts +3 -2
  59. package/dist/components/Callout.js +3 -2
  60. package/dist/components/Card.d.ts +33 -9
  61. package/dist/components/Card.js +24 -8
  62. package/dist/components/Checkbox.d.ts +3 -1
  63. package/dist/components/Checkbox.js +4 -2
  64. package/dist/components/ColorInput.js +1 -1
  65. package/dist/components/Combobox.d.ts +23 -0
  66. package/dist/components/Combobox.js +252 -86
  67. package/dist/components/ConfirmModal.d.ts +26 -2
  68. package/dist/components/ConfirmModal.js +81 -15
  69. package/dist/components/ContextMenu.d.ts +29 -2
  70. package/dist/components/ContextMenu.js +60 -8
  71. package/dist/components/CopyButton.d.ts +21 -8
  72. package/dist/components/CopyButton.js +29 -9
  73. package/dist/components/CreateModal.d.ts +16 -4
  74. package/dist/components/CreateModal.js +27 -16
  75. package/dist/components/DatePicker.d.ts +31 -1
  76. package/dist/components/DatePicker.js +178 -47
  77. package/dist/components/DescriptionList.d.ts +13 -3
  78. package/dist/components/DescriptionList.js +43 -11
  79. package/dist/components/Disclosure.d.ts +10 -2
  80. package/dist/components/Disclosure.js +18 -7
  81. package/dist/components/Drawer.js +5 -5
  82. package/dist/components/EmptyState.d.ts +12 -4
  83. package/dist/components/EmptyState.js +18 -10
  84. package/dist/components/ErrorPage.d.ts +40 -0
  85. package/dist/components/ErrorPage.js +101 -0
  86. package/dist/components/ExternalLink.d.ts +4 -1
  87. package/dist/components/ExternalLink.js +4 -5
  88. package/dist/components/Field.d.ts +21 -6
  89. package/dist/components/Field.js +32 -16
  90. package/dist/components/FileButton.d.ts +19 -0
  91. package/dist/components/FileButton.js +32 -0
  92. package/dist/components/HoverCard.d.ts +26 -6
  93. package/dist/components/HoverCard.js +11 -5
  94. package/dist/components/IconButton.js +4 -2
  95. package/dist/components/InfoTip.js +3 -3
  96. package/dist/components/Input.d.ts +33 -3
  97. package/dist/components/Input.js +149 -14
  98. package/dist/components/Kbd.d.ts +40 -0
  99. package/dist/components/Kbd.js +168 -0
  100. package/dist/components/Listbox.d.ts +103 -0
  101. package/dist/components/Listbox.js +190 -0
  102. package/dist/components/Loader.d.ts +8 -2
  103. package/dist/components/Loader.js +16 -1
  104. package/dist/components/Markdown.js +1 -1
  105. package/dist/components/MarkdownImpl.js +1 -1
  106. package/dist/components/Menu.d.ts +20 -2
  107. package/dist/components/Menu.js +48 -19
  108. package/dist/components/MenuEntries.d.ts +38 -2
  109. package/dist/components/MenuEntries.js +266 -39
  110. package/dist/components/Meter.d.ts +50 -11
  111. package/dist/components/Meter.js +71 -21
  112. package/dist/components/Modal.d.ts +9 -3
  113. package/dist/components/Modal.js +68 -29
  114. package/dist/components/OneTimeCodeInput.js +1 -1
  115. package/dist/components/PageHeader.js +111 -7
  116. package/dist/components/PageHeaderAction.d.ts +8 -3
  117. package/dist/components/PageHeaderAction.js +122 -34
  118. package/dist/components/Pagination.d.ts +13 -1
  119. package/dist/components/Pagination.js +90 -19
  120. package/dist/components/Popover.d.ts +13 -2
  121. package/dist/components/Popover.js +20 -21
  122. package/dist/components/Provider.d.ts +140 -3
  123. package/dist/components/Provider.js +142 -12
  124. package/dist/components/RefreshButton.d.ts +13 -6
  125. package/dist/components/RefreshButton.js +17 -9
  126. package/dist/components/RunStatusIcon.d.ts +16 -7
  127. package/dist/components/RunStatusIcon.js +21 -8
  128. package/dist/components/Section.d.ts +11 -4
  129. package/dist/components/Section.js +33 -22
  130. package/dist/components/SegmentedControl.d.ts +16 -3
  131. package/dist/components/SegmentedControl.js +7 -3
  132. package/dist/components/Select.d.ts +19 -3
  133. package/dist/components/Select.js +114 -68
  134. package/dist/components/SelectionCopy.d.ts +10 -0
  135. package/dist/components/SelectionCopy.js +105 -0
  136. package/dist/components/SettingModeOption.js +1 -1
  137. package/dist/components/SettingsCard.js +1 -1
  138. package/dist/components/Skeleton.js +1 -1
  139. package/dist/components/StatusBadge.d.ts +55 -10
  140. package/dist/components/StatusBadge.js +91 -15
  141. package/dist/components/StickyBar.js +1 -1
  142. package/dist/components/Table.d.ts +22 -4
  143. package/dist/components/Table.js +46 -24
  144. package/dist/components/Tabs.d.ts +9 -4
  145. package/dist/components/Tabs.js +9 -4
  146. package/dist/components/Tag.d.ts +49 -0
  147. package/dist/components/Tag.js +199 -0
  148. package/dist/components/ThemeEditor.js +1 -1
  149. package/dist/components/Toast.js +1 -1
  150. package/dist/components/ToggleGroup.d.ts +4 -1
  151. package/dist/components/ToggleGroup.js +30 -13
  152. package/dist/components/Tooltip.d.ts +9 -3
  153. package/dist/components/Tooltip.js +55 -39
  154. package/dist/components/buttonClasses.d.ts +5 -2
  155. package/dist/components/buttonClasses.js +11 -10
  156. package/dist/components/chart.d.ts +69 -0
  157. package/dist/components/chart.js +145 -0
  158. package/dist/components/comboboxItems.d.ts +7 -1
  159. package/dist/components/comboboxItems.js +6 -2
  160. package/dist/components/dismiss.d.ts +5 -1
  161. package/dist/components/dismiss.js +6 -2
  162. package/dist/components/floating.d.ts +4 -4
  163. package/dist/components/floating.js +5 -5
  164. package/dist/components/headerActionsFit.d.ts +37 -0
  165. package/dist/components/headerActionsFit.js +81 -0
  166. package/dist/components/pageHeaderStore.d.ts +3 -0
  167. package/dist/components/pageHeaderStore.js +3 -0
  168. package/dist/components/pathBreaks.js +2 -2
  169. package/dist/components/popupContainer.d.ts +17 -0
  170. package/dist/components/popupContainer.js +69 -0
  171. package/dist/components/relativeTime.d.ts +21 -10
  172. package/dist/components/relativeTime.js +50 -22
  173. package/dist/components/segmented.d.ts +18 -4
  174. package/dist/components/segmented.js +48 -6
  175. package/dist/components/typedDate.d.ts +5 -0
  176. package/dist/components/typedDate.js +50 -0
  177. package/dist/components/useClosingValue.d.ts +4 -0
  178. package/dist/components/useClosingValue.js +12 -0
  179. package/dist/components/useCopy.d.ts +6 -0
  180. package/dist/components/useCopy.js +3 -3
  181. package/dist/components/useNarrow.d.ts +2 -0
  182. package/dist/components/useNarrow.js +16 -0
  183. package/dist/editor/Editor.js +1 -1
  184. package/dist/editor/FormCodePanel.js +1 -1
  185. package/dist/editor/Monaco.js +1 -1
  186. package/dist/editor/workers.js +1 -1
  187. package/dist/file-explorer/FileExplorer.js +16 -15
  188. package/dist/file-explorer/FileExplorerContext.js +2 -2
  189. package/dist/file-explorer/FilePreview/Viewer.js +3 -3
  190. package/dist/file-explorer/FilePreview/index.js +2 -2
  191. package/dist/file-explorer/FileRowsWindow.js +3 -3
  192. package/dist/file-explorer/FileTable.js +1 -1
  193. package/dist/file-explorer/LoadMoreRow.js +1 -1
  194. package/dist/file-explorer/TreeView.js +5 -5
  195. package/dist/file-explorer/helpers.js +2 -2
  196. package/dist/file-explorer/toasts.js +2 -2
  197. package/dist/form/Fields/CheckboxGroupField.js +1 -1
  198. package/dist/form/Fields/DropdownField.js +1 -1
  199. package/dist/form/Fields/DurationField.js +2 -2
  200. package/dist/form/Fields/MultiDropdownField.js +1 -1
  201. package/dist/form/Fields/NumberField.js +1 -1
  202. package/dist/form/Fields/ObjectField.js +1 -1
  203. package/dist/form/Fields/RangeField.js +1 -1
  204. package/dist/form/Fields/StringField.js +1 -1
  205. package/dist/form/Form.js +3 -3
  206. package/dist/form/FormLayout.js +1 -1
  207. package/dist/form/Wizard/WizardContainer.js +1 -1
  208. package/dist/form/Wizard/WizardNavigation.js +1 -1
  209. package/dist/form/Wizard/WizardStepContent.js +1 -1
  210. package/dist/form/fieldRegistry.js +1 -1
  211. package/dist/form/formEditing.js +1 -1
  212. package/dist/format.d.ts +17 -7
  213. package/dist/format.js +39 -16
  214. package/dist/graph/AnnotationBanner.js +2 -2
  215. package/dist/graph/Connectors.js +5 -5
  216. package/dist/graph/DependencyGraph.js +10 -9
  217. package/dist/graph/GraphEditor.js +4 -4
  218. package/dist/graph/GraphEditorDialogs.js +13 -12
  219. package/dist/graph/GraphNode.js +1 -1
  220. package/dist/graph/InputDialog.js +5 -5
  221. package/dist/graph/InputsEditor.js +7 -7
  222. package/dist/graph/MatrixGroup.js +2 -2
  223. package/dist/graph/ProblemsButton.js +3 -3
  224. package/dist/graph/Reveal.js +1 -1
  225. package/dist/graph/RunWarningsList.js +1 -1
  226. package/dist/graph/ShortcutsButton.js +25 -8
  227. package/dist/graph/StepSummaries.js +1 -1
  228. package/dist/graph/TreeView.js +2 -2
  229. package/dist/graph/WorkflowSettingsDialog.js +3 -3
  230. package/dist/graph/editorFields.js +3 -3
  231. package/dist/graph/editorPrimitives.js +1 -1
  232. package/dist/graph/editorRows.js +5 -5
  233. package/dist/graph/fieldProblems.js +2 -2
  234. package/dist/graph/inputRefs.js +7 -6
  235. package/dist/graph/stepWith.js +2 -2
  236. package/dist/index.d.ts +30 -21
  237. package/dist/index.js +20 -12
  238. package/dist/list/ActionToolbar.d.ts +4 -0
  239. package/dist/list/ActionToolbar.js +35 -12
  240. package/dist/list/ListFilterBar.d.ts +42 -0
  241. package/dist/list/ListFilterBar.js +789 -0
  242. package/dist/list/ListPage.d.ts +42 -4
  243. package/dist/list/ListPage.js +56 -22
  244. package/dist/list/ListTable.d.ts +27 -3
  245. package/dist/list/ListTable.js +48 -23
  246. package/dist/list/ListToolbar.d.ts +31 -0
  247. package/dist/list/ListToolbar.js +43 -0
  248. package/dist/list/ListViewControls.d.ts +8 -4
  249. package/dist/list/ListViewControls.js +22 -14
  250. package/dist/list/UserHoverCard.d.ts +8 -2
  251. package/dist/list/UserHoverCard.js +26 -13
  252. package/dist/list/filterModel.d.ts +122 -0
  253. package/dist/list/filterModel.js +308 -0
  254. package/dist/list/index.d.ts +6 -2
  255. package/dist/list/index.js +6 -2
  256. package/dist/list/listSearch.d.ts +1 -1
  257. package/dist/list/listSearch.js +8 -4
  258. package/dist/list/listView.d.ts +24 -2
  259. package/dist/list/listView.js +47 -45
  260. package/dist/list/rowMenu.d.ts +10 -5
  261. package/dist/list/rowMenu.js +17 -7
  262. package/dist/list/useListFilters.d.ts +68 -0
  263. package/dist/list/useListFilters.js +252 -0
  264. package/dist/logviewer/LogViewer.js +4 -4
  265. package/dist/styles/theme.css +94 -0
  266. package/dist/styles.css +1 -1
  267. package/package.json +11 -11
  268. package/dist/assets/yaml.worker-k9AdOqzf.js +0 -1441
  269. package/dist/components/fullscreen.d.ts +0 -4
  270. package/dist/components/fullscreen.js +0 -18
package/README.md CHANGED
@@ -24,7 +24,7 @@ Hosts bind strings, navigation, data hooks and the workflow engine through
24
24
  ```tsx
25
25
  <UIProvider
26
26
  strings={{ common: { save: 'Speichern' }, fileExplorer: { preview: { open: 'Öffnen' } } }}
27
- navigation={{ goTo: navigate }}
27
+ navigation={{ goTo: navigate, goBack: router.history.back, canGoBack: router.history.canGoBack }}
28
28
  data={{ useRunFile, usePersistedState }}
29
29
  slots={{ link: RouterLink }}
30
30
  engine={() => import('./engine').then((m) => m.engine)}
@@ -47,6 +47,24 @@ rule a select or scroll area would inject ships in `theme.css`. A host that
47
47
  renders on the server passes its policy's `nonce`, which the few components
48
48
  that emit a script to place themselves before hydration put on it.
49
49
 
50
+ Pass `locale`, the app's language as a BCP 47 tag such as `de-DE`, so relative
51
+ times, exact times, durations and the date picker read as the app's own text
52
+ does. Without it they follow the date library's default locale, or the browser's.
53
+ `useLocale()` reads it, for a host formatting with `formatDateTime`.
54
+
55
+ Pass `avatarColoring="kind"` for avatars that tell people from agents at a
56
+ glance: every person shares one tint and every agent another, with two initials
57
+ even for a one-word name. The host sets the tints with `--avatar-person`,
58
+ `--avatar-person-text`, `--avatar-agent`, `--avatar-agent-text` and the agent's
59
+ edge `--avatar-agent-ring`. An agent's avatar (`kind="agent"`) is a rounded
60
+ square under either coloring, so its shape and not only its color sets it apart.
61
+
62
+ Menus, selects, pickers, popovers, tooltips and hover cards open over the page,
63
+ or over the fullscreen element while there is one. Opened inside a `<dialog>`
64
+ shown with `showModal()`, they open inside that dialog instead: the browser
65
+ draws a modal dialog above everything else and leaves the page behind it inert,
66
+ so a popup over the page would open under it, out of reach.
67
+
50
68
  ## Which component?
51
69
 
52
70
  Each need has one component. Reach for it before styling markup by hand.
@@ -59,6 +77,8 @@ Each need has one component. Reach for it before styling markup by hand.
59
77
  | A link out of the app | `ExternalLink` |
60
78
  | Work under way | `Loader`; `Skeleton` in a `LoadingRegion` for the shape of content still loading |
61
79
  | Nothing to show yet, or nothing matches | `EmptyState` |
80
+ | A page, card or section that couldn't show what it was asked for, such as a 404 | `ErrorPage`, its words chosen by the status |
81
+ | A list page's toolbar and its loading, error, empty and no-match states | `ListToolbar` in `ListPage` with `total`, `matches`, `empty` and `noMatches` (from `./list`) |
62
82
  | A text field | `Input`, `Textarea`, or a control framed by `Field`, whose `actions` hold buttons that act on it |
63
83
  | A number, a date, a color | `NumberInput`, `DatePicker`, `ColorInput` |
64
84
  | A one-time code, such as a sign-in check | `OneTimeCodeInput` |
@@ -92,11 +112,15 @@ Each need has one component. Reach for it before styling markup by hand.
92
112
  | How full something is, how far a task has come | `Meter`, `ProgressBar` |
93
113
  | Pages of a long list | `Pagination` |
94
114
  | A grouped page of settings | `SettingsCard` with `SettingRow`s |
95
- | A person | `Avatar`, colored by name without a picture |
115
+ | A person or an agent | `Avatar`, colored by name without a picture, or by kind; `AvatarGroup` for an overlapping stack with a "+N" chip |
96
116
  | A status in words | `StatusBadge`, or `StatusDot` beside text that says it |
117
+ | A thing's tags or labels | `Tag`; `TagList` for several, wrapped or on one line ending in "+N" |
118
+ | Keys to press | `Kbd`, with `Mod` for Command or Control; `ariaKeyShortcuts` for the control the keys press |
119
+ | A chart's colors, legend and tooltip | `seriesColors`, `ChartLegendList`, `ChartTooltipContent` and `useSeriesHighlight`, around a chart drawn by hand or by a library |
97
120
  | A run's, job's or step's status beside its name | `RunStatusIcon`; `StatusBadge` with `runStatusTone` to say it in words |
98
- | A copy action | `CopyButton`; `CopyCodeBlock` for a command or snippet; `useCopy` for a menu row or a control of your own |
99
- | A live relative time or duration | `RelativeTime`, `Elapsed` |
121
+ | A copy action | `CopyButton`; `CopyCodeBlock` for a command or snippet, with `secret` for a value shown once; `SelectionCopy` to copy part of a block by selecting it; `useCopy` for a menu row or a control of your own |
122
+ | A live relative time or duration | `RelativeTime` (with a `fallback`, an `exact` style for its tooltip, or `show="absolute"`), `Elapsed` |
123
+ | Fetching again on demand | `RefreshButton`; `variant="inline"` in a line of details; `loading` on an `ActionToolbar` action |
100
124
  | Rendered Markdown | `Markdown` |
101
125
  | Code or YAML to read or edit | `Editor` from `./editor`; `EditorField` as a form field, `FormCodePanel` for a form's values as JSON |
102
126
  | A run's jobs, steps, logs, annotations and warnings | `DependencyGraph` from `./graph`, as a graph or a list |
@@ -331,7 +355,8 @@ all at one lightness and chroma so none reads louder, and each
331
355
  `--theme-category-N-text` is solved to secondary text's targets on its fill. An
332
356
  `Avatar` without a picture takes one, picked by an FNV-1a hash of its name (or
333
357
  its `colorKey`), so the same name has the same color on every page; a nameless
334
- one keeps the accent. `categoryOf(key)` gives the same pick, 1–8, and
358
+ one keeps the accent. Under `avatarColoring="kind"` it takes its kind's tint
359
+ instead. `categoryOf(key)` gives the same pick, 1–8, and
335
360
  `categoryClasses(key)` its fill and text classes, for anything else drawn
336
361
  beside a name, such as a group's icon.
337
362
 
@@ -2,11 +2,10 @@ import { type ReactNode } from 'react';
2
2
  import { type AccessModel } from './model.js';
3
3
  import type { SearchPrincipals } from './PrincipalPicker.js';
4
4
  import { type AccessStrings } from './strings.js';
5
- import type { AccessChange, AccessGrant, AccessGroupDetails, AccessInheritedGrant, AccessPermission, AccessPrincipal, AccessRole } from './types.js';
5
+ import type { AccessChange, AccessGrant, AccessGroupDetails, AccessInheritedGrant, AccessPermission, AccessPrincipal, AccessRole, AccessRule, AccessVisibility } from './types.js';
6
6
  export type AccessArrangement = 'role' | 'principal';
7
- export interface AccessDrawerProps {
8
- open: boolean;
9
- onClose: () => void;
7
+ /** What the drawer and the inline panel share: everything but where they sit. */
8
+ export interface AccessEditorProps {
10
9
  /** What access is to, as the reader knows it: "gpu-cluster-east". */
11
10
  resourceName: string;
12
11
  /** Every role that can be granted here, in the order to list them, and any held here only
@@ -48,16 +47,38 @@ export interface AccessDrawerProps {
48
47
  peopleCount?: number | undefined;
49
48
  /** People and groups the reader granted to lately, listed first in the picker. */
50
49
  recentPrincipalIds?: readonly string[] | undefined;
51
- /** How the list starts out: role by role, or person and group one by one. */
50
+ /** How the list starts out: role by role, or person and group one by one. Person by person
51
+ * where each holds one role, role by role otherwise. */
52
52
  defaultArrangement?: AccessArrangement | undefined;
53
+ /** A switch above the list between open to everyone who can see what the resource is in
54
+ * and only those listed. It saves as a grant does, with Undo in a toast. */
55
+ visibility?: AccessVisibility | undefined;
56
+ /** Reasons someone has access that no grant here changes, such as admins who always do:
57
+ * locked rows ahead of the grants. */
58
+ rules?: readonly AccessRule[] | undefined;
59
+ /** Each person and group holds one role here. Their row picks it from a list, a change
60
+ * swaps the old role for the new one in a single change, and the grant form picks one. */
61
+ singleRole?: boolean | undefined;
62
+ }
63
+ export interface AccessDrawerProps extends AccessEditorProps {
64
+ open: boolean;
65
+ onClose: () => void;
53
66
  /** As `Drawer`'s. */
54
67
  width?: number | string | undefined;
55
68
  }
69
+ export interface AccessPanelProps extends AccessEditorProps {
70
+ /** On the panel's frame. */
71
+ className?: string | undefined;
72
+ }
56
73
  /** Who has access to one resource, and the place to change it: roles granted to people and
57
74
  * groups. Overlapping roles are allowed and only redundant, which the drawer points out; taking
58
75
  * a role away first says exactly what is lost. The owner and roles granted above the resource
59
- * show, locked. Each change is saved as it is made, with Undo in a toast. */
60
- export declare function AccessDrawer({ open, onClose, resourceName, roles, principals, grants, owner, inherited, permissions, onChange, loading, readOnly, readOnlyReason, describeGroup, searchPrincipals, groupCount, peopleCount, recentPrincipalIds, defaultArrangement, width, }: AccessDrawerProps): import("react").JSX.Element;
76
+ * show, locked. Each change is saved as it is made, with Undo in a toast. `AccessPanel` is the
77
+ * same editor inline on a page. */
78
+ export declare function AccessDrawer({ open, onClose, width, ...props }: AccessDrawerProps): import("react").JSX.Element;
79
+ /** The access editor inline on a page, such as a settings section, rather than in a drawer: the
80
+ * list in a frame, which the grant form takes over with a back arrow in its own header. */
81
+ export declare function AccessPanel({ className, ...props }: AccessPanelProps): import("react").JSX.Element;
61
82
  type Match = (query: string, ...texts: (string | undefined)[]) => boolean;
62
83
  export declare function filterAccess(model: AccessModel, arrangement: AccessArrangement, query: string, match: Match, t: AccessStrings): {
63
84
  shown: Map<string, readonly string[]>;
@@ -1,32 +1,37 @@
1
- import { AddIcon, LockIcon, UsersIcon } from "../icons.js";
2
- import { toast } from "../components/Toast.js";
1
+ import { cx } from "../cx.js";
3
2
  import { EmptyState } from "../components/EmptyState.js";
3
+ import { AddIcon, ArrowLeftIcon, LockIcon, UsersIcon } from "../icons.js";
4
+ import { useOpenMenu } from "../components/ContextMenu.js";
5
+ import { Loader } from "../components/Loader.js";
6
+ import { toast } from "../components/Toast.js";
4
7
  import { useQueryMatch } from "../components/search.js";
5
8
  import { LoadingRegion, Skeleton } from "../components/Skeleton.js";
6
9
  import { Button } from "../components/Button.js";
10
+ import { IconButton } from "../components/IconButton.js";
7
11
  import { Callout } from "../components/Callout.js";
12
+ import "../components/dismiss.js";
8
13
  import { Drawer } from "../components/Drawer.js";
14
+ import { Select } from "../components/Select.js";
9
15
  import { SearchInput } from "../components/SearchInput.js";
10
16
  import { SegmentedControl } from "../components/SegmentedControl.js";
11
17
  import { buildModel, invert, key, principalsWithAccess, warnAboutMisuse } from "./model.js";
12
- import { ByPrincipal, ByRole, OWNER_SECTION, principalLabel, removeKey } from "./AccessList.js";
18
+ import { ByPrincipal, ByPrincipalSingle, ByRole, OWNER_SECTION, RuleRow, principalLabel, removeKey } from "./AccessList.js";
13
19
  import { holdFocus, useFocusAfterRender } from "./focus.js";
14
20
  import { useGrantView } from "./GrantView.js";
15
21
  import { useAccessStrings } from "./strings.js";
16
- import { jsx, jsxs } from "react/jsx-runtime";
17
22
  import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
23
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
18
24
  //#region src/access/AccessDrawer.tsx
19
25
  var EMPTY_SELECTION = {
20
26
  principalIds: [],
21
27
  roleIds: []
22
28
  };
23
- /** Who has access to one resource, and the place to change it: roles granted to people and
24
- * groups. Overlapping roles are allowed and only redundant, which the drawer points out; taking
25
- * a role away first says exactly what is lost. The owner and roles granted above the resource
26
- * show, locked. Each change is saved as it is made, with Undo in a toast. */
27
- function AccessDrawer({ open, onClose, resourceName, roles, principals, grants, owner, inherited, permissions, onChange, loading = false, readOnly = false, readOnlyReason, describeGroup, searchPrincipals, groupCount, peopleCount, recentPrincipalIds, defaultArrangement = "role", width = 560 }) {
29
+ /** The list and the grant form, and the saving, failing and undoing they share, for the drawer
30
+ * and the inline panel to lay out. */
31
+ function useAccessEditor({ resourceName, roles, principals, grants, owner, inherited, permissions, onChange, loading = false, readOnly = false, readOnlyReason, describeGroup, searchPrincipals, groupCount, peopleCount, recentPrincipalIds, singleRole = false, defaultArrangement = singleRole ? "principal" : "role", visibility, rules }, inline) {
28
32
  const t = useAccessStrings();
29
33
  const uid = useId();
34
+ const openMenu = useOpenMenu();
30
35
  const model = useMemo(() => buildModel({
31
36
  roles,
32
37
  principals,
@@ -71,6 +76,8 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
71
76
  const [removal, setRemoval] = useState();
72
77
  const [saving, setSaving] = useState();
73
78
  const [failure, setFailure] = useState();
79
+ const [visibilitySaving, setVisibilitySaving] = useState(false);
80
+ const [visibilityFailure, setVisibilityFailure] = useState();
74
81
  const cache = useRef(/* @__PURE__ */ new Map()).current;
75
82
  const seen = useRef(/* @__PURE__ */ new Map()).current;
76
83
  const body = useRef(null);
@@ -82,6 +89,8 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
82
89
  const focusLater = useFocusAfterRender();
83
90
  const change = useRef(onChange);
84
91
  change.current = onChange;
92
+ const visibilityChange = useRef(visibility?.onChange);
93
+ visibilityChange.current = visibility?.onChange;
85
94
  const match = useQueryMatch();
86
95
  const filtered = useFiltered(model, arrangement, query.trim(), match, t);
87
96
  const editable = !readOnly && onChange !== void 0;
@@ -99,10 +108,14 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
99
108
  const undo = (done) => {
100
109
  Promise.resolve(change.current?.(invert(done))).then(() => toast.info(t.undone), (error) => toast.error(t.nothingChanged(messageOf(error)), { title: t.undoFailed }));
101
110
  };
111
+ /** One role swapped for another, as a row's role picker sends it. */
112
+ const swapped = (next) => next.grant.length === 1 && next.revoke.length > 0 && next.revoke.every((revoked) => revoked.principalId === next.grant[0]?.principalId);
102
113
  /** Saves a change and reports how it went; resolves true once saved. */
103
114
  const save = async (next, where) => {
104
115
  if (!onChange || next.grant.length === 0 && next.revoke.length === 0) return false;
105
116
  const { principalNames, roleNames } = names(next);
117
+ const swap = swapped(next);
118
+ const newRole = model.roleById.get(next.grant[0]?.roleId ?? "")?.name ?? "";
106
119
  setSaving(next);
107
120
  setFailure(void 0);
108
121
  try {
@@ -110,7 +123,7 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
110
123
  } catch (error) {
111
124
  setFailure({
112
125
  change: next,
113
- title: next.grant.length > 0 ? t.grantFailed(principalNames, roleNames) : t.removeFailed(principalNames[0] ?? "", roleNames),
126
+ title: swap ? t.roleChangeFailed(principalNames[0] ?? "", newRole) : next.grant.length > 0 ? t.grantFailed(principalNames, roleNames) : t.removeFailed(principalNames[0] ?? "", roleNames),
114
127
  message: messageOf(error),
115
128
  ...where
116
129
  });
@@ -118,12 +131,36 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
118
131
  } finally {
119
132
  setSaving(void 0);
120
133
  }
121
- toast.success(next.grant.length > 0 ? t.granted(principalNames, roleNames) : t.removed(principalNames, roleNames), { action: {
134
+ toast.success(swap ? t.roleChanged(principalNames[0] ?? "", newRole) : next.grant.length > 0 ? t.granted(principalNames, roleNames) : t.removed(principalNames, roleNames), { action: {
122
135
  label: t.common.undo,
123
136
  onClick: () => undo(next)
124
137
  } });
125
138
  return true;
126
139
  };
140
+ const openLabel = visibility?.openLabel ?? "";
141
+ const restrictedLabel = visibility?.restrictedLabel ?? t.restrictedLabel;
142
+ const choiceLabel = (restricted) => restricted ? restrictedLabel : openLabel;
143
+ const saveVisibility = async (restricted) => {
144
+ const save = visibilityChange.current;
145
+ if (!save) return;
146
+ setVisibilitySaving(true);
147
+ setVisibilityFailure(void 0);
148
+ try {
149
+ await save(restricted);
150
+ } catch (error) {
151
+ setVisibilityFailure({
152
+ restricted,
153
+ message: messageOf(error)
154
+ });
155
+ return;
156
+ } finally {
157
+ setVisibilitySaving(false);
158
+ }
159
+ toast.success(t.visibilityChanged(choiceLabel(restricted)), { action: {
160
+ label: t.common.undo,
161
+ onClick: () => Promise.resolve(visibilityChange.current?.(!restricted)).then(() => toast.info(t.undone), (error) => toast.error(t.nothingChanged(messageOf(error)), { title: t.undoFailed }))
162
+ } });
163
+ };
127
164
  const toList = () => {
128
165
  setView("list");
129
166
  setFailure((current) => current?.inGrant ? void 0 : current);
@@ -167,6 +204,23 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
167
204
  setRemoval(void 0);
168
205
  focusLater(() => document.getElementById(sectionId(anchor ?? pending.principalId)) ?? body.current?.querySelector("input[type=\"search\"]"));
169
206
  };
207
+ const changeRole = (principalId, roleId) => {
208
+ const held = (model.held.get(principalId) ?? []).filter((role) => role.id !== roleId);
209
+ const has = (model.held.get(principalId) ?? []).some((role) => role.id === roleId);
210
+ save({
211
+ grant: has ? [] : [{
212
+ principalId,
213
+ roleId
214
+ }],
215
+ revoke: held.map((role) => ({
216
+ principalId,
217
+ roleId: role.id
218
+ }))
219
+ }, {
220
+ inGrant: false,
221
+ principalId
222
+ });
223
+ };
170
224
  const retry = (failed) => failed.inGrant ? grant(failed.change) : save(failed.change, {
171
225
  inGrant: false,
172
226
  principalId: failed.principalId,
@@ -185,11 +239,10 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
185
239
  onDismiss: () => setFailure(void 0),
186
240
  children: t.nothingChanged(failure.message)
187
241
  });
188
- const close = () => {
242
+ const reset = () => {
189
243
  setView("list");
190
244
  setRemoval(void 0);
191
245
  setQuery("");
192
- onClose();
193
246
  };
194
247
  const grantView = useGrantView({
195
248
  model,
@@ -209,12 +262,15 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
209
262
  onCancel: toList,
210
263
  focusLater,
211
264
  boundary: bodyElement,
265
+ singleRole,
212
266
  t
213
267
  });
214
268
  const savingKeys = useMemo(() => new Set(saving?.revoke.map(key) ?? []), [saving]);
215
269
  const withAccess = principalsWithAccess(model);
216
270
  const empty = !loading && withAccess.length === 0;
217
- const View = arrangement === "role" ? ByRole : ByPrincipal;
271
+ const View = arrangement === "role" ? ByRole : singleRole ? ByPrincipalSingle : ByPrincipal;
272
+ const needle = query.trim();
273
+ const shownRules = (rules ?? []).filter((rule) => !needle || match(needle, rule.title, rule.detail));
218
274
  const grantButton = (className) => /* @__PURE__ */ jsx(Button, {
219
275
  variant: "primary",
220
276
  icon: /* @__PURE__ */ jsx(AddIcon, {}),
@@ -224,6 +280,67 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
224
280
  className,
225
281
  children: t.grantAccess
226
282
  });
283
+ const visibilityValue = visibility?.restricted ? "restricted" : "open";
284
+ const visibilityOptions = [{
285
+ value: "open",
286
+ label: openLabel
287
+ }, {
288
+ value: "restricted",
289
+ label: restrictedLabel
290
+ }];
291
+ const visibilityRow = visibility && /* @__PURE__ */ jsxs("div", {
292
+ className: "flex flex-col gap-3 border-b border-(--theme-border) px-4 py-3",
293
+ children: [/* @__PURE__ */ jsxs("div", {
294
+ className: "flex items-center gap-2",
295
+ children: [
296
+ /* @__PURE__ */ jsx("div", {
297
+ className: "hidden @min-[30rem]/access:block",
298
+ children: /* @__PURE__ */ jsx(SegmentedControl, {
299
+ label: t.visibility,
300
+ value: visibilityValue,
301
+ disabled: !editable || visibilitySaving,
302
+ onValueChange: (next) => {
303
+ saveVisibility(next === "restricted");
304
+ },
305
+ options: visibilityOptions
306
+ })
307
+ }),
308
+ /* @__PURE__ */ jsx(Select, {
309
+ "aria-label": t.visibility,
310
+ items: visibilityOptions,
311
+ value: visibilityValue,
312
+ disabled: !editable || visibilitySaving,
313
+ onValueChange: (next) => {
314
+ if (next !== null && next !== visibilityValue) saveVisibility(next === "restricted");
315
+ },
316
+ className: "min-w-0 flex-1 @min-[30rem]/access:hidden"
317
+ }),
318
+ visibilitySaving && /* @__PURE__ */ jsx(Loader, {
319
+ inline: true,
320
+ size: "1em",
321
+ label: t.common.loading
322
+ })
323
+ ]
324
+ }), visibilityFailure && /* @__PURE__ */ jsx(Callout, {
325
+ tone: "error",
326
+ live: true,
327
+ title: t.visibilityFailed(choiceLabel(visibilityFailure.restricted)),
328
+ action: /* @__PURE__ */ jsx(Button, {
329
+ size: "sm",
330
+ variant: "secondary",
331
+ onClick: () => saveVisibility(visibilityFailure.restricted),
332
+ children: t.common.retry
333
+ }),
334
+ onDismiss: () => setVisibilityFailure(void 0),
335
+ children: t.nothingChanged(visibilityFailure.message)
336
+ })]
337
+ });
338
+ const ruleRows = shownRules.map((rule) => /* @__PURE__ */ jsx(RuleRow, {
339
+ lead: arrangement === "role" ? "chevron" : singleRole ? "tile" : "both",
340
+ rule,
341
+ roleName: rule.roleId ? model.roleById.get(rule.roleId)?.name : void 0,
342
+ t
343
+ }, rule.id));
227
344
  const list = /* @__PURE__ */ jsxs("div", {
228
345
  className: "@container/access flex min-h-0 flex-1 flex-col",
229
346
  children: [
@@ -239,10 +356,11 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
239
356
  ]
240
357
  })
241
358
  }),
359
+ visibilityRow,
242
360
  !empty && /* @__PURE__ */ jsxs("div", {
243
361
  className: "flex flex-wrap items-center gap-2 border-b border-(--theme-border) px-4 py-3",
244
362
  children: [
245
- /* @__PURE__ */ jsx("div", {
363
+ !singleRole && /* @__PURE__ */ jsx("div", {
246
364
  className: "w-full @min-[30rem]/access:w-auto",
247
365
  children: /* @__PURE__ */ jsx(SegmentedControl, {
248
366
  fill: true,
@@ -258,7 +376,7 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
258
376
  }]
259
377
  })
260
378
  }),
261
- editable && grantButton("ms-auto hidden @min-[30rem]/access:inline-flex"),
379
+ editable && grantButton(singleRole ? "order-last hidden @min-[30rem]/access:inline-flex" : "ms-auto hidden @min-[30rem]/access:inline-flex"),
262
380
  /* @__PURE__ */ jsx(SearchInput, {
263
381
  label: t.filter,
264
382
  value: query,
@@ -266,7 +384,7 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
266
384
  setQuery(next);
267
385
  setExpanded(expandedFor(model, next.trim(), match, t));
268
386
  },
269
- className: "min-w-0 basis-full"
387
+ className: singleRole ? "min-w-0 flex-1" : "min-w-0 basis-full"
270
388
  })
271
389
  ]
272
390
  }),
@@ -275,18 +393,18 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
275
393
  children: failureNode
276
394
  }),
277
395
  /* @__PURE__ */ jsx("div", {
278
- className: "min-h-0 flex-1 overflow-y-auto",
396
+ className: cx("min-h-0 flex-1", inline ? "[&>:last-child]:border-b-0" : "overflow-y-auto"),
279
397
  children: loading ? /* @__PURE__ */ jsx(LoadingRegion, {
280
398
  label: t.loading,
281
399
  children: /* @__PURE__ */ jsx(RowsSkeleton, {})
282
- }) : empty ? /* @__PURE__ */ jsx(EmptyState, {
400
+ }) : empty ? /* @__PURE__ */ jsxs(Fragment$1, { children: [ruleRows, /* @__PURE__ */ jsx(EmptyState, {
283
401
  variant: "bare",
284
402
  icon: /* @__PURE__ */ jsx(UsersIcon, {}),
285
403
  title: t.emptyTitle,
286
404
  description: t.emptyDescription(resourceName),
287
405
  action: editable && grantButton(""),
288
- className: "py-16"
289
- }) : filtered.order.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
406
+ className: ruleRows.length > 0 ? "py-8" : "py-16"
407
+ })] }) : filtered.order.length === 0 && shownRules.length === 0 ? /* @__PURE__ */ jsx(EmptyState, {
290
408
  variant: "bare",
291
409
  title: t.noMatches,
292
410
  action: /* @__PURE__ */ jsx(Button, {
@@ -295,7 +413,7 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
295
413
  onClick: () => setQuery(""),
296
414
  children: t.clearFilter
297
415
  })
298
- }) : /* @__PURE__ */ jsx(View, {
416
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [ruleRows, /* @__PURE__ */ jsx(View, {
299
417
  model,
300
418
  shown: filtered.shown,
301
419
  order: filtered.order,
@@ -309,13 +427,15 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
309
427
  [arrangement]: next
310
428
  };
311
429
  }),
312
- filtering: query.trim() !== "",
430
+ filtering: needle !== "",
313
431
  sectionId,
314
432
  t,
315
433
  actions: {
316
434
  readOnly: !editable,
317
435
  saving: savingKeys,
318
436
  removal,
437
+ singleRole,
438
+ openMenu,
319
439
  onAskRemove: (next) => {
320
440
  holdFocus();
321
441
  setFailure(void 0);
@@ -328,13 +448,14 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
328
448
  },
329
449
  onRemove: remove,
330
450
  onGrantMore: (principalId) => toGrant([principalId]),
451
+ onChangeRole: changeRole,
331
452
  failure: failure && !failure.inGrant ? {
332
453
  node: failureNode,
333
454
  principalId: failure.principalId,
334
455
  roleId: failure.roleId
335
456
  } : void 0
336
457
  }
337
- })
458
+ })] })
338
459
  }),
339
460
  editable && !empty && /* @__PURE__ */ jsx("div", {
340
461
  className: "border-t border-(--theme-border) p-4 @min-[30rem]/access:hidden",
@@ -343,29 +464,89 @@ function AccessDrawer({ open, onClose, resourceName, roles, principals, grants,
343
464
  ]
344
465
  });
345
466
  const granting = view === "grant";
346
- return /* @__PURE__ */ jsx(Drawer, {
347
- open,
348
- onClose: close,
349
- width,
350
- padded: false,
467
+ return {
468
+ granting,
351
469
  title: granting ? t.grantAccess : t.title,
352
- description: granting ? resourceName : loading ? resourceName : t.summary(resourceName, withAccess.filter((principal) => principal.kind === "user").length, withAccess.filter((principal) => principal.kind === "group").length, withAccess.some((principal) => principal.kind === "everyone")),
353
- onBack: granting ? toList : void 0,
354
- backLabel: t.back,
470
+ description: granting || loading ? resourceName : t.summary(resourceName, withAccess.filter((principal) => principal.kind === "user").length, withAccess.filter((principal) => principal.kind === "group").length, withAccess.some((principal) => principal.kind === "everyone")),
471
+ toList,
472
+ reset,
355
473
  footer: granting ? grantView.footer : void 0,
356
474
  onKeyDown: (e) => {
357
475
  const target = e.target;
358
476
  if (granting && e.key === "Escape" && !e.defaultPrevented && target.getAttribute("aria-expanded") !== "true") {
359
477
  e.stopPropagation();
360
- e.preventBaseUIHandler();
478
+ e.preventBaseUIHandler?.();
361
479
  toList();
362
480
  }
363
481
  },
364
- children: /* @__PURE__ */ jsx("div", {
482
+ body: /* @__PURE__ */ jsx("div", {
365
483
  ref: bodyRef,
366
484
  className: "flex min-h-0 flex-1 flex-col",
367
485
  children: granting ? grantView.body : list
368
486
  })
487
+ };
488
+ }
489
+ /** Who has access to one resource, and the place to change it: roles granted to people and
490
+ * groups. Overlapping roles are allowed and only redundant, which the drawer points out; taking
491
+ * a role away first says exactly what is lost. The owner and roles granted above the resource
492
+ * show, locked. Each change is saved as it is made, with Undo in a toast. `AccessPanel` is the
493
+ * same editor inline on a page. */
494
+ function AccessDrawer({ open, onClose, width = 560, ...props }) {
495
+ const t = useAccessStrings();
496
+ const editor = useAccessEditor(props, false);
497
+ return /* @__PURE__ */ jsx(Drawer, {
498
+ open,
499
+ onClose: () => {
500
+ editor.reset();
501
+ onClose();
502
+ },
503
+ width,
504
+ padded: false,
505
+ title: editor.title,
506
+ description: editor.description,
507
+ onBack: editor.granting ? editor.toList : void 0,
508
+ backLabel: t.back,
509
+ footer: editor.footer,
510
+ onKeyDown: editor.onKeyDown,
511
+ children: editor.body
512
+ });
513
+ }
514
+ /** The access editor inline on a page, such as a settings section, rather than in a drawer: the
515
+ * list in a frame, which the grant form takes over with a back arrow in its own header. */
516
+ function AccessPanel({ className, ...props }) {
517
+ const t = useAccessStrings();
518
+ const editor = useAccessEditor(props, true);
519
+ return /* @__PURE__ */ jsxs("section", {
520
+ "data-access-panel": "",
521
+ tabIndex: -1,
522
+ "aria-label": editor.granting ? t.grantAccess : t.title,
523
+ onKeyDown: editor.onKeyDown,
524
+ className: cx("flex min-w-0 flex-col overflow-hidden rounded-xl border border-(--theme-border) bg-(--theme-panel-bg) text-(--theme-app) outline-none", className),
525
+ children: [
526
+ editor.granting && /* @__PURE__ */ jsxs("div", {
527
+ className: "flex shrink-0 items-start gap-3 border-b border-(--theme-border) px-4 py-3",
528
+ children: [/* @__PURE__ */ jsx(IconButton, {
529
+ icon: /* @__PURE__ */ jsx(ArrowLeftIcon, {}),
530
+ label: t.back,
531
+ onClick: editor.toList,
532
+ className: "-my-1 -ms-1.5"
533
+ }), /* @__PURE__ */ jsxs("div", {
534
+ className: "min-w-0 flex-1",
535
+ children: [/* @__PURE__ */ jsx("h3", {
536
+ className: "m-0 text-sm font-semibold wrap-break-word",
537
+ children: editor.title
538
+ }), /* @__PURE__ */ jsx("p", {
539
+ className: "m-0 mt-0.5 text-xs text-(--theme-muted-text-color)",
540
+ children: editor.description
541
+ })]
542
+ })]
543
+ }),
544
+ editor.body,
545
+ editor.footer && /* @__PURE__ */ jsx("div", {
546
+ className: "flex shrink-0 items-center justify-end gap-2 border-t border-(--theme-border) px-4 py-3",
547
+ children: editor.footer
548
+ })
549
+ ]
369
550
  });
370
551
  }
371
552
  var messageOf = (error) => error instanceof Error && error.message ? error.message : void 0;
@@ -467,4 +648,4 @@ function RowsSkeleton() {
467
648
  }, row));
468
649
  }
469
650
  //#endregion
470
- export { AccessDrawer, filterAccess };
651
+ export { AccessDrawer, AccessPanel, filterAccess };
@@ -1,7 +1,8 @@
1
1
  import { type ReactNode } from 'react';
2
+ import type { OpenMenu } from '../components/ContextMenu.js';
2
3
  import { type AccessModel } from './model.js';
3
4
  import type { AccessStrings } from './strings.js';
4
- import type { AccessPrincipal } from './types.js';
5
+ import type { AccessPrincipal, AccessRule } from './types.js';
5
6
  /** The id the owner's section has among the roles' sections. */
6
7
  export declare const OWNER_SECTION = "__owner";
7
8
  /** A removal waiting on the reader: one role, or every role a principal holds here. */
@@ -20,6 +21,12 @@ export interface ListActions {
20
21
  onCancelRemove: () => void;
21
22
  onRemove: (removal: PendingRemoval) => void;
22
23
  onGrantMore: (principalId: string) => void;
24
+ /** Gives a principal this one role in place of the ones it holds, where each holds one. */
25
+ onChangeRole: (principalId: string, roleId: string) => void;
26
+ /** Each principal holds one role here. */
27
+ singleRole: boolean;
28
+ /** Opens a row's own menu on a right-click. */
29
+ openMenu: OpenMenu;
23
30
  /** The failed save's callout, and where it belongs: under the role or principal it was about. */
24
31
  failure: {
25
32
  node: ReactNode;
@@ -56,4 +63,15 @@ interface ViewProps {
56
63
  export declare function ByRole({ model, shown, order, expanded, onExpandedChange, filtering, actions, t, sectionId, }: ViewProps): (import("react").JSX.Element | null | undefined)[];
57
64
  /** Which roles each person and group has, one by one. */
58
65
  export declare function ByPrincipal({ model, shown, order, expanded, onExpandedChange, actions, t, sectionId, }: ViewProps): (import("react").JSX.Element | null)[];
66
+ /** A reason someone has access that no grant here changes, locked ahead of the grants. */
67
+ export declare function RuleRow({ rule, roleName, lead, t, }: {
68
+ rule: AccessRule;
69
+ roleName: string | undefined;
70
+ /** Lines its title up with the rows below: in a chevron's column, in a tile's, or after both. */
71
+ lead: 'chevron' | 'tile' | 'both';
72
+ t: AccessStrings;
73
+ }): import("react").JSX.Element;
74
+ /** Person and group one by one where each holds one role: a flat row each, its role picked
75
+ * from a list on the row. */
76
+ export declare function ByPrincipalSingle({ model, order, actions, t, sectionId }: ViewProps): (import("react").JSX.Element | null)[];
59
77
  export {};