nexus-shared 3.2.0 → 4.1.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 (271) hide show
  1. package/CHANGELOG.md +2359 -0
  2. package/README.md +1911 -1123
  3. package/dist/Client.Index.d.ts +27 -0
  4. package/dist/Client.Index.js +27 -0
  5. package/dist/Components/Chats/ChatComposer.d.ts +7 -1
  6. package/dist/Components/Chats/ChatComposer.js +9 -6
  7. package/dist/Components/Chats/ChatParts.d.ts +15 -2
  8. package/dist/Components/Chats/ChatParts.js +15 -4
  9. package/dist/Components/Chats/ChatPopup.d.ts +46 -0
  10. package/dist/Components/Chats/ChatPopup.js +34 -0
  11. package/dist/Components/Chats/ChatThread.d.ts +9 -1
  12. package/dist/Components/Chats/ChatThread.js +54 -6
  13. package/dist/Components/Dashboards/DashboardBoard.d.ts +62 -0
  14. package/dist/Components/Dashboards/DashboardBoard.js +594 -0
  15. package/dist/Components/Dashboards/DashboardWidgets.d.ts +155 -0
  16. package/dist/Components/Dashboards/DashboardWidgets.js +159 -0
  17. package/dist/Components/Dashboards/WidgetFrame.d.ts +51 -0
  18. package/dist/Components/Dashboards/WidgetFrame.js +45 -0
  19. package/dist/Components/Documents/Button.d.ts +6 -1
  20. package/dist/Components/Documents/Button.js +2 -2
  21. package/dist/Components/Documents/DocumentFrame.d.ts +50 -0
  22. package/dist/Components/Documents/DocumentFrame.js +83 -0
  23. package/dist/Components/Documents/Menu.d.ts +4 -0
  24. package/dist/Components/Documents/Menu.js +40 -24
  25. package/dist/Components/Documents/PrintDocument.d.ts +95 -0
  26. package/dist/Components/Documents/PrintDocument.js +62 -0
  27. package/dist/Components/Documents/TabButtons.d.ts +6 -0
  28. package/dist/Components/Documents/TabButtons.js +5 -4
  29. package/dist/Components/Documents/Tabs.d.ts +59 -0
  30. package/dist/Components/Documents/Tabs.js +70 -0
  31. package/dist/Components/Domains/DomainCard.d.ts +98 -0
  32. package/dist/Components/Domains/DomainCard.js +182 -0
  33. package/dist/Components/Forms/ApiForm.js +24 -3
  34. package/dist/Components/Forms/Crud.d.ts +2 -2
  35. package/dist/Components/Forms/Crud.js +628 -247
  36. package/dist/Components/Forms/CrudActions.d.ts +137 -0
  37. package/dist/Components/Forms/CrudActions.js +214 -0
  38. package/dist/Components/Forms/CrudHistory.d.ts +6 -1
  39. package/dist/Components/Forms/CrudHistory.js +14 -6
  40. package/dist/Components/Forms/CrudNarrowPicker.d.ts +31 -0
  41. package/dist/Components/Forms/CrudNarrowPicker.js +180 -0
  42. package/dist/Components/Forms/CrudParts.d.ts +37 -2
  43. package/dist/Components/Forms/CrudParts.js +123 -7
  44. package/dist/Components/Forms/CrudRowForms.d.ts +26 -0
  45. package/dist/Components/Forms/CrudRowForms.js +174 -0
  46. package/dist/Components/Forms/CrudScopes.d.ts +28 -0
  47. package/dist/Components/Forms/CrudScopes.js +76 -0
  48. package/dist/Components/Forms/CrudTree.d.ts +90 -0
  49. package/dist/Components/Forms/CrudTree.js +363 -0
  50. package/dist/Components/Forms/CrudWorkspace.d.ts +73 -0
  51. package/dist/Components/Forms/CrudWorkspace.js +177 -0
  52. package/dist/Components/Forms/ExcelImport.js +3 -3
  53. package/dist/Components/Forms/Form.d.ts +6 -5
  54. package/dist/Components/Forms/Form.js +134 -21
  55. package/dist/Components/Forms/FormLeaveGuard.d.ts +44 -0
  56. package/dist/Components/Forms/FormLeaveGuard.js +65 -0
  57. package/dist/Components/Forms/FormQuestion.d.ts +6 -0
  58. package/dist/Components/Forms/FormQuestion.js +28 -0
  59. package/dist/Components/Forms/RecordFlows.d.ts +50 -0
  60. package/dist/Components/Forms/RecordFlows.js +282 -0
  61. package/dist/Components/Forms/SubmitForm.d.ts +37 -2
  62. package/dist/Components/Forms/SubmitForm.js +182 -10
  63. package/dist/Components/Inputs/CheckBox.js +1 -1
  64. package/dist/Components/Inputs/ChoiceField.d.ts +21 -1
  65. package/dist/Components/Inputs/ChoiceField.js +28 -2
  66. package/dist/Components/Inputs/ComputedInput.d.ts +51 -0
  67. package/dist/Components/Inputs/ComputedInput.js +179 -0
  68. package/dist/Components/Inputs/Dropdown.d.ts +13 -0
  69. package/dist/Components/Inputs/Dropdown.js +8 -6
  70. package/dist/Components/Inputs/DropdownParts.d.ts +3 -1
  71. package/dist/Components/Inputs/DropdownParts.js +16 -4
  72. package/dist/Components/Inputs/Field.d.ts +7 -1
  73. package/dist/Components/Inputs/Field.js +2 -2
  74. package/dist/Components/Inputs/GroupForm.d.ts +6 -6
  75. package/dist/Components/Inputs/GroupForm.js +12 -12
  76. package/dist/Components/Inputs/InputRenderer.d.ts +13 -6
  77. package/dist/Components/Inputs/InputRenderer.js +37 -32
  78. package/dist/Components/Inputs/MarkdownEditor.d.ts +6 -0
  79. package/dist/Components/Inputs/MarkdownEditor.js +32 -6
  80. package/dist/Components/Inputs/PictureInput.d.ts +41 -0
  81. package/dist/Components/Inputs/PictureInput.js +75 -0
  82. package/dist/Components/Inputs/RadioGroup.d.ts +16 -3
  83. package/dist/Components/Inputs/RadioGroup.js +8 -3
  84. package/dist/Components/Inputs/RowsInput.d.ts +15 -2
  85. package/dist/Components/Inputs/RowsInput.js +96 -8
  86. package/dist/Components/Inputs/TabularForm.d.ts +5 -5
  87. package/dist/Components/Inputs/TabularForm.js +45 -32
  88. package/dist/Components/Inputs/TextArea.d.ts +6 -0
  89. package/dist/Components/Inputs/TextArea.js +11 -2
  90. package/dist/Components/Inputs/TextBoxTypes.d.ts +6 -0
  91. package/dist/Components/Inputs/TextBoxTypes.js +17 -0
  92. package/dist/Components/Inputs/TreeView.d.ts +76 -2
  93. package/dist/Components/Inputs/TreeView.js +227 -16
  94. package/dist/Components/Layouts/MessageBox.d.ts +15 -2
  95. package/dist/Components/Layouts/MessageBox.js +56 -7
  96. package/dist/Components/Layouts/Popup.d.ts +34 -7
  97. package/dist/Components/Layouts/Popup.js +143 -13
  98. package/dist/Components/Layouts/PopupDock.d.ts +3 -1
  99. package/dist/Components/Layouts/PopupDock.js +2 -1
  100. package/dist/Components/Layouts/PopupHost.js +5 -4
  101. package/dist/Components/Messages/MailClient.d.ts +50 -0
  102. package/dist/Components/Messages/MailClient.js +410 -0
  103. package/dist/Components/Messages/MailColumns.d.ts +21 -0
  104. package/dist/Components/Messages/MailColumns.js +92 -0
  105. package/dist/Components/Messages/MailCompose.d.ts +46 -0
  106. package/dist/Components/Messages/MailCompose.js +154 -0
  107. package/dist/Components/Messages/MailReader.d.ts +43 -0
  108. package/dist/Components/Messages/MailReader.js +207 -0
  109. package/dist/Components/Messages/MailThread.d.ts +71 -0
  110. package/dist/Components/Messages/MailThread.js +185 -0
  111. package/dist/Components/Messages/MessageBell.d.ts +41 -0
  112. package/dist/Components/Messages/MessageBell.js +76 -0
  113. package/dist/Components/Viewers/DataTable.js +153 -49
  114. package/dist/Components/Viewers/DataTableColumns.d.ts +2 -2
  115. package/dist/Components/Viewers/DataTableColumns.js +2 -1
  116. package/dist/Components/Viewers/DataTableFilters.d.ts +1 -1
  117. package/dist/Components/Viewers/DataTableFilters.js +17 -2
  118. package/dist/Components/Viewers/DataTableParts.d.ts +23 -5
  119. package/dist/Components/Viewers/DataTableParts.js +26 -14
  120. package/dist/Components/Viewers/OwnerFiles.d.ts +227 -0
  121. package/dist/Components/Viewers/OwnerFiles.js +700 -0
  122. package/dist/Components/Viewers/QrCodeImage.d.ts +22 -0
  123. package/dist/Components/Viewers/QrCodeImage.js +15 -0
  124. package/dist/Components/Viewers/RecordCards.d.ts +16 -0
  125. package/dist/Components/Viewers/RecordCards.js +509 -0
  126. package/dist/Helpers/ApiModules.js +4 -0
  127. package/dist/Helpers/AuditEntries.d.ts +50 -0
  128. package/dist/Helpers/AuditEntries.js +96 -0
  129. package/dist/Helpers/BusyHooks.d.ts +6 -0
  130. package/dist/Helpers/BusyHooks.js +39 -0
  131. package/dist/Helpers/ChatBackend.d.ts +8 -1
  132. package/dist/Helpers/ChatBackend.js +18 -0
  133. package/dist/Helpers/ChatHelpers.d.ts +2 -0
  134. package/dist/Helpers/ChatHelpers.js +4 -1
  135. package/dist/Helpers/CrudBackend.d.ts +20 -2
  136. package/dist/Helpers/CrudBackend.js +46 -2
  137. package/dist/Helpers/CrudColumns.d.ts +91 -6
  138. package/dist/Helpers/CrudColumns.js +148 -7
  139. package/dist/Helpers/CrudDetailsTabs.d.ts +58 -0
  140. package/dist/Helpers/CrudDetailsTabs.js +126 -0
  141. package/dist/Helpers/CrudHelpers.d.ts +116 -2
  142. package/dist/Helpers/CrudHelpers.js +256 -0
  143. package/dist/Helpers/CrudNarrowPick.d.ts +83 -0
  144. package/dist/Helpers/CrudNarrowPick.js +157 -0
  145. package/dist/Helpers/CrudPickerPlan.d.ts +58 -0
  146. package/dist/Helpers/CrudPickerPlan.js +46 -0
  147. package/dist/Helpers/CrudRowForms.d.ts +32 -0
  148. package/dist/Helpers/CrudRowForms.js +76 -0
  149. package/dist/Helpers/CrudScopes.d.ts +32 -0
  150. package/dist/Helpers/CrudScopes.js +79 -0
  151. package/dist/Helpers/DashboardHelpers.d.ts +115 -0
  152. package/dist/Helpers/DashboardHelpers.js +305 -0
  153. package/dist/Helpers/DomainCards.d.ts +140 -0
  154. package/dist/Helpers/DomainCards.js +370 -0
  155. package/dist/Helpers/DropdownHelpers.d.ts +3 -1
  156. package/dist/Helpers/DropdownHelpers.js +6 -2
  157. package/dist/Helpers/ExcelHelpers.d.ts +1 -1
  158. package/dist/Helpers/ExcelHelpers.js +12 -11
  159. package/dist/Helpers/FileAdapter.d.ts +158 -0
  160. package/dist/Helpers/FileAdapter.js +173 -0
  161. package/dist/Helpers/FilePrimaries.d.ts +48 -0
  162. package/dist/Helpers/FilePrimaries.js +138 -0
  163. package/dist/Helpers/FormConfirm.d.ts +32 -0
  164. package/dist/Helpers/FormConfirm.js +40 -0
  165. package/dist/Helpers/FormStore.d.ts +100 -5
  166. package/dist/Helpers/FormStore.js +368 -25
  167. package/dist/Helpers/InputConditions.d.ts +56 -0
  168. package/dist/Helpers/InputConditions.js +192 -0
  169. package/dist/Helpers/InputMemory.d.ts +106 -0
  170. package/dist/Helpers/InputMemory.js +395 -0
  171. package/dist/Helpers/InputNarrowing.d.ts +50 -0
  172. package/dist/Helpers/InputNarrowing.js +148 -0
  173. package/dist/Helpers/InputParamsHelpers.d.ts +44 -1
  174. package/dist/Helpers/InputParamsHelpers.js +157 -74
  175. package/dist/Helpers/MailClientHelpers.d.ts +96 -0
  176. package/dist/Helpers/MailClientHelpers.js +211 -0
  177. package/dist/Helpers/MessageBackend.d.ts +152 -0
  178. package/dist/Helpers/MessageBackend.js +329 -0
  179. package/dist/Helpers/MessageHelpers.d.ts +160 -0
  180. package/dist/Helpers/MessageHelpers.js +573 -0
  181. package/dist/Helpers/MessageUnread.d.ts +95 -0
  182. package/dist/Helpers/MessageUnread.js +245 -0
  183. package/dist/Helpers/NexusDriveAdapter.d.ts +71 -0
  184. package/dist/Helpers/NexusDriveAdapter.js +236 -0
  185. package/dist/Helpers/NumberHelpers.d.ts +156 -0
  186. package/dist/Helpers/NumberHelpers.js +242 -0
  187. package/dist/Helpers/PagedPicker.d.ts +42 -0
  188. package/dist/Helpers/PagedPicker.js +85 -0
  189. package/dist/Helpers/Permissions.js +2 -1
  190. package/dist/Helpers/QrCode.d.ts +26 -0
  191. package/dist/Helpers/QrCode.js +463 -0
  192. package/dist/Helpers/RecordCards.d.ts +205 -0
  193. package/dist/Helpers/RecordCards.js +378 -0
  194. package/dist/Helpers/RequestCache.d.ts +14 -2
  195. package/dist/Helpers/RequestCache.js +22 -3
  196. package/dist/Helpers/RowsStore.d.ts +61 -2
  197. package/dist/Helpers/RowsStore.js +203 -16
  198. package/dist/Helpers/ServerPreview.d.ts +69 -0
  199. package/dist/Helpers/ServerPreview.js +99 -0
  200. package/dist/Helpers/TableColumns.js +4 -3
  201. package/dist/Helpers/TableExport.d.ts +7 -1
  202. package/dist/Helpers/TableExport.js +9 -3
  203. package/dist/Helpers/TableFilters.d.ts +1 -1
  204. package/dist/Helpers/TableFilters.js +24 -23
  205. package/dist/Helpers/TableHelpers.d.ts +38 -4
  206. package/dist/Helpers/TableHelpers.js +78 -10
  207. package/dist/Helpers/TableJoins.d.ts +13 -0
  208. package/dist/Helpers/TableJoins.js +34 -0
  209. package/dist/Helpers/TableStore.d.ts +28 -4
  210. package/dist/Helpers/TableStore.js +73 -11
  211. package/dist/Helpers/ToolbarFit.d.ts +23 -0
  212. package/dist/Helpers/ToolbarFit.js +30 -0
  213. package/dist/Helpers/TreeStore.d.ts +156 -6
  214. package/dist/Helpers/TreeStore.js +547 -18
  215. package/dist/Interfaces/ApiInterfaces.d.ts +10 -1
  216. package/dist/Interfaces/ApiInterfaces.js +10 -1
  217. package/dist/Interfaces/ChatInterfaces.d.ts +22 -0
  218. package/dist/Interfaces/CrudInterfaces.d.ts +539 -6
  219. package/dist/Interfaces/DashboardInterfaces.d.ts +123 -0
  220. package/dist/Interfaces/DashboardInterfaces.js +1 -0
  221. package/dist/Interfaces/DomainInterfaces.d.ts +145 -0
  222. package/dist/Interfaces/DomainInterfaces.js +1 -0
  223. package/dist/Interfaces/FileInterfaces.d.ts +183 -0
  224. package/dist/Interfaces/FileInterfaces.js +6 -0
  225. package/dist/Interfaces/FormInterfaces.d.ts +460 -32
  226. package/dist/Interfaces/FormInterfaces.js +79 -1
  227. package/dist/Interfaces/InputInterfaces.d.ts +22 -3
  228. package/dist/Interfaces/MailInterfaces.d.ts +192 -0
  229. package/dist/Interfaces/MailInterfaces.js +1 -0
  230. package/dist/Interfaces/RecordInterfaces.d.ts +367 -0
  231. package/dist/Interfaces/RecordInterfaces.js +1 -0
  232. package/dist/Interfaces/TableInterfaces.d.ts +102 -3
  233. package/dist/Services/ExcelApi.js +5 -0
  234. package/dist/Services/FileApi.d.ts +13 -0
  235. package/dist/Services/FileApi.js +29 -0
  236. package/dist/Services/ImageEditorLoader.d.ts +14 -0
  237. package/dist/Services/ImageEditorLoader.js +51 -0
  238. package/dist/Services/JoinSources.d.ts +79 -6
  239. package/dist/Services/JoinSources.js +132 -21
  240. package/dist/Services/MessageApi.d.ts +18 -0
  241. package/dist/Services/MessageApi.js +64 -0
  242. package/dist/Services/ModuleImport.d.ts +7 -0
  243. package/dist/Services/ModuleImport.js +22 -0
  244. package/dist/Shared.Index.d.ts +28 -0
  245. package/dist/Shared.Index.js +28 -0
  246. package/package.json +13 -2
  247. package/src/Styles/Nexus.Avatar.css +6 -5
  248. package/src/Styles/Nexus.Button.css +39 -4
  249. package/src/Styles/Nexus.Chat.css +114 -22
  250. package/src/Styles/Nexus.Choice.css +16 -0
  251. package/src/Styles/Nexus.Crud.css +486 -21
  252. package/src/Styles/Nexus.Dashboard.css +763 -0
  253. package/src/Styles/Nexus.Domain.css +384 -0
  254. package/src/Styles/Nexus.Dropdown.css +8 -0
  255. package/src/Styles/Nexus.Editor.css +20 -0
  256. package/src/Styles/Nexus.Files.css +437 -0
  257. package/src/Styles/Nexus.Form.css +5 -2
  258. package/src/Styles/Nexus.Index.css +7 -0
  259. package/src/Styles/Nexus.Input.css +73 -0
  260. package/src/Styles/Nexus.Mail.css +1062 -0
  261. package/src/Styles/Nexus.Menu.css +10 -1
  262. package/src/Styles/Nexus.Popup.css +135 -1
  263. package/src/Styles/Nexus.Print.css +288 -0
  264. package/src/Styles/Nexus.QrCode.css +12 -0
  265. package/src/Styles/Nexus.Record.css +430 -0
  266. package/src/Styles/Nexus.Tab.Buttons.css +87 -22
  267. package/src/Styles/Nexus.Table.css +1458 -1387
  268. package/src/Styles/Nexus.Theme.Picker.css +1 -0
  269. package/src/Styles/Nexus.Themes.css +12 -6
  270. package/src/Styles/Nexus.Tokens.css +8 -3
  271. package/src/Styles/Nexus.Tree.css +41 -1
