nexus-shared 3.2.0 → 4.0.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 (187) hide show
  1. package/CHANGELOG.md +1564 -0
  2. package/README.md +1705 -1123
  3. package/dist/Client.Index.d.ts +14 -0
  4. package/dist/Client.Index.js +14 -0
  5. package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
  6. package/dist/Components/Documents/DocumentFrame.js +83 -0
  7. package/dist/Components/Documents/Menu.d.ts +4 -0
  8. package/dist/Components/Documents/Menu.js +40 -24
  9. package/dist/Components/Documents/PrintDocument.d.ts +95 -0
  10. package/dist/Components/Documents/PrintDocument.js +62 -0
  11. package/dist/Components/Documents/Tabs.d.ts +59 -0
  12. package/dist/Components/Documents/Tabs.js +70 -0
  13. package/dist/Components/Forms/ApiForm.js +19 -2
  14. package/dist/Components/Forms/Crud.d.ts +2 -2
  15. package/dist/Components/Forms/Crud.js +576 -243
  16. package/dist/Components/Forms/CrudActions.d.ts +136 -0
  17. package/dist/Components/Forms/CrudActions.js +184 -0
  18. package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
  19. package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
  20. package/dist/Components/Forms/CrudParts.d.ts +30 -2
  21. package/dist/Components/Forms/CrudParts.js +55 -5
  22. package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
  23. package/dist/Components/Forms/CrudRowForms.js +114 -0
  24. package/dist/Components/Forms/CrudScopes.d.ts +28 -0
  25. package/dist/Components/Forms/CrudScopes.js +76 -0
  26. package/dist/Components/Forms/CrudTree.d.ts +90 -0
  27. package/dist/Components/Forms/CrudTree.js +363 -0
  28. package/dist/Components/Forms/CrudWorkspace.d.ts +63 -0
  29. package/dist/Components/Forms/CrudWorkspace.js +169 -0
  30. package/dist/Components/Forms/ExcelImport.js +3 -3
  31. package/dist/Components/Forms/Form.d.ts +4 -4
  32. package/dist/Components/Forms/Form.js +123 -17
  33. package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
  34. package/dist/Components/Forms/FormLeaveGuard.js +65 -0
  35. package/dist/Components/Forms/RecordFlows.d.ts +50 -0
  36. package/dist/Components/Forms/RecordFlows.js +282 -0
  37. package/dist/Components/Forms/SubmitForm.d.ts +37 -2
  38. package/dist/Components/Forms/SubmitForm.js +195 -10
  39. package/dist/Components/Inputs/CheckBox.js +1 -1
  40. package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
  41. package/dist/Components/Inputs/ChoiceField.js +28 -2
  42. package/dist/Components/Inputs/Dropdown.d.ts +13 -0
  43. package/dist/Components/Inputs/Dropdown.js +5 -5
  44. package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
  45. package/dist/Components/Inputs/DropdownParts.js +16 -4
  46. package/dist/Components/Inputs/Field.d.ts +7 -1
  47. package/dist/Components/Inputs/Field.js +2 -2
  48. package/dist/Components/Inputs/GroupForm.d.ts +6 -6
  49. package/dist/Components/Inputs/GroupForm.js +10 -11
  50. package/dist/Components/Inputs/InputRenderer.d.ts +9 -6
  51. package/dist/Components/Inputs/InputRenderer.js +37 -32
  52. package/dist/Components/Inputs/PictureInput.d.ts +41 -0
  53. package/dist/Components/Inputs/PictureInput.js +70 -0
  54. package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
  55. package/dist/Components/Inputs/RadioGroup.js +8 -3
  56. package/dist/Components/Inputs/RowsInput.d.ts +1 -1
  57. package/dist/Components/Inputs/RowsInput.js +18 -3
  58. package/dist/Components/Inputs/TabularForm.d.ts +5 -5
  59. package/dist/Components/Inputs/TabularForm.js +29 -29
  60. package/dist/Components/Inputs/TextArea.d.ts +6 -0
  61. package/dist/Components/Inputs/TextArea.js +11 -2
  62. package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
  63. package/dist/Components/Inputs/TextBoxTypes.js +17 -0
  64. package/dist/Components/Inputs/TreeView.d.ts +53 -2
  65. package/dist/Components/Inputs/TreeView.js +147 -13
  66. package/dist/Components/Layouts/MessageBox.d.ts +15 -2
  67. package/dist/Components/Layouts/MessageBox.js +56 -7
  68. package/dist/Components/Layouts/Popup.d.ts +13 -6
  69. package/dist/Components/Layouts/Popup.js +37 -9
  70. package/dist/Components/Layouts/PopupDock.d.ts +3 -1
  71. package/dist/Components/Layouts/PopupDock.js +2 -1
  72. package/dist/Components/Layouts/PopupHost.js +5 -4
  73. package/dist/Components/Viewers/DataTable.js +150 -48
  74. package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
  75. package/dist/Components/Viewers/DataTableFilters.js +17 -2
  76. package/dist/Components/Viewers/DataTableParts.d.ts +21 -5
  77. package/dist/Components/Viewers/DataTableParts.js +26 -14
  78. package/dist/Components/Viewers/OwnerFiles.d.ts +175 -0
  79. package/dist/Components/Viewers/OwnerFiles.js +589 -0
  80. package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
  81. package/dist/Components/Viewers/QrCodeImage.js +15 -0
  82. package/dist/Components/Viewers/RecordCards.d.ts +16 -0
  83. package/dist/Components/Viewers/RecordCards.js +509 -0
  84. package/dist/Helpers/BusyHooks.d.ts +6 -0
  85. package/dist/Helpers/BusyHooks.js +39 -0
  86. package/dist/Helpers/CrudBackend.d.ts +20 -2
  87. package/dist/Helpers/CrudBackend.js +46 -2
  88. package/dist/Helpers/CrudColumns.d.ts +53 -6
  89. package/dist/Helpers/CrudColumns.js +98 -7
  90. package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
  91. package/dist/Helpers/CrudDetailsTabs.js +126 -0
  92. package/dist/Helpers/CrudHelpers.d.ts +81 -2
  93. package/dist/Helpers/CrudHelpers.js +195 -0
  94. package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
  95. package/dist/Helpers/CrudNarrowPick.js +157 -0
  96. package/dist/Helpers/CrudRowForms.d.ts +15 -0
  97. package/dist/Helpers/CrudRowForms.js +43 -0
  98. package/dist/Helpers/CrudScopes.d.ts +32 -0
  99. package/dist/Helpers/CrudScopes.js +79 -0
  100. package/dist/Helpers/DropdownHelpers.d.ts +3 -1
  101. package/dist/Helpers/DropdownHelpers.js +6 -2
  102. package/dist/Helpers/ExcelHelpers.d.ts +1 -1
  103. package/dist/Helpers/ExcelHelpers.js +12 -11
  104. package/dist/Helpers/FileAdapter.d.ts +78 -0
  105. package/dist/Helpers/FileAdapter.js +102 -0
  106. package/dist/Helpers/FilePrimaries.d.ts +48 -0
  107. package/dist/Helpers/FilePrimaries.js +138 -0
  108. package/dist/Helpers/FormConfirm.d.ts +32 -0
  109. package/dist/Helpers/FormConfirm.js +40 -0
  110. package/dist/Helpers/FormStore.d.ts +90 -5
  111. package/dist/Helpers/FormStore.js +318 -22
  112. package/dist/Helpers/InputConditions.d.ts +50 -0
  113. package/dist/Helpers/InputConditions.js +168 -0
  114. package/dist/Helpers/InputMemory.d.ts +106 -0
  115. package/dist/Helpers/InputMemory.js +385 -0
  116. package/dist/Helpers/InputNarrowing.d.ts +18 -0
  117. package/dist/Helpers/InputNarrowing.js +67 -0
  118. package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
  119. package/dist/Helpers/InputParamsHelpers.js +157 -74
  120. package/dist/Helpers/NexusDriveAdapter.d.ts +55 -0
  121. package/dist/Helpers/NexusDriveAdapter.js +207 -0
  122. package/dist/Helpers/NumberHelpers.d.ts +95 -0
  123. package/dist/Helpers/NumberHelpers.js +126 -0
  124. package/dist/Helpers/Permissions.js +2 -1
  125. package/dist/Helpers/QrCode.d.ts +26 -0
  126. package/dist/Helpers/QrCode.js +463 -0
  127. package/dist/Helpers/RecordCards.d.ts +205 -0
  128. package/dist/Helpers/RecordCards.js +378 -0
  129. package/dist/Helpers/RequestCache.d.ts +14 -2
  130. package/dist/Helpers/RequestCache.js +22 -3
  131. package/dist/Helpers/RowsStore.d.ts +37 -2
  132. package/dist/Helpers/RowsStore.js +128 -10
  133. package/dist/Helpers/ServerPreview.d.ts +69 -0
  134. package/dist/Helpers/ServerPreview.js +99 -0
  135. package/dist/Helpers/TableColumns.js +4 -3
  136. package/dist/Helpers/TableExport.d.ts +7 -1
  137. package/dist/Helpers/TableExport.js +9 -3
  138. package/dist/Helpers/TableFilters.d.ts +1 -1
  139. package/dist/Helpers/TableFilters.js +24 -23
  140. package/dist/Helpers/TableHelpers.d.ts +23 -2
  141. package/dist/Helpers/TableHelpers.js +51 -10
  142. package/dist/Helpers/TableJoins.d.ts +13 -0
  143. package/dist/Helpers/TableJoins.js +34 -0
  144. package/dist/Helpers/TableStore.d.ts +21 -1
  145. package/dist/Helpers/TableStore.js +64 -7
  146. package/dist/Helpers/TreeStore.d.ts +81 -0
  147. package/dist/Helpers/TreeStore.js +365 -0
  148. package/dist/Interfaces/CrudInterfaces.d.ts +465 -5
  149. package/dist/Interfaces/FileInterfaces.d.ts +162 -0
  150. package/dist/Interfaces/FileInterfaces.js +4 -0
  151. package/dist/Interfaces/FormInterfaces.d.ts +391 -30
  152. package/dist/Interfaces/FormInterfaces.js +79 -1
  153. package/dist/Interfaces/InputInterfaces.d.ts +15 -3
  154. package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
  155. package/dist/Interfaces/RecordInterfaces.js +1 -0
  156. package/dist/Interfaces/TableInterfaces.d.ts +74 -1
  157. package/dist/Services/ExcelApi.js +5 -0
  158. package/dist/Services/FileApi.d.ts +13 -0
  159. package/dist/Services/FileApi.js +29 -0
  160. package/dist/Services/ImageEditorLoader.d.ts +14 -0
  161. package/dist/Services/ImageEditorLoader.js +51 -0
  162. package/dist/Services/JoinSources.d.ts +37 -5
  163. package/dist/Services/JoinSources.js +90 -14
  164. package/dist/Services/ModuleImport.d.ts +7 -0
  165. package/dist/Services/ModuleImport.js +22 -0
  166. package/dist/Shared.Index.d.ts +16 -0
  167. package/dist/Shared.Index.js +16 -0
  168. package/package.json +7 -2
  169. package/src/Styles/Nexus.Avatar.css +6 -5
  170. package/src/Styles/Nexus.Button.css +10 -4
  171. package/src/Styles/Nexus.Choice.css +16 -0
  172. package/src/Styles/Nexus.Crud.css +383 -9
  173. package/src/Styles/Nexus.Dropdown.css +8 -0
  174. package/src/Styles/Nexus.Files.css +410 -0
  175. package/src/Styles/Nexus.Form.css +5 -2
  176. package/src/Styles/Nexus.Index.css +4 -0
  177. package/src/Styles/Nexus.Input.css +42 -0
  178. package/src/Styles/Nexus.Menu.css +10 -1
  179. package/src/Styles/Nexus.Popup.css +77 -1
  180. package/src/Styles/Nexus.Print.css +288 -0
  181. package/src/Styles/Nexus.QrCode.css +12 -0
  182. package/src/Styles/Nexus.Record.css +430 -0
  183. package/src/Styles/Nexus.Tab.Buttons.css +87 -22
  184. package/src/Styles/Nexus.Table.css +67 -5
  185. package/src/Styles/Nexus.Themes.css +12 -6
  186. package/src/Styles/Nexus.Tokens.css +8 -3
  187. package/src/Styles/Nexus.Tree.css +31 -1
@@ -5,13 +5,19 @@ export * from "./Components/Chats/ChatConversations.tsx";
5
5
  export * from "./Components/Chats/ChatPanel.tsx";
6
6
  export * from "./Components/Chats/ChatParts.tsx";
7
7
  export * from "./Components/Chats/ChatThread.tsx";
8
+ export * from "./Components/Documents/DocumentFrame.tsx";
8
9
  export * from "./Components/Documents/Menu.tsx";
9
10
  export * from "./Components/Documents/SplitButton.tsx";
10
11
  export * from "./Components/Documents/TabButtons.tsx";
12
+ export * from "./Components/Documents/Tabs.tsx";
11
13
  export * from "./Components/Forms/ApiForm.tsx";
12
14
  export * from "./Components/Forms/Crud.tsx";
13
15
  export * from "./Components/Forms/CrudHistory.tsx";
16
+ export * from "./Components/Forms/CrudNarrowPicker.tsx";
14
17
  export * from "./Components/Forms/CrudParts.tsx";
18
+ export * from "./Components/Forms/CrudRowForms.tsx";
19
+ export * from "./Components/Forms/CrudScopes.tsx";
20
+ export * from "./Components/Forms/CrudTree.tsx";
15
21
  export * from "./Components/Forms/ExcelImport.tsx";