@@ -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
+ }
@@ -35,6 +35,12 @@ interface TabButtonsBaseProps<T extends string> {
35
35
  labels?: TabButtonLabels;
36
36
  /** Fills the width of its container. With `labels="all"`, options share the width equally. A `list` always fills it. */
37
37
  block?: boolean;
38
+ /**
39
+ * The options wrap onto more rows when they do not fit, as the choice tabs of `RadioGroup design="tabs"` and
40
+ * `multiple` do: each option as wide as its label, the chosen one raised on its own rather than by a sliding
41
+ * indicator. For a row of filters that must fit a phone. `nav` design only.
42
+ */
43
+ wrap?: boolean;
38
44
  /** Radio (or checkbox) group name, for forms. Default: a unique id. */
39
45
  name?: string;
40
46
  /**
@@ -12,14 +12,15 @@ import { cx } from "../../Helpers/ClassHelpers.js";
12
12
  * Space toggles one), every chosen option raised as the single choice's indicator is, and the options wrap.
13
13
  */
14
14
  export function TabButtons(props) {
15
- const { options, design = "nav", size = "md", tone = "neutral", labels = "all", block = false, name, form, className, multiple: _multiple, value: _value, onChange: _onChange, ...labelling } = props;
15
+ const { options, design = "nav", size = "md", tone = "neutral", labels = "all", block = false, wrap = false, name, form, className, multiple: _multiple, value: _value, onChange: _onChange, ...labelling } = props;
16
16
  const generatedName = useId();
17
17
  const groupName = name ?? generatedName;
18
18
  const list = design === "list";
19
19
  const labelsSelected = !list && labels !== "all";
20
20
  const chosen = props.multiple ? new Set(props.value) : null;
21
- // One choice slides one indicator; several have no single place for it, so each chosen option draws its own.
22
- const index = chosen ? -1 : options.findIndex(option => option.value === props.value);
21
+ const wraps = wrap && !list && !chosen;
22
+ // One choice slides one indicator; several, or options that wrap, have no single place for it: each chosen option draws its own.
23
+ const index = chosen || wraps ? -1 : options.findIndex(option => option.value === props.value);
23
24
  const style = { "--nx-tabs-count": Math.max(options.length, 1), "--nx-tabs-index": Math.max(index, 0) };
24
25
  const isChecked = (option) => (chosen ? chosen.has(option.value) : option.value === props.value);
25
26
  const toggle = (option, checked) => {
@@ -34,5 +35,5 @@ export function TabButtons(props) {
34
35
  next.delete(option.value);
35
36
  props.onChange(options.filter(one => next.has(one.value)).map(one => one.value));
36
37
  };
37
- return (_jsxs("div", { role: chosen ? "group" : "radiogroup", "aria-orientation": list && !chosen ? "vertical" : undefined, ...labelling, className: cx("nx-tabs", `nx-tabs--${size}`, list && "nx-tabs--list", tone === "primary" && "nx-tabs--primary", labelsSelected && "nx-tabs--labels-selected", !list && labels === "auto" && "nx-tabs--labels-auto", chosen && !list && "nx-tabs--multiple", block && "nx-tabs--block", className), style: list || chosen ? undefined : style, children: [index >= 0 && !list && !labelsSelected && _jsx("span", { className: "nx-tabs__indicator", "aria-hidden": "true" }), options.map(option => (_jsxs("label", { className: "nx-tabs__option", title: option.title ?? (labelsSelected && typeof option.label === "string" ? option.label : undefined), children: [_jsx("input", { type: chosen ? "checkbox" : "radio", className: "nx-visually-hidden", name: groupName, form: form, value: option.value, checked: isChecked(option), disabled: option.disabled, onChange: event => toggle(option, event.currentTarget.checked) }), option.icon, _jsxs("span", { className: "nx-tabs__label", children: [_jsx("span", { className: "nx-tabs__text", children: option.label }), list && option.description && _jsx("span", { className: "nx-tabs__description", children: option.description })] }), list && option.badge !== undefined && option.badge !== null && _jsx("span", { className: "nx-tabs__badge", children: option.badge })] }, option.value)))] }));
38
+ return (_jsxs("div", { role: chosen ? "group" : "radiogroup", "aria-orientation": list && !chosen ? "vertical" : undefined, ...labelling, className: cx("nx-tabs", `nx-tabs--${size}`, list && "nx-tabs--list", tone === "primary" && "nx-tabs--primary", labelsSelected && "nx-tabs--labels-selected", !list && labels === "auto" && "nx-tabs--labels-auto", chosen && !list && "nx-tabs--multiple", wraps && "nx-tabs--wrap", block && "nx-tabs--block", className), style: list || chosen || wraps ? undefined : style, children: [index >= 0 && !list && !labelsSelected && _jsx("span", { className: "nx-tabs__indicator", "aria-hidden": "true" }), options.map(option => (_jsxs("label", { className: "nx-tabs__option", title: option.title ?? (labelsSelected && typeof option.label === "string" ? option.label : undefined), children: [_jsx("input", { type: chosen ? "checkbox" : "radio", className: "nx-visually-hidden", name: groupName, form: form, value: option.value, checked: isChecked(option), disabled: option.disabled, onChange: event => toggle(option, event.currentTarget.checked) }), option.icon, _jsxs("span", { className: "nx-tabs__label", children: [_jsx("span", { className: "nx-tabs__text", children: option.label }), list && option.description && _jsx("span", { className: "nx-tabs__description", children: option.description })] }), list && option.badge !== undefined && option.badge !== null && _jsx("span", { className: "nx-tabs__badge", children: option.badge })] }, option.value)))] }));
38
39
  }
@@ -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;
@@ -0,0 +1,70 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { cx } from "../../Helpers/ClassHelpers.js";
5
+ /** The ids a tab and its panel are joined by. */
6
+ export function tabIds(id, value) {
7
+ const safe = value.replace(/[^A-Za-z0-9_-]/g, "_");
8
+ return { tab: `${id}-tab-${safe}`, panel: `${id}-panel-${safe}` };
9
+ }
10
+ /** Whether the tabs are laid out in a row now: the `nav` track, or `auto`'s strip in a narrow container. */
11
+ function laidInRow(list) {
12
+ const style = getComputedStyle(list);
13
+ return style.display.includes("flex") ? style.flexDirection.startsWith("row") : style.gridAutoFlow.startsWith("column");
14
+ }
15
+ /**
16
+ * Tabs: one button per panel, the chosen one marked `aria-selected`. Only the chosen tab is in the Tab order; the
17
+ * arrow keys move to the next or previous tab and open it (Up and Down, Left and Right, whichever way they lie), Home
18
+ * and End to the first and the last.
19
+ */
20
+ export function Tabs({ options, value, onChange, id, design = "list", size = "md", tone = "neutral", className, ...labelling }) {
21
+ const listRef = useRef(null);
22
+ // Which way the tabs lie, for screen readers: `auto` lies in a column or a row with its container.
23
+ const [vertical, setVertical] = useState(design !== "nav");
24
+ useEffect(() => {
25
+ const list = listRef.current;
26
+ if (!list || design !== "auto" || typeof ResizeObserver === "undefined")
27
+ return;
28
+ const observer = new ResizeObserver(() => setVertical(!laidInRow(list)));
29
+ observer.observe(list);
30
+ return () => observer.disconnect();
31
+ }, [design]);
32
+ const enabled = options.filter(option => !option.disabled);
33
+ const chosen = options.some(option => option.value === value && !option.disabled) ? value : enabled[0]?.value;
34
+ function move(event) {
35
+ const at = enabled.findIndex(option => option.value === chosen);
36
+ let next = -1;
37
+ if (event.key === "ArrowDown" || event.key === "ArrowRight")
38
+ next = (at + 1) % enabled.length;
39
+ else if (event.key === "ArrowUp" || event.key === "ArrowLeft")
40
+ next = (at - 1 + enabled.length) % enabled.length;
41
+ else if (event.key === "Home")
42
+ next = 0;
43
+ else if (event.key === "End")
44
+ next = enabled.length - 1;
45
+ const target = enabled[next]?.value;
46
+ if (target === undefined)
47
+ return;
48
+ event.preventDefault();
49
+ if (target !== chosen)
50
+ onChange(target);
51
+ listRef.current?.querySelector(`#${CSS.escape(tabIds(id, target).tab)}`)?.focus();
52
+ }
53
+ const list = design !== "nav";
54
+ return (_jsxs("div", { ref: listRef, role: "tablist", "aria-orientation": vertical ? "vertical" : "horizontal", ...labelling, className: cx("nx-tabs", `nx-tabs--${size}`, list && "nx-tabs--list", design === "auto" && "nx-tabs--auto", tone === "primary" && "nx-tabs--primary", className), style: list ? undefined : { "--nx-tabs-count": Math.max(options.length, 1), "--nx-tabs-index": Math.max(options.findIndex(option => option.value === chosen), 0) }, onKeyDown: move, children: [!list && _jsx("span", { className: "nx-tabs__indicator", "aria-hidden": "true" }), options.map(option => {
55
+ const ids = tabIds(id, option.value);
56
+ const selected = option.value === chosen;
57
+ return (_jsxs("button", { type: "button", role: "tab", id: ids.tab, "aria-selected": selected, "aria-controls": selected ? ids.panel : undefined, tabIndex: selected ? 0 : -1, disabled: option.disabled, className: "nx-tabs__option", title: option.title, onClick: () => {
58
+ if (!selected)
59
+ onChange(option.value);
60
+ }, children: [option.icon, _jsxs("span", { className: "nx-tabs__label", children: [_jsx("span", { className: "nx-tabs__text", children: option.label }), list && option.description && _jsx("span", { className: "nx-tabs__description", children: option.description })] }), list && option.badge !== undefined && option.badge !== null && option.badge !== false && _jsx("span", { className: "nx-tabs__badge", children: option.badge })] }, option.value));
61
+ })] }));
62
+ }
63
+ /**
64
+ * The chosen tab's panel, joined to its tab. It takes the focus on Tab from the tabs (`tabIndex` 0), so a panel with
65
+ * nothing to focus is still reached and read.
66
+ */
67
+ export function TabPanel({ tabs, value, className, children }) {
68
+ const ids = tabIds(tabs, value);
69
+ return (_jsx("div", { role: "tabpanel", id: ids.panel, "aria-labelledby": ids.tab, tabIndex: 0, className: cx("nx-tab-panel", className), children: children }));
70
+ }
@@ -0,0 +1,98 @@
1
+ import { type ReactNode, type Ref } from "react";
2
+ import { DomainCardRun } from "../../Helpers/DomainCards.ts";
3
+ import type { ChatDomainValue, ChatId } from "../../Interfaces/ChatInterfaces.ts";
4
+ import type { DomainCardContext, DomainCardDefinition, DomainCardParams, DomainCardSnapshot } from "../../Interfaces/DomainInterfaces.ts";
5
+ /** What a card's `ref` can do. */
6
+ export interface DomainCardHandle {
7
+ /** Reads the record again: it changed. */
8
+ reload(): void;
9
+ }
10
+ export interface DomainCardProps {
11
+ /** The record's kind: the app's own domain value, as `defineChatDomains` names it. */
12
+ domain: ChatDomainValue;
13
+ /** The record's id as the reader may open it. Without one the card shows the kind and the words alone. */
14
+ id: ChatId | null | undefined;
15
+ /** The record's words as the message holds them: shown while it loads, and when it cannot be opened. */
16
+ label?: string | null;
17
+ /** Grows when the record may have changed (a mail thread's notification): the card reads it again. */
18
+ revision?: number;
19
+ /** Who reads: answers are kept per reader, and `load` is told. */
20
+ viewer?: ChatId | null;
21
+ /** The card to draw with, instead of the kind's registered one; `null` for none. */
22
+ card?: DomainCardDefinition<any> | null;
23
+ /** The kind's name, instead of the registered one: "Leave request". */
24
+ kind?: string;
25
+ /** The title's heading level. Default 3. */
26
+ headingLevel?: 2 | 3 | 4 | 5 | 6;
27
+ /** A button that folds the card to its title and figures, its details a press away. Default true. */
28
+ collapsible?: boolean;
29
+ /** Opens folded, when the page does not hold the fold. */
30
+ defaultCollapsed?: boolean;
31
+ /** Folded, when the page holds it (a mail thread folds it by its width); pair it with `onCollapsedChange`. */
32
+ collapsed?: boolean;
33
+ /** The reader folded or unfolded it. */
34
+ onCollapsedChange?: (collapsed: boolean) => void;
35
+ className?: string;
36
+ ref?: Ref<DomainCardHandle>;
37
+ }
38
+ /** A record's card, read live from its module: see the notes at the top of this file. */
39
+ export declare function DomainCard({ card: given, kind: givenKind, ...props }: DomainCardProps): import("react").JSX.Element;
40
+ /** One record's read, as `DomainCard` draws it: the run, its snapshot, and the card's params. */
41
+ export interface DomainCardState<TData = unknown> {
42
+ run: DomainCardRun<TData>;
43
+ snapshot: DomainCardSnapshot<TData>;
44
+ context: DomainCardContext;
45
+ /** What `view` made of the record, while it is on screen. */
46
+ params?: DomainCardParams;
47
+ /** `view` failed on the answer: shown as a failure. */
48
+ viewError?: unknown;
49
+ }
50
+ /** What `useDomainCard` reads. */
51
+ export interface UseDomainCardOptions<TData> {
52
+ domain: ChatDomainValue;
53
+ id: ChatId;
54
+ card: DomainCardDefinition<TData>;
55
+ viewer?: ChatId | null;
56
+ label?: string | null;
57
+ kind?: string;
58
+ /** Grows when the record may have changed: read again. */
59
+ revision?: number;
60
+ }
61
+ /** One record's card read, for a page that draws it its own way: the snapshot, and `run.reload()` / `run.retry()`. */
62
+ export declare function useDomainCard<TData>({ domain, id, card, viewer, label, kind, revision }: UseDomainCardOptions<TData>): DomainCardState<TData>;
63
+ export interface DomainCardFrameProps {
64
+ /** What the card shows. */
65
+ params: DomainCardParams;
66
+ /** The kind's name, as a tag before the title: "Leave request". */
67
+ kind?: string;
68
+ /** The title's heading level. Default 3. */
69
+ headingLevel?: 2 | 3 | 4 | 5 | 6;
70
+ /** Words or marks at the end of the title line, beside the state: "Updated 2:45 PM". */
71
+ status?: ReactNode;
72
+ /** The card is being read again. */
73
+ busy?: boolean;
74
+ /** A button that folds the card to its title line. Default false. */
75
+ collapsible?: boolean;
76
+ /** Folded, when the page holds it; otherwise the frame holds it from `defaultCollapsed`. */
77
+ collapsed?: boolean;
78
+ defaultCollapsed?: boolean;
79
+ onCollapsedChange?: (collapsed: boolean) => void;
80
+ /** More of the card, after its lines and before its link. */
81
+ children?: ReactNode;
82
+ className?: string;
83
+ "data-state"?: string;
84
+ }
85
+ /**
86
+ * A card drawn from params alone: the look every record's card shares. `DomainCard` draws it once a record is read; a
87
+ * page that already holds a record (a dashboard, a print preview) can draw it straight.
88
+ */
89
+ export declare function DomainCardFrame({ params, kind, headingLevel, status, busy, collapsible, collapsed: heldCollapsed, defaultCollapsed, onCollapsedChange, children, className, "data-state": dataState, }: DomainCardFrameProps): import("react").JSX.Element;
90
+ export interface DomainLineProps {
91
+ /** The kind's name, as a tag: "Leave request". */
92
+ kind?: string | null;
93
+ /** The record's words: "LR2082-0007 · Puja Shrestha". */
94
+ label?: string | null;
95
+ className?: string;
96
+ }
97
+ /** A record named by its kind and its words, with nothing read: a message about a kind with no card. */
98
+ export declare function DomainLine({ kind, label, className }: DomainLineProps): import("react").JSX.Element | null;