16
22
  export * from "./Components/Forms/Form.tsx";
17
23
  export * from "./Components/Forms/SubmitForm.tsx";
@@ -27,6 +33,7 @@ export * from "./Components/Inputs/GroupForm.tsx";
27
33
  export * from "./Components/Inputs/InputRenderer.tsx";
28
34
  export * from "./Components/Inputs/MarkdownEditor.tsx";
29
35
  export * from "./Components/Inputs/NumberBox.tsx";
36
+ export * from "./Components/Inputs/PictureInput.tsx";
30
37
  export * from "./Components/Inputs/RadioGroup.tsx";
31
38
  export * from "./Components/Inputs/ReadOnlyNotice.tsx";
32
39
  export * from "./Components/Inputs/RichTextEditor.tsx";
@@ -48,14 +55,21 @@ export * from "./Components/Layouts/Toaster.tsx";
48
55
  export * from "./Components/Viewers/Avatar.tsx";
49
56
  export * from "./Components/Viewers/DataTable.tsx";
50
57
  export * from "./Components/Viewers/DataTableParts.tsx";
58
+ export * from "./Components/Viewers/OwnerFiles.tsx";
59
+ export * from "./Components/Viewers/RecordCards.tsx";
51
60
  export * from "./Helpers/ChatHooks.ts";
52
61
  export * from "./Helpers/DatePreferences.ts";
53
62
  export * from "./Helpers/DragHelpers.ts";
54
63
  export * from "./Helpers/PopoverHelpers.ts";
55
64
  export * from "./Helpers/PopoverHooks.ts";
65
+ export * from "./Helpers/ServerPreview.ts";
66
+ export { downloadText } from "./Helpers/TableExport.ts";
56
67
  export * from "./Helpers/UserPreferences.ts";
57
68
  export * from "./Services/BrowserApi.ts";
58
69
  export * from "./Services/ChatApi.ts";
59
70
  export * from "./Services/ExcelApi.ts";
71
+ export * from "./Services/FileApi.ts";
72
+ export * from "./Services/ImageEditorLoader.ts";
73
+ export * from "./Services/ModuleImport.ts";
60
74
  export * from "./Services/JoinSources.ts";
61
75
  export * from "./Services/ThemeService.ts";
@@ -8,13 +8,19 @@ export * from "./Components/Chats/ChatConversations.js";
8
8
  export * from "./Components/Chats/ChatPanel.js";
9
9
  export * from "./Components/Chats/ChatParts.js";
10
10
  export * from "./Components/Chats/ChatThread.js";
11
+ export * from "./Components/Documents/DocumentFrame.js";
11
12
  export * from "./Components/Documents/Menu.js";
12
13
  export * from "./Components/Documents/SplitButton.js";
13
14
  export * from "./Components/Documents/TabButtons.js";
15
+ export * from "./Components/Documents/Tabs.js";
14
16
  export * from "./Components/Forms/ApiForm.js";
15
17
  export * from "./Components/Forms/Crud.js";
16
18
  export * from "./Components/Forms/CrudHistory.js";
19
+ export * from "./Components/Forms/CrudNarrowPicker.js";
17
20
  export * from "./Components/Forms/CrudParts.js";
21
+ export * from "./Components/Forms/CrudRowForms.js";
22
+ export * from "./Components/Forms/CrudScopes.js";
23
+ export * from "./Components/Forms/CrudTree.js";
18
24
  export * from "./Components/Forms/ExcelImport.js";
19
25
  export * from "./Components/Forms/Form.js";
20
26
  export * from "./Components/Forms/SubmitForm.js";
@@ -30,6 +36,7 @@ export * from "./Components/Inputs/GroupForm.js";
30
36
  export * from "./Components/Inputs/InputRenderer.js";
31
37
  export * from "./Components/Inputs/MarkdownEditor.js";
32
38
  export * from "./Components/Inputs/NumberBox.js";
39
+ export * from "./Components/Inputs/PictureInput.js";
33
40
  export * from "./Components/Inputs/RadioGroup.js";
34
41
  export * from "./Components/Inputs/ReadOnlyNotice.js";
35
42
  export * from "./Components/Inputs/RichTextEditor.js";
@@ -51,16 +58,23 @@ export * from "./Components/Layouts/Toaster.js";
51
58
  export * from "./Components/Viewers/Avatar.js";
52
59
  export * from "./Components/Viewers/DataTable.js";
53
60
  export * from "./Components/Viewers/DataTableParts.js";
61
+ export * from "./Components/Viewers/OwnerFiles.js";
62
+ export * from "./Components/Viewers/RecordCards.js";
54
63
  // Helpers
55
64
  export * from "./Helpers/ChatHooks.js";
56
65
  export * from "./Helpers/DatePreferences.js";
57
66
  export * from "./Helpers/DragHelpers.js";
58
67
  export * from "./Helpers/PopoverHelpers.js";
59
68
  export * from "./Helpers/PopoverHooks.js";
69
+ export * from "./Helpers/ServerPreview.js";
70
+ export { downloadText } from "./Helpers/TableExport.js";
60
71
  export * from "./Helpers/UserPreferences.js";
61
72
  // Services
62
73
  export * from "./Services/BrowserApi.js";
63
74
  export * from "./Services/ChatApi.js";
64
75
  export * from "./Services/ExcelApi.js";
76
+ export * from "./Services/FileApi.js";
77
+ export * from "./Services/ImageEditorLoader.js";
78
+ export * from "./Services/ModuleImport.js";
65
79
  export * from "./Services/JoinSources.js";
66
80
  export * from "./Services/ThemeService.js";
@@ -0,0 +1,50 @@
1
+ import { type CSSProperties, type ReactNode, type Ref } from "react";
2
+ /** What a page shown in a `DocumentFrame` can be asked to do. */
3
+ export interface DocumentFrameHandle {
4
+ /** Opens the print dialog for the page shown; `false` when it cannot (not `printable`, no page yet, frame not loaded). */
5
+ print: () => boolean;
6
+ }
7
+ export interface DocumentFrameProps {
8
+ /** The whole page to show, or null/undefined (or "") while there is none yet. */
9
+ html: string | null | undefined;
10
+ /** The frame's name for screen readers and its title attribute: "Voucher preview". Required. */
11
+ title: string;
12
+ /**
13
+ * A read is under way: the page shown stays, and a read that lasts dims it and says `loadingMessage` in the bar (a
14
+ * quick one says nothing, as a form's status line). The page area is `aria-busy` from the start. While loading, or
15
+ * with an `error`, an `html` of null keeps the last page in view.
16
+ */
17
+ loading?: boolean;
18
+ /** What the bar says while a read lasts. Default "Loading…". */
19
+ loadingMessage?: ReactNode;
20
+ /** Why the page could not be shown: a line with `role="alert"` in the bar above the page, which stays in view under it. */
21
+ error?: ReactNode;
22
+ /** Shown in the page area before any page arrives. Default "Nothing to show yet." */
23
+ emptyMessage?: ReactNode;
24
+ /**
25
+ * Buttons the page puts in the frame's bar, at its end beside the status line ("Print", "Reset to default"); on a
26
+ * narrow frame they wrap under the line. The bar shows only when there are actions, or a status to say.
27
+ */
28
+ actions?: ReactNode;
29
+ /**
30
+ * The page may be printed through the handle's `print()`: only then is the frame sandboxed
31
+ * "allow-same-origin allow-modals" (same origin so the parent can call the frame's print(); modals so print() opens
32
+ * the dialog). Never allow-scripts. Without it the sandbox is "" (an opaque origin, nothing allowed).
33
+ */
34
+ printable?: boolean;
35
+ className?: string;
36
+ style?: CSSProperties;
37
+ ref?: Ref<DocumentFrameHandle>;
38
+ }
39
+ /**
40
+ * A whole HTML page from a server, in a frame that fills the height its container gives it (at least 22rem) and
41
+ * scrolls inside itself: an A4 sheet wider than the frame scrolls sideways there, never the page. Above it, a bar holds
42
+ * the status line and the page's `actions`. A page read again keeps its place while the title stays the same (a
43
+ * `printable` frame; the sandbox of another lets nobody look in).
44
+ *
45
+ * @example
46
+ * const frame = useRef<DocumentFrameHandle>(null);
47
+ * <DocumentFrame ref={frame} title="Voucher preview" html={preview} loading={reading} error={problem} printable
48
+ * actions={<Button onClick={() => frame.current?.print()}>Print</Button>} />
49
+ */
50
+ export declare function DocumentFrame({ html, title, loading, loadingMessage, error, emptyMessage, actions, printable, className, style, ref, }: DocumentFrameProps): import("react").JSX.Element;
@@ -0,0 +1,83 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { iconAlertCircle, iconFileText, iconLoader2, InlineIcon } from "nexus-icons";
4
+ import { useImperativeHandle, useLayoutEffect, useRef } from "react";
5
+ import { useSlowBusy } from "../../Helpers/BusyHooks.js";
6
+ import { cx } from "../../Helpers/ClassHelpers.js";
7
+ /*
8
+ * A whole HTML page a server wrote - a print layout's preview, a document to print - shown in a sandboxed frame. The
9
+ * page is the server's, from `<!DOCTYPE html>` to `</html>`, with its own `@page` sheet and screen style, so it is never
10
+ * mixed into this page's DOM and styles: it goes into an `<iframe srcdoc>`, and **no script ever runs in it**, whatever
11
+ * the page holds. The sandbox is "" (an opaque origin: nothing allowed, the parent cannot reach in either), or, for a
12
+ * page that may be printed, "allow-same-origin allow-modals": the parent may call the frame's print(), and print() may
13
+ * open its dialog. Never allow-scripts, allow-forms, allow-popups or allow-top-navigation.
14
+ */
15
+ /** A page that may be printed: the parent reaches the frame's window, and its print() may open the dialog. */
16
+ const PRINTABLE_SANDBOX = "allow-same-origin allow-modals";
17
+ const hasContent = (node) => node !== undefined && node !== null && node !== false && node !== "";
18
+ /**
19
+ * A whole HTML page from a server, in a frame that fills the height its container gives it (at least 22rem) and
20
+ * scrolls inside itself: an A4 sheet wider than the frame scrolls sideways there, never the page. Above it, a bar holds
21
+ * the status line and the page's `actions`. A page read again keeps its place while the title stays the same (a
22
+ * `printable` frame; the sandbox of another lets nobody look in).
23
+ *
24
+ * @example
25
+ * const frame = useRef<DocumentFrameHandle>(null);
26
+ * <DocumentFrame ref={frame} title="Voucher preview" html={preview} loading={reading} error={problem} printable
27
+ * actions={<Button onClick={() => frame.current?.print()}>Print</Button>} />
28
+ */
29
+ export function DocumentFrame({ html, title, loading = false, loadingMessage = "Loading…", error, emptyMessage = "Nothing to show yet.", actions, printable = false, className, style, ref, }) {
30
+ const frameRef = useRef(null);
31
+ const failed = hasContent(error);
32
+ // The page shown: the one given, else the last one while a read is under way or has failed.
33
+ const last = useRef(null);
34
+ const page = html ? html : loading || failed ? last.current : null;
35
+ const slow = useSlowBusy(loading);
36
+ const said = failed || slow;
37
+ const shownTitle = useRef(title);
38
+ const place = useRef(null);
39
+ // A new page replaces the one shown: note where the old one was scrolled to, while it is still there (the frame
40
+ // loads the new one a task later). Only a printable frame's page can be read; another's throws, and keeps nothing.
41
+ useLayoutEffect(() => {
42
+ try {
43
+ const view = frameRef.current?.contentWindow;
44
+ place.current = view && view.location.href === "about:srcdoc" ? { title: shownTitle.current, x: view.scrollX, y: view.scrollY } : null;
45
+ }
46
+ catch {
47
+ place.current = null;
48
+ }
49
+ last.current = page;
50
+ shownTitle.current = title;
51
+ }, [page, title]);
52
+ function keepPlace() {
53
+ const kept = place.current;
54
+ if (!kept || kept.title !== title || (!kept.x && !kept.y))
55
+ return;
56
+ try {
57
+ frameRef.current?.contentWindow?.scrollTo(kept.x, kept.y);
58
+ }
59
+ catch {
60
+ // The frame is not ours to reach (not printable): it opens at the top.
61
+ }
62
+ }
63
+ useImperativeHandle(ref, () => ({
64
+ print() {
65
+ const frame = frameRef.current;
66
+ if (!printable || !frame)
67
+ return false;
68
+ try {
69
+ const view = frame.contentWindow;
70
+ const document_ = frame.contentDocument;
71
+ if (!view || !document_ || document_.URL !== "about:srcdoc" || document_.readyState !== "complete")
72
+ return false;
73
+ view.focus();
74
+ view.print();
75
+ return true;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ },
81
+ }), [printable]);
82
+ return (_jsxs("div", { className: cx("nx-document-frame", className), style: style, children: [_jsxs("div", { className: cx("nx-form-buttons nx-form-buttons--end nx-document-frame__bar", !said && !hasContent(actions) && "nx-visually-hidden"), children: [failed ? (_jsxs("p", { className: "nx-form-status nx-form-status--error", role: "alert", children: [_jsx(InlineIcon, { icon: iconAlertCircle }), _jsx("span", { children: error })] }, "error")) : (_jsx("p", { className: "nx-form-status", role: "status", children: slow && (_jsxs(_Fragment, { children: [_jsx(InlineIcon, { icon: iconLoader2, className: "nx-spin" }), _jsx("span", { children: loadingMessage })] })) }, "status")), hasContent(actions) && _jsx("div", { className: "nx-form-buttons__actions", children: actions })] }), _jsx("div", { className: cx("nx-document-frame__view", slow && "nx-document-frame__view--busy"), "aria-busy": loading || undefined, children: page ? (_jsx("iframe", { ref: frameRef, className: "nx-document-frame__page", title: title, sandbox: printable ? PRINTABLE_SANDBOX : "", srcDoc: page, referrerPolicy: "no-referrer", onLoad: keepPlace }, printable ? "printable" : "sealed")) : (!loading && (_jsxs("p", { className: "nx-form-status nx-document-frame__empty", children: [_jsx(InlineIcon, { icon: iconFileText }), _jsx("span", { children: emptyMessage })] }))) })] }));
83
+ }
@@ -12,6 +12,10 @@ export interface MenuItem<T extends string = string> {
12
12
  shortcut?: string;
13
13
  /** `danger` draws the item in the danger color, for Delete and the like. */
14
14
  tone?: "danger";
15
+ /**
16
+ * Switched off. With a `description` it is held, saying why: still reached by the arrow keys, and a press shows the
17
+ * description in a note beside it, as a held Add and a read-only field do.
18
+ */
15
19
  disabled?: boolean;
16
20
  /** Renders the item as a link. */
17
21
  href?: string;
@@ -4,6 +4,7 @@ import { iconCheck, InlineIcon } from "nexus-icons";
4
4
  import { cloneElement, useId, useLayoutEffect, useRef, useState, } from "react";
5
5
  import { cx } from "../../Helpers/ClassHelpers.js";
6
6
  import { followAnchor, isPopoverOpen, placeAtPoint, placeUnderAnchor, showPanel } from "../../Helpers/PopoverHelpers.js";
7
+ import { useReadOnlyNotice } from "../Inputs/ReadOnlyNotice.js";
7
8
  export function isMenuItem(entry) {
8
9
  return "value" in entry;
9
10
  }
@@ -13,8 +14,9 @@ const ITEM_ROLE = {
13
14
  checkbox: "menuitemcheckbox",
14
15
  };
15
16
  // A row, and a control an app put in a heading (the admin's account card has the picture there). The arrow keys walk
16
- // both: Tab shuts a menu, so a control inside one is reachable no other way.
17
- const ENABLED_ITEM = '.nx-menu__item:not([aria-disabled="true"]), .nx-menu__heading [role="menuitem"]:not([aria-disabled="true"])';
17
+ // both: Tab shuts a menu, so a control inside one is reachable no other way. A row switched off with its reason (`data-held`)
18
+ // is walked too, so the keyboard can press it and hear why.
19
+ const ENABLED_ITEM = '.nx-menu__item:not([aria-disabled="true"]), .nx-menu__item[data-held], .nx-menu__heading [role="menuitem"]:not([aria-disabled="true"])';
18
20
  function enabledItems(panel) {
19
21
  return Array.from(panel.querySelectorAll(ENABLED_ITEM));
20
22
  }
@@ -51,9 +53,17 @@ function mergeRefs(...refs) {
51
53
  * the next item starting with it, Enter or Space chooses, Esc closes, and Tab closes and moves on.
52
54
  */
53
55
  export function MenuPanel({ id, items, onSelect, kind = "menu", size = "md", closeOnSelect = true, className, ref, onToggle, ...labelling }) {
56
+ // A held item's reason, shown on a press in the note a read-only field and a table's held Add show, beside the item.
57
+ const panelRef = useRef(null);
58
+ const [reason, setReason] = useState(null);
59
+ const notice = useReadOnlyNotice(true, panelRef, reason, { anchor: target => target.closest(".nx-menu__item"), typing: false, lock: "label" });
54
60
  function choose(item, event) {
55
61
  if (item.disabled) {
56
62
  event.preventDefault();
63
+ if (item.description) {
64
+ setReason(item.description);
65
+ notice.show(event.currentTarget);
66
+ }
57
67
  return;
58
68
  }
59
69
  onSelect?.(item.value, item);
@@ -102,7 +112,7 @@ export function MenuPanel({ id, items, onSelect, kind = "menu", size = "md", clo
102
112
  return;
103
113
  pointerRef.current = { x: event.clientX, y: event.clientY };
104
114
  const item = event.currentTarget;
105
- if (item.getAttribute("aria-disabled") !== "true" && document.activeElement !== item)
115
+ if ((item.getAttribute("aria-disabled") !== "true" || item.hasAttribute("data-held")) && document.activeElement !== item)
106
116
  item.focus({ preventScroll: true });
107
117
  }
108
118
  // A control in a heading is chosen like a row, so it closes the menu like one.
@@ -113,27 +123,33 @@ export function MenuPanel({ id, items, onSelect, kind = "menu", size = "md", clo
113
123
  const role = ITEM_ROLE[kind];
114
124
  // When any item has an icon, every item keeps the icon column, so the labels line up.
115
125
  const hasIcons = items.some(entry => isMenuItem(entry) && entry.icon);
116
- return (_jsx("div", { ref: ref, id: id, popover: "auto", role: "menu", tabIndex: -1, className: cx("nx-popover", "nx-menu", `nx-menu--${size}`, className), onToggle: onToggle, onKeyDown: onKeyDown, onClick: onClick, ...labelling, children: _jsx("div", { role: "none", className: "nx-menu__list nx-popover__scroll", children: items.map((entry, index) => {
117
- // Items are keyed by their value, so a separator keyed by its index alone collided with an item whose value
118
- // was that number ("5" in a menu of projects). The prefix keeps the two apart.
119
- if (!isMenuItem(entry)) {
120
- if ("separator" in entry)
121
- return _jsx("div", { role: "separator", className: "nx-menu__separator" }, `nx-at-${index}`);
122
- return (_jsx("div", { role: "presentation", className: "nx-menu__heading", children: entry.heading }, `nx-at-${index}`));
123
- }
124
- const item = entry;
125
- const content = (_jsxs(_Fragment, { children: [kind !== "menu" && _jsx("span", { className: "nx-menu__check", children: item.checked && _jsx(InlineIcon, { icon: iconCheck }) }), hasIcons && _jsx("span", { className: "nx-menu__icon", children: item.icon }), _jsxs("span", { className: "nx-menu__body", children: [_jsx("span", { className: "nx-menu__label", children: item.label }), item.description && _jsx("span", { className: "nx-menu__description", children: item.description })] }), item.shortcut && _jsx("kbd", { className: "nx-menu__shortcut", children: item.shortcut })] }));
126
- const shared = {
127
- role,
128
- className: cx("nx-menu__item", item.tone === "danger" && "nx-menu__item--danger", item.checked && "nx-menu__item--checked"),
129
- tabIndex: -1,
130
- title: item.title,
131
- "aria-disabled": item.disabled || undefined,
132
- "aria-checked": kind === "menu" ? undefined : Boolean(item.checked),
133
- onPointerMove,
134
- };
135
- return item.href && !item.disabled ? (_jsx("a", { ...shared, href: item.href, target: item.target, rel: item.target === "_blank" ? "noopener" : undefined, onClick: event => choose(item, event), children: content }, item.value)) : (_jsx("button", { ...shared, type: "button", onClick: event => choose(item, event), children: content }, item.value));
136
- }) }) }));
126
+ return (_jsxs("div", { ref: mergeRefs(ref, panelRef), id: id, popover: "auto", role: "menu", tabIndex: -1, className: cx("nx-popover", "nx-menu", `nx-menu--${size}`, className), onToggle: event => {
127
+ // The note goes with the menu.
128
+ if (event.newState === "closed")
129
+ notice.hide();
130
+ onToggle?.(event);
131
+ }, onKeyDown: onKeyDown, onClick: onClick, ...labelling, children: [_jsx("div", { role: "none", className: "nx-menu__list nx-popover__scroll", children: items.map((entry, index) => {
132
+ // Items are keyed by their value, so a separator keyed by its index alone collided with an item whose value
133
+ // was that number ("5" in a menu of projects). The prefix keeps the two apart.
134
+ if (!isMenuItem(entry)) {
135
+ if ("separator" in entry)
136
+ return _jsx("div", { role: "separator", className: "nx-menu__separator" }, `nx-at-${index}`);
137
+ return (_jsx("div", { role: "presentation", className: "nx-menu__heading", children: entry.heading }, `nx-at-${index}`));
138
+ }
139
+ const item = entry;
140
+ const content = (_jsxs(_Fragment, { children: [kind !== "menu" && _jsx("span", { className: "nx-menu__check", children: item.checked && _jsx(InlineIcon, { icon: iconCheck }) }), hasIcons && _jsx("span", { className: "nx-menu__icon", children: item.icon }), _jsxs("span", { className: "nx-menu__body", children: [_jsx("span", { className: "nx-menu__label", children: item.label }), item.description && _jsx("span", { className: "nx-menu__description", children: item.description })] }), item.shortcut && _jsx("kbd", { className: "nx-menu__shortcut", children: item.shortcut })] }));
141
+ const shared = {
142
+ role,
143
+ className: cx("nx-menu__item", item.tone === "danger" && "nx-menu__item--danger", item.checked && "nx-menu__item--checked"),
144
+ tabIndex: -1,
145
+ title: item.title,
146
+ "aria-disabled": item.disabled || undefined,
147
+ "data-held": (item.disabled && Boolean(item.description)) || undefined,
148
+ "aria-checked": kind === "menu" ? undefined : Boolean(item.checked),
149
+ onPointerMove,
150
+ };
151
+ return item.href && !item.disabled ? (_jsx("a", { ...shared, href: item.href, target: item.target, rel: item.target === "_blank" ? "noopener" : undefined, onClick: event => choose(item, event), children: content }, item.value)) : (_jsx("button", { ...shared, type: "button", onClick: event => choose(item, event), children: content }, item.value));
152
+ }) }), notice.notice] }));
137
153
  }
138
154
  /**
139
155
  * A dropdown menu: a button that opens a list of actions in a native popover under it (above it when there is no
@@ -0,0 +1,95 @@
1
+ import { type CSSProperties, type ReactNode } from "react";
2
+ /** The paper a document prints on: A5 portrait, or an 80 mm thermal roll. */
3
+ export type PrintPaper = "a5" | "thermal";
4
+ /** The papers, as a `TabButtons` takes its options: `<TabButtons options={PRINT_PAPERS} value={paper} onChange={setPaper} />`. */
5
+ export declare const PRINT_PAPERS: readonly {
6
+ value: PrintPaper;
7
+ label: string;
8
+ }[];
9
+ /** One line of facts: a label and its value. `hidden`, or a falsy entry, leaves it out. */
10
+ export interface PrintFact {
11
+ label: ReactNode;
12
+ value: ReactNode;
13
+ hidden?: boolean;
14
+ }
15
+ /** One column of a lines table. Default `align`: `end` for the last column (the money), `start` for the others. */
16
+ export interface PrintColumn {
17
+ label: ReactNode;
18
+ align?: "start" | "end";
19
+ }
20
+ /** One line of a lines table: a cell per column, and a smaller note under the first ("Paid up to 3 Dec"). */
21
+ export interface PrintLine {
22
+ cells: readonly ReactNode[];
23
+ note?: ReactNode;
24
+ hidden?: boolean;
25
+ }
26
+ /** Labels and values, one per line, the labels in one column. */
27
+ export interface PrintFactsBlock {
28
+ facts: readonly (PrintFact | null | false | undefined)[];
29
+ }
30
+ /** A table of lines with its headings, and totals under a rule (each a label across the other columns, and a value). */
31
+ export interface PrintLinesBlock {
32
+ columns: readonly PrintColumn[];
33
+ lines: readonly (PrintLine | null | false | undefined)[];
34
+ totals?: readonly (PrintFact | null | false | undefined)[];
35
+ }
36
+ /** What a document holds between its head and its footer, in order. */
37
+ export type PrintBlock = PrintFactsBlock | PrintLinesBlock;
38
+ /** A logo by its address: an image the page already has the address of (a Drive file's view address, a data URL). */
39
+ export interface PrintLogo {
40
+ src: string;
41
+ /** What the logo is, for a screen reader: the company's name. Default "" (the title beside it says it). */
42
+ alt?: string;
43
+ }
44
+ export interface PrintDocumentProps {
45
+ /** The paper: `a5` (default) or `thermal`, an 80 mm roll. Sets the width on screen and the printed page. */
46
+ paper?: PrintPaper;
47
+ /**
48
+ * The company's logo at the head: an image by its address (`{ src, alt }`), or anything to draw (an `<img>`, an
49
+ * `<svg>`). On A5 it sits beside the title lines, in a column of 24 mm at most 16 mm tall, so the head grows no taller
50
+ * than its text; on an 80 mm roll it sits small and centred above the title (at most 36 x 10 mm), or not at all with
51
+ * `logoOnThermal={false}`. Printed in black and white. An image must have loaded before printing.
52
+ */
53
+ logo?: PrintLogo | ReactNode;
54
+ /** The logo on an 80 mm roll too. Default true. */
55
+ logoOnThermal?: boolean;
56
+ /** The big line at the top: the company's name, or the document's own title. */
57
+ title: ReactNode;
58
+ /** Smaller lines under the title: the company under a title of its own, the branch, an address. Falsy ones are left out. */
59
+ subtitles?: readonly ReactNode[];
60
+ /** What the document is, in capitals under the head: "Receipt", "Bill on credit". */
61
+ kind?: ReactNode;
62
+ /** A stamp across it, under the head: "Voided: entered twice". */
63
+ stamp?: ReactNode;
64
+ /** The facts and lines, in order. */
65
+ blocks: readonly PrintBlock[];
66
+ /** The last lines, under a rule, line breaks kept: the company's thanks, its terms. */
67
+ footer?: ReactNode;
68
+ /** The document's name to a screen reader: "Receipt 1024". */
69
+ "aria-label"?: string;
70
+ id?: string;
71
+ className?: string;
72
+ style?: CSSProperties;
73
+ }
74
+ /**
75
+ * A printable document - a receipt, a bill, a statement - declared by params: a head (title, smaller lines, its kind),
76
+ * an optional stamp, blocks of facts and lines, and a footer. On screen it reads in the theme's colours at its paper's
77
+ * width; printed (`window.print()`), it is the only thing on the paper, black on white, on the paper picked: the
78
+ * component writes the page's @page rule while it is on the page. No state, so it renders in server and client
79
+ * components alike.
80
+ *
81
+ * @example
82
+ * <PrintDocument
83
+ * paper={paper}
84
+ * title="Himal Cable Network"
85
+ * subtitles={["Head office"]}
86
+ * kind="Receipt"
87
+ * stamp={voided ? "Voided: entered twice" : undefined}
88
+ * blocks={[
89
+ * { facts: [{ label: "Receipt no.", value: "1024" }, { label: "Date", value: "3 Nov 2026" }] },
90
+ * { columns: [{ label: "For" }, { label: "Amount" }], lines: [{ cells: ["Basic, 3 months", "1,500.00"], note: "Paid up to 3 Feb 2027" }], totals: [{ label: "Total paid", value: "Rs. 1,500.00" }] },
91
+ * ]}
92
+ * footer="Thank you."
93
+ * />
94
+ */
95
+ export declare function PrintDocument({ paper, logo, logoOnThermal, title, subtitles, kind, stamp, blocks, footer, "aria-label": ariaLabel, id, className, style }: PrintDocumentProps): import("react").JSX.Element;
@@ -0,0 +1,62 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { isValidElement } from "react";
3
+ import { cx } from "../../Helpers/ClassHelpers.js";
4
+ /** The papers, as a `TabButtons` takes its options: `<TabButtons options={PRINT_PAPERS} value={paper} onChange={setPaper} />`. */
5
+ export const PRINT_PAPERS = [
6
+ { value: "a5", label: "A5" },
7
+ { value: "thermal", label: "80 mm" },
8
+ ];
9
+ /**
10
+ * The paper each choice prints on. An unnamed @page applies to every printed page, wherever the document sits in the
11
+ * page, so the browser takes it (a named page would need the document in the normal flow, and a printed one is lifted
12
+ * out of it); only a document on the page writes one. An 80 mm roll is printed on a 297 mm length, cut where it ends.
13
+ */
14
+ const PAGE_RULES = {
15
+ a5: "@page { size: A5 portrait; margin: 10mm; }",
16
+ thermal: "@page { size: 80mm 297mm; margin: 4mm; }",
17
+ };
18
+ /** Whether the logo given is an address (`{ src }`) rather than something to draw. */
19
+ const isPrintLogo = (logo) => typeof logo === "object" && logo !== null && !isValidElement(logo) && typeof logo.src === "string";
20
+ const shown = (items) => (items ?? []).filter((item) => Boolean(item) && !item.hidden);
21
+ function FactsBlock({ block }) {
22
+ const facts = shown(block.facts);
23
+ if (facts.length === 0)
24
+ return null;
25
+ return (_jsx("dl", { className: "nx-print__facts", children: facts.map((fact, index) => (_jsxs("div", { children: [_jsx("dt", { children: fact.label }), _jsx("dd", { children: fact.value })] }, index))) }));
26
+ }
27
+ function LinesBlock({ block }) {
28
+ const { columns } = block;
29
+ const lines = shown(block.lines);
30
+ const totals = shown(block.totals);
31
+ const endOf = (index) => ((columns[index]?.align ?? (index === columns.length - 1 && index > 0 ? "end" : "start")) === "end" ? "nx-print__end" : undefined);
32
+ return (_jsxs("table", { className: "nx-print__lines", children: [_jsx("thead", { children: _jsx("tr", { children: columns.map((column, index) => (_jsx("th", { scope: "col", className: endOf(index), children: column.label }, index))) }) }), _jsx("tbody", { children: lines.map((line, row) => (_jsx("tr", { children: columns.map((_, index) => (_jsxs("td", { className: endOf(index), children: [line.cells[index], index === 0 && line.note ? _jsx("span", { className: "nx-print__note", children: line.note }) : null] }, index))) }, row))) }), totals.length > 0 && (_jsx("tfoot", { children: totals.map((total, row) => (_jsxs("tr", { children: [_jsx("th", { scope: "row", colSpan: Math.max(1, columns.length - 1), children: total.label }), _jsx("td", { className: "nx-print__end", children: total.value })] }, row))) }))] }));
33
+ }
34
+ /**
35
+ * A printable document - a receipt, a bill, a statement - declared by params: a head (title, smaller lines, its kind),
36
+ * an optional stamp, blocks of facts and lines, and a footer. On screen it reads in the theme's colours at its paper's
37
+ * width; printed (`window.print()`), it is the only thing on the paper, black on white, on the paper picked: the
38
+ * component writes the page's @page rule while it is on the page. No state, so it renders in server and client
39
+ * components alike.
40
+ *
41
+ * @example
42
+ * <PrintDocument
43
+ * paper={paper}
44
+ * title="Himal Cable Network"
45
+ * subtitles={["Head office"]}
46
+ * kind="Receipt"
47
+ * stamp={voided ? "Voided: entered twice" : undefined}
48
+ * blocks={[
49
+ * { facts: [{ label: "Receipt no.", value: "1024" }, { label: "Date", value: "3 Nov 2026" }] },
50
+ * { columns: [{ label: "For" }, { label: "Amount" }], lines: [{ cells: ["Basic, 3 months", "1,500.00"], note: "Paid up to 3 Feb 2027" }], totals: [{ label: "Total paid", value: "Rs. 1,500.00" }] },
51
+ * ]}
52
+ * footer="Thank you."
53
+ * />
54
+ */
55
+ export function PrintDocument({ paper = "a5", logo, logoOnThermal = true, title, subtitles, kind, stamp, blocks, footer, "aria-label": ariaLabel, id, className, style }) {
56
+ const thermal = paper === "thermal";
57
+ const lines = (_jsxs(_Fragment, { children: [_jsx("p", { className: "nx-print__title", children: title }), (subtitles ?? []).filter(Boolean).map((line, index) => (_jsx("p", { children: line }, index))), kind ? _jsx("p", { className: "nx-print__kind", children: kind }) : null] }));
58
+ // Not lazy: a print starts at once and takes only what has loaded.
59
+ const mark = isPrintLogo(logo) ? _jsx("img", { src: logo.src, alt: logo.alt ?? "", decoding: "sync" }) : logo;
60
+ const withLogo = mark !== undefined && mark !== null && mark !== false && mark !== "" && !(thermal && !logoOnThermal);
61
+ return (_jsxs("article", { id: id, className: cx("nx-print", thermal && "nx-print--thermal", className), style: style, "aria-label": ariaLabel, children: [_jsx("style", { children: PAGE_RULES[paper] }), withLogo ? (_jsxs("header", { className: "nx-print__head nx-print__head--logo", children: [_jsx("div", { className: "nx-print__logo", children: mark }), _jsx("div", { className: "nx-print__heading", children: lines })] })) : (_jsx("header", { className: "nx-print__head", children: lines })), stamp ? (_jsx("p", { className: "nx-print__stamp", role: "note", children: stamp })) : null, blocks.map((block, index) => ("facts" in block ? _jsx(FactsBlock, { block: block }, index) : _jsx(LinesBlock, { block: block }, index))), footer ? _jsx("footer", { className: "nx-print__foot", children: footer }) : null] }));
62
+ }
@@ -0,0 +1,59 @@
1
+ import { type ReactNode } from "react";
2
+ import type { Size } from "../../Interfaces/TypeInterfaces.ts";
3
+ import type { TabButtonOption } from "./TabButtons.tsx";
4
+ /** A tab: the shape of a `TabButtons` option. */
5
+ export type TabOption<T extends string = string> = TabButtonOption<T>;
6
+ /**
7
+ * `list`: rows, one under another, for a side column. `nav`: the horizontal track. `auto`: the list, and in a
8
+ * container narrower than 720px (a phone) one strip of the same rows that scrolls sideways.
9
+ */
10
+ export type TabsDesign = "list" | "nav" | "auto";
11
+ /** A label is required: pass `aria-label`, or `aria-labelledby` pointing at a visible heading. */
12
+ export type TabsProps<T extends string = string> = {
13
+ options: readonly TabOption<T>[];
14
+ /** The chosen tab. */
15
+ value: T;
16
+ onChange: (value: T) => void;
17
+ /**
18
+ * Names the tabs and their panels: tab `{id}-tab-{value}`, panel `{id}-panel-{value}`. Give the panel the same id,
19
+ * or draw it with `TabPanel`.
20
+ */
21
+ id: string;
22
+ /** Default `list`. */
23
+ design?: TabsDesign;
24
+ /** Default `md`. */
25
+ size?: Size;
26
+ /** `neutral` (default) or `primary`, as `TabButtons`. */
27
+ tone?: "neutral" | "primary";
28
+ className?: string;
29
+ } & ({
30
+ "aria-label": string;
31
+ "aria-labelledby"?: never;
32
+ } | {
33
+ "aria-labelledby": string;
34
+ "aria-label"?: never;
35
+ });
36
+ /** The ids a tab and its panel are joined by. */
37
+ export declare function tabIds(id: string, value: string): {
38
+ tab: string;
39
+ panel: string;
40
+ };
41
+ /**
42
+ * Tabs: one button per panel, the chosen one marked `aria-selected`. Only the chosen tab is in the Tab order; the
43
+ * arrow keys move to the next or previous tab and open it (Up and Down, Left and Right, whichever way they lie), Home
44
+ * and End to the first and the last.
45
+ */
46
+ export declare function Tabs<T extends string>({ options, value, onChange, id, design, size, tone, className, ...labelling }: TabsProps<T>): import("react").JSX.Element;
47
+ export interface TabPanelProps {
48
+ /** The `id` of the `Tabs` it belongs to. */
49
+ tabs: string;
50
+ /** The tab it shows. */
51
+ value: string;
52
+ className?: string;
53
+ children?: ReactNode;
54
+ }
55
+ /**
56
+ * The chosen tab's panel, joined to its tab. It takes the focus on Tab from the tabs (`tabIndex` 0), so a panel with
57
+ * nothing to focus is still reached and read.
58
+ */
59
+ export declare function TabPanel({ tabs, value, className, children }: TabPanelProps): import("react").JSX.Element;