nexus-shared 1.1.20 → 2.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 (332) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +903 -0
  3. package/dist/Client.Index.d.ts +44 -0
  4. package/dist/Client.Index.js +49 -0
  5. package/dist/Components/Documents/Button.d.ts +29 -0
  6. package/dist/Components/Documents/Button.js +11 -0
  7. package/dist/Components/Documents/Menu.d.ts +102 -0
  8. package/dist/Components/Documents/Menu.js +238 -0
  9. package/dist/Components/Documents/SplitButton.d.ts +64 -0
  10. package/dist/Components/Documents/SplitButton.js +127 -0
  11. package/dist/Components/Documents/TabButtons.d.ts +62 -0
  12. package/dist/Components/Documents/TabButtons.js +19 -0
  13. package/dist/Components/Forms/ApiForm.d.ts +21 -0
  14. package/dist/Components/Forms/ApiForm.js +99 -0
  15. package/dist/Components/Forms/Crud.d.ts +17 -0
  16. package/dist/Components/Forms/Crud.js +556 -0
  17. package/dist/Components/Forms/CrudParts.d.ts +15 -0
  18. package/dist/Components/Forms/CrudParts.js +36 -0
  19. package/dist/Components/Forms/Form.d.ts +52 -0
  20. package/dist/Components/Forms/Form.js +319 -0
  21. package/dist/Components/Forms/SubmitForm.d.ts +65 -0
  22. package/dist/Components/Forms/SubmitForm.js +198 -0
  23. package/dist/Components/Inputs/Calendar.d.ts +69 -0
  24. package/dist/Components/Inputs/Calendar.js +258 -0
  25. package/dist/Components/Inputs/CheckBox.d.ts +53 -0
  26. package/dist/Components/Inputs/CheckBox.js +87 -0
  27. package/dist/Components/Inputs/CheckBoxGroup.d.ts +20 -0
  28. package/dist/Components/Inputs/CheckBoxGroup.js +80 -0
  29. package/dist/Components/Inputs/ChoiceField.d.ts +122 -0
  30. package/dist/Components/Inputs/ChoiceField.js +169 -0
  31. package/dist/Components/Inputs/DatePicker.d.ts +54 -0
  32. package/dist/Components/Inputs/DatePicker.js +246 -0
  33. package/dist/Components/Inputs/DateRangePicker.d.ts +62 -0
  34. package/dist/Components/Inputs/DateRangePicker.js +283 -0
  35. package/dist/Components/Inputs/DateTimePicker.d.ts +75 -0
  36. package/dist/Components/Inputs/DateTimePicker.js +399 -0
  37. package/dist/Components/Inputs/Dropdown.d.ts +117 -0
  38. package/dist/Components/Inputs/Dropdown.js +839 -0
  39. package/dist/Components/Inputs/DropdownParts.d.ts +84 -0
  40. package/dist/Components/Inputs/DropdownParts.js +102 -0
  41. package/dist/Components/Inputs/EditorShell.d.ts +45 -0
  42. package/dist/Components/Inputs/EditorShell.js +214 -0
  43. package/dist/Components/Inputs/Field.d.ts +53 -0
  44. package/dist/Components/Inputs/Field.js +16 -0
  45. package/dist/Components/Inputs/GroupForm.d.ts +20 -0
  46. package/dist/Components/Inputs/GroupForm.js +170 -0
  47. package/dist/Components/Inputs/InputField.d.ts +59 -0
  48. package/dist/Components/Inputs/InputField.js +169 -0
  49. package/dist/Components/Inputs/InputRenderer.d.ts +35 -0
  50. package/dist/Components/Inputs/InputRenderer.js +99 -0
  51. package/dist/Components/Inputs/MarkdownEditor.d.ts +67 -0
  52. package/dist/Components/Inputs/MarkdownEditor.js +289 -0
  53. package/dist/Components/Inputs/NumberBox.d.ts +53 -0
  54. package/dist/Components/Inputs/NumberBox.js +228 -0
  55. package/dist/Components/Inputs/RadioGroup.d.ts +20 -0
  56. package/dist/Components/Inputs/RadioGroup.js +43 -0
  57. package/dist/Components/Inputs/ReadOnlyNotice.d.ts +36 -0
  58. package/dist/Components/Inputs/ReadOnlyNotice.js +116 -0
  59. package/dist/Components/Inputs/RichTextEditor.d.ts +103 -0
  60. package/dist/Components/Inputs/RichTextEditor.js +1170 -0
  61. package/dist/Components/Inputs/RichTextParts.d.ts +63 -0
  62. package/dist/Components/Inputs/RichTextParts.js +157 -0
  63. package/dist/Components/Inputs/RowsInput.d.ts +104 -0
  64. package/dist/Components/Inputs/RowsInput.js +259 -0
  65. package/dist/Components/Inputs/Slider.d.ts +87 -0
  66. package/dist/Components/Inputs/Slider.js +216 -0
  67. package/dist/Components/Inputs/TabularForm.d.ts +23 -0
  68. package/dist/Components/Inputs/TabularForm.js +283 -0
  69. package/dist/Components/Inputs/TextArea.d.ts +43 -0
  70. package/dist/Components/Inputs/TextArea.js +31 -0
  71. package/dist/Components/Inputs/TextBox.d.ts +52 -0
  72. package/dist/Components/Inputs/TextBox.js +58 -0
  73. package/dist/Components/Inputs/TextField.d.ts +31 -0
  74. package/dist/Components/Inputs/TextField.js +35 -0
  75. package/dist/Components/Inputs/TimePanel.d.ts +34 -0
  76. package/dist/Components/Inputs/TimePanel.js +288 -0
  77. package/dist/Components/Inputs/TimePicker.d.ts +54 -0
  78. package/dist/Components/Inputs/TimePicker.js +239 -0
  79. package/dist/Components/Inputs/TreeView.d.ts +160 -0
  80. package/dist/Components/Inputs/TreeView.js +947 -0
  81. package/dist/Components/Layouts/MessageBox.d.ts +71 -0
  82. package/dist/Components/Layouts/MessageBox.js +104 -0
  83. package/dist/Components/Layouts/Popup.d.ts +92 -0
  84. package/dist/Components/Layouts/Popup.js +376 -0
  85. package/dist/Components/Layouts/PopupContext.d.ts +2 -0
  86. package/dist/Components/Layouts/PopupContext.js +4 -0
  87. package/dist/Components/Layouts/PopupDock.d.ts +22 -0
  88. package/dist/Components/Layouts/PopupDock.js +121 -0
  89. package/dist/Components/Layouts/PopupHost.d.ts +56 -0
  90. package/dist/Components/Layouts/PopupHost.js +163 -0
  91. package/dist/Components/Layouts/PopupRules.d.ts +30 -0
  92. package/dist/Components/Layouts/PopupRules.js +63 -0
  93. package/dist/Components/Layouts/ThemePicker.d.ts +13 -0
  94. package/dist/Components/Layouts/ThemePicker.js +22 -0
  95. package/dist/Components/Layouts/ThemeScript.d.ts +14 -0
  96. package/dist/Components/Layouts/ThemeScript.js +13 -0
  97. package/dist/Components/Layouts/ThemeSwitcher.d.ts +13 -0
  98. package/dist/Components/Layouts/ThemeSwitcher.js +29 -0
  99. package/dist/Components/Layouts/Toaster.d.ts +76 -0
  100. package/dist/Components/Layouts/Toaster.js +172 -0
  101. package/dist/Components/Viewers/DataTable.d.ts +25 -0
  102. package/dist/Components/Viewers/DataTable.js +542 -0
  103. package/dist/Components/Viewers/DataTableParts.d.ts +134 -0
  104. package/dist/Components/Viewers/DataTableParts.js +145 -0
  105. package/dist/Components/Viewers/MarkdownView.d.ts +26 -0
  106. package/dist/Components/Viewers/MarkdownView.js +79 -0
  107. package/dist/Components/Viewers/RichTextView.d.ts +18 -0
  108. package/dist/Components/Viewers/RichTextView.js +17 -0
  109. package/dist/Helpers/AnimationHelpers.d.ts +6 -0
  110. package/dist/Helpers/AnimationHelpers.js +35 -0
  111. package/dist/Helpers/ApiClient.d.ts +74 -0
  112. package/dist/Helpers/ApiClient.js +197 -0
  113. package/dist/Helpers/ApiFormHelpers.d.ts +26 -0
  114. package/dist/Helpers/ApiFormHelpers.js +56 -0
  115. package/dist/Helpers/ApiModules.d.ts +135 -0
  116. package/dist/Helpers/ApiModules.js +194 -0
  117. package/dist/Helpers/ApiResponses.d.ts +41 -0
  118. package/dist/Helpers/ApiResponses.js +159 -0
  119. package/dist/Helpers/ApiRoutes.d.ts +150 -0
  120. package/dist/Helpers/ApiRoutes.js +191 -0
  121. package/dist/Helpers/ApiToasts.d.ts +21 -0
  122. package/dist/Helpers/ApiToasts.js +22 -0
  123. package/dist/Helpers/ClassHelpers.d.ts +3 -0
  124. package/dist/Helpers/ClassHelpers.js +4 -0
  125. package/dist/Helpers/CrudBackend.d.ts +56 -0
  126. package/dist/Helpers/CrudBackend.js +119 -0
  127. package/dist/Helpers/CrudHelpers.d.ts +76 -0
  128. package/dist/Helpers/CrudHelpers.js +202 -0
  129. package/dist/Helpers/DateHelpers.d.ts +88 -0
  130. package/dist/Helpers/DateHelpers.js +363 -0
  131. package/dist/Helpers/DatePreferences.d.ts +14 -0
  132. package/dist/Helpers/DatePreferences.js +94 -0
  133. package/dist/Helpers/DateTimeHelpers.d.ts +87 -0
  134. package/dist/Helpers/DateTimeHelpers.js +286 -0
  135. package/dist/Helpers/DragHelpers.d.ts +129 -0
  136. package/dist/Helpers/DragHelpers.js +281 -0
  137. package/dist/Helpers/DropdownHelpers.d.ts +56 -0
  138. package/dist/Helpers/DropdownHelpers.js +180 -0
  139. package/dist/Helpers/FormDrafts.d.ts +18 -0
  140. package/dist/Helpers/FormDrafts.js +76 -0
  141. package/dist/Helpers/FormStore.d.ts +231 -0
  142. package/dist/Helpers/FormStore.js +734 -0
  143. package/dist/Helpers/InputParamsHelpers.d.ts +79 -0
  144. package/dist/Helpers/InputParamsHelpers.js +388 -0
  145. package/dist/Helpers/MarkdownEditing.d.ts +27 -0
  146. package/dist/Helpers/MarkdownEditing.js +250 -0
  147. package/dist/Helpers/MarkdownHelpers.d.ts +82 -0
  148. package/dist/Helpers/MarkdownHelpers.js +775 -0
  149. package/dist/Helpers/MessageBuilder.d.ts +74 -0
  150. package/dist/Helpers/MessageBuilder.js +254 -0
  151. package/dist/Helpers/NepaliCalendar.d.ts +25 -0
  152. package/dist/Helpers/NepaliCalendar.js +107 -0
  153. package/dist/Helpers/NumberHelpers.d.ts +71 -0
  154. package/dist/Helpers/NumberHelpers.js +202 -0
  155. package/dist/Helpers/Permissions.d.ts +62 -0
  156. package/dist/Helpers/Permissions.js +133 -0
  157. package/dist/Helpers/PopoverHelpers.d.ts +43 -0
  158. package/dist/Helpers/PopoverHelpers.js +91 -0
  159. package/dist/Helpers/RichTextHelpers.d.ts +77 -0
  160. package/dist/Helpers/RichTextHelpers.js +1072 -0
  161. package/dist/Helpers/RowsStore.d.ts +160 -0
  162. package/dist/Helpers/RowsStore.js +584 -0
  163. package/dist/Helpers/TableColumns.d.ts +40 -0
  164. package/dist/Helpers/TableColumns.js +254 -0
  165. package/dist/Helpers/TableExport.d.ts +30 -0
  166. package/dist/Helpers/TableExport.js +97 -0
  167. package/dist/Helpers/TableHelpers.d.ts +102 -0
  168. package/dist/Helpers/TableHelpers.js +281 -0
  169. package/dist/Helpers/TableStore.d.ts +142 -0
  170. package/dist/Helpers/TableStore.js +530 -0
  171. package/dist/Helpers/ThemeHelpers.d.ts +15 -0
  172. package/dist/Helpers/ThemeHelpers.js +16 -0
  173. package/dist/Helpers/TimeHelpers.d.ts +73 -0
  174. package/dist/Helpers/TimeHelpers.js +253 -0
  175. package/dist/Helpers/TreeStore.d.ts +188 -0
  176. package/dist/Helpers/TreeStore.js +577 -0
  177. package/dist/Helpers/ValidationHelpers.d.ts +124 -0
  178. package/dist/Helpers/ValidationHelpers.js +301 -0
  179. package/dist/Interfaces/ApiInterfaces.d.ts +149 -0
  180. package/dist/Interfaces/ApiInterfaces.js +24 -0
  181. package/dist/Interfaces/CrudInterfaces.d.ts +171 -0
  182. package/{src/sso-client.tsx → dist/Interfaces/CrudInterfaces.js} +1 -1
  183. package/dist/Interfaces/DateInterfaces.d.ts +154 -0
  184. package/dist/Interfaces/DateInterfaces.js +29 -0
  185. package/dist/Interfaces/FormInterfaces.d.ts +352 -0
  186. package/dist/Interfaces/FormInterfaces.js +1 -0
  187. package/dist/Interfaces/InputInterfaces.d.ts +141 -0
  188. package/dist/Interfaces/InputInterfaces.js +1 -0
  189. package/dist/Interfaces/MessageInterfaces.d.ts +111 -0
  190. package/dist/Interfaces/MessageInterfaces.js +5 -0
  191. package/dist/Interfaces/TableInterfaces.d.ts +291 -0
  192. package/dist/Interfaces/TableInterfaces.js +1 -0
  193. package/dist/Interfaces/ThemeInterfaces.d.ts +17 -0
  194. package/dist/Interfaces/ThemeInterfaces.js +17 -0
  195. package/dist/Interfaces/TypeInterfaces.d.ts +4 -0
  196. package/dist/Interfaces/TypeInterfaces.js +1 -0
  197. package/dist/Server.Index.d.ts +2 -0
  198. package/dist/Server.Index.js +5 -0
  199. package/dist/Services/ApiProxy.d.ts +32 -0
  200. package/dist/Services/ApiProxy.js +111 -0
  201. package/dist/Services/BrowserApi.d.ts +78 -0
  202. package/dist/Services/BrowserApi.js +111 -0
  203. package/dist/Services/ServerApi.d.ts +49 -0
  204. package/dist/Services/ServerApi.js +75 -0
  205. package/dist/Services/ThemeService.d.ts +17 -0
  206. package/dist/Services/ThemeService.js +83 -0
  207. package/dist/Shared.Index.d.ts +43 -0
  208. package/dist/Shared.Index.js +47 -0
  209. package/package.json +82 -33
  210. package/src/Styles/Nexus.Base.css +70 -0
  211. package/src/Styles/Nexus.Button.css +151 -0
  212. package/src/Styles/Nexus.Calendar.css +275 -0
  213. package/src/Styles/Nexus.Choice.css +345 -0
  214. package/src/Styles/Nexus.Crud.css +84 -0
  215. package/src/Styles/Nexus.DateTime.css +81 -0
  216. package/src/Styles/Nexus.Dropdown.css +774 -0
  217. package/src/Styles/Nexus.Editor.css +297 -0
  218. package/src/Styles/Nexus.Form.css +298 -0
  219. package/src/Styles/Nexus.Index.css +34 -0
  220. package/src/Styles/Nexus.Input.css +637 -0
  221. package/src/Styles/Nexus.Markdown.css +265 -0
  222. package/src/Styles/Nexus.Menu.css +249 -0
  223. package/src/Styles/Nexus.Popup.css +731 -0
  224. package/src/Styles/Nexus.RichText.css +463 -0
  225. package/src/Styles/Nexus.Rows.css +566 -0
  226. package/src/Styles/Nexus.Slider.css +278 -0
  227. package/src/Styles/Nexus.Tab.Buttons.css +364 -0
  228. package/src/Styles/Nexus.Table.css +1014 -0
  229. package/src/Styles/Nexus.Theme.Picker.css +159 -0
  230. package/src/Styles/Nexus.Themes.css +194 -0
  231. package/src/Styles/Nexus.Time.css +147 -0
  232. package/src/Styles/Nexus.Toast.css +265 -0
  233. package/src/Styles/Nexus.Tokens.css +72 -0
  234. package/src/Styles/Nexus.Tree.css +435 -0
  235. package/src/api-services/authentication-service.tsx +0 -23
  236. package/src/api-services/preference-service.tsx +0 -5
  237. package/src/api-services/system-service.tsx +0 -34
  238. package/src/client.ts +0 -31
  239. package/src/components/documents/button.tsx +0 -142
  240. package/src/components/documents/icon-box.tsx +0 -93
  241. package/src/components/documents/page-title.tsx +0 -7
  242. package/src/components/documents/tab-button.tsx +0 -172
  243. package/src/components/documents/tag.tsx +0 -30
  244. package/src/components/index.js +0 -0
  245. package/src/components/inputs/checkbox-input.tsx +0 -66
  246. package/src/components/inputs/input-box.tsx +0 -48
  247. package/src/components/inputs/input-element.tsx +0 -65
  248. package/src/components/inputs/input-form.tsx +0 -164
  249. package/src/components/inputs/input.tsx +0 -181
  250. package/src/components/inputs/number-input.tsx +0 -108
  251. package/src/components/inputs/radiobox-input.tsx +0 -53
  252. package/src/components/inputs/textarea-input.tsx +0 -47
  253. package/src/components/inputs/textbox-input.tsx +0 -45
  254. package/src/components/layouts/global-dialogbox.tsx +0 -431
  255. package/src/components/layouts/global-layout.tsx +0 -59
  256. package/src/components/layouts/layout-helpers.tsx +0 -20
  257. package/src/components/layouts/panels/user-panel.tsx +0 -112
  258. package/src/components/layouts/utility-menu.tsx +0 -49
  259. package/src/components/panels/theme-panel.tsx +0 -46
  260. package/src/helpers/bitwise-helpers.tsx +0 -11
  261. package/src/helpers/browser-helpers.tsx +0 -156
  262. package/src/helpers/datasource-helpers.tsx +0 -99
  263. package/src/helpers/element-helpers.tsx +0 -57
  264. package/src/helpers/input-helpers.tsx +0 -74
  265. package/src/helpers/string-helpers.tsx +0 -28
  266. package/src/helpers/utility-helpers.tsx +0 -45
  267. package/src/helpers/validation-helpers.tsx +0 -302
  268. package/src/index.ts +0 -26
  269. package/src/interface.ts +0 -19
  270. package/src/interfaces/auth-token-interfaces.tsx +0 -6
  271. package/src/interfaces/browser-interfaces.tsx +0 -36
  272. package/src/interfaces/button-interfaces.tsx +0 -63
  273. package/src/interfaces/datasource-interfaces.tsx +0 -22
  274. package/src/interfaces/datatable-interfaces.tsx +0 -25
  275. package/src/interfaces/dialogbox-interfaces.tsx +0 -5
  276. package/src/interfaces/exception-interfaces.tsx +0 -99
  277. package/src/interfaces/http-interfaces.tsx +0 -129
  278. package/src/interfaces/icon-interfaces.tsx +0 -129
  279. package/src/interfaces/input-interfaces.tsx +0 -410
  280. package/src/interfaces/layout-interfaces.tsx +0 -191
  281. package/src/interfaces/menu-interfaces.tsx +0 -49
  282. package/src/interfaces/message-interfaces.tsx +0 -32
  283. package/src/interfaces/permission-interfaces.tsx +0 -9
  284. package/src/interfaces/storage-interfaces.tsx +0 -5
  285. package/src/interfaces/system-interfaces.tsx +0 -22
  286. package/src/interfaces/theme-interfaces.tsx +0 -123
  287. package/src/interfaces/type-interfaces.tsx +0 -28
  288. package/src/interfaces/user-interfaces.tsx +0 -47
  289. package/src/nexus-client.tsx +0 -21
  290. package/src/nexus.environments.tsx +0 -66
  291. package/src/proxy-api/proxy-backend.tsx +0 -60
  292. package/src/proxy-api/proxy-constants.tsx +0 -28
  293. package/src/proxy-api/proxy-helpers.tsx +0 -91
  294. package/src/proxy-api-client.tsx +0 -1
  295. package/src/proxy-api-server.tsx +0 -2
  296. package/src/services/http-client-services.tsx +0 -75
  297. package/src/services/http-services.tsx +0 -158
  298. package/src/services/loader-service.tsx +0 -185
  299. package/src/services/localstorage-service.tsx +0 -119
  300. package/src/services/message-services.tsx +0 -383
  301. package/src/services/theme-service.tsx +0 -161
  302. package/src/services/user-services.tsx +0 -10
  303. package/src/sso-config/auth-token-validation.ts +0 -16
  304. package/src/sso-config/callback-route.tsx +0 -35
  305. package/src/sso-config/cookie-encryption.ts +0 -51
  306. package/src/sso-config/cookie-helpers.tsx +0 -102
  307. package/src/sso-config/forgot-password-route.tsx +0 -36
  308. package/src/sso-config/oauth-callback-state.ts +0 -61
  309. package/src/sso-config/pkce-helpers.ts +0 -9
  310. package/src/sso-config/provider-complete-route.tsx +0 -18
  311. package/src/sso-config/redirect-context-actions.tsx +0 -45
  312. package/src/sso-config/redirect-context-helpers.tsx +0 -159
  313. package/src/sso-config/redirect-context-persist.tsx +0 -18
  314. package/src/sso-config/redirect-context-route.tsx +0 -45
  315. package/src/sso-config/refresh-route.tsx +0 -23
  316. package/src/sso-config/sign-in-route.tsx +0 -74
  317. package/src/sso-config/sign-out-route.tsx +0 -57
  318. package/src/sso-config/sign-up-route.tsx +0 -57
  319. package/src/sso-config/sso-backend-flow.ts +0 -38
  320. package/src/sso-config/sso-complete-route.tsx +0 -50
  321. package/src/sso-config/sso-config.ts +0 -59
  322. package/src/sso-config/sso-interfaces.tsx +0 -99
  323. package/src/sso-config/sso-response-helpers.tsx +0 -20
  324. package/src/sso-server.tsx +0 -21
  325. package/src/styles/nexus.animation.css +0 -269
  326. package/src/styles/nexus.core.css +0 -119
  327. package/src/styles/nexus.dialog.css +0 -144
  328. package/src/styles/nexus.icon.css +0 -51
  329. package/src/styles/nexus.input.css +0 -207
  330. package/src/styles/nexus.loader.css +0 -11
  331. package/src/styles/nexus.logic.css +0 -47
  332. package/src/styles/nexus.utility.css +0 -347
@@ -0,0 +1,22 @@
1
+ import { type ReactNode } from "react";
2
+ /** A minimized window as the dock shows it. */
3
+ export interface DockedWindow {
4
+ id: string;
5
+ title: ReactNode;
6
+ /** Plain text of the title, for the tooltip. */
7
+ label?: string;
8
+ /** The window's icon; a window icon when left out. */
9
+ icon?: ReactNode;
10
+ /** Brings the window back. */
11
+ restore: () => void;
12
+ /** Asks the window to close; without it the bar has no close button. */
13
+ close?: () => void;
14
+ }
15
+ /** Puts a minimized window on the dock, or updates the one with its id in place. Newest last. */
16
+ export declare function dockWindow(entry: DockedWindow): void;
17
+ /** Takes a window off the dock. */
18
+ export declare function undockWindow(id: string): void;
19
+ /** Whether a dock is on the page, so a window can be minimized and still be found. */
20
+ export declare function useDockReady(): boolean;
21
+ /** The minimized popups, at the bottom right of the page. Rendered by `<PopupHost />`; render it yourself only without a host. */
22
+ export declare function PopupDock(): import("react").JSX.Element | null;
@@ -0,0 +1,121 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { iconAppWindow, iconChevronDown, iconChevronUp, iconX, InlineIcon } from "nexus-icons";
4
+ import { useEffect, useId, useLayoutEffect, useRef, useState, useSyncExternalStore } from "react";
5
+ import { cx } from "../../Helpers/ClassHelpers.js";
6
+ const EMPTY = [];
7
+ let windows = [];
8
+ let docks = 0;
9
+ const listeners = new Set();
10
+ function emit() {
11
+ for (const listener of listeners)
12
+ listener();
13
+ }
14
+ function subscribe(listener) {
15
+ listeners.add(listener);
16
+ return () => {
17
+ listeners.delete(listener);
18
+ };
19
+ }
20
+ const getWindows = () => windows;
21
+ const getServerWindows = () => EMPTY;
22
+ const getReady = () => docks > 0;
23
+ const getServerReady = () => false;
24
+ /** Puts a minimized window on the dock, or updates the one with its id in place. Newest last. */
25
+ export function dockWindow(entry) {
26
+ const index = windows.findIndex(item => item.id === entry.id);
27
+ windows = index < 0 ? [...windows, entry] : windows.map(item => (item.id === entry.id ? entry : item));
28
+ emit();
29
+ }
30
+ /** Takes a window off the dock. */
31
+ export function undockWindow(id) {
32
+ if (!windows.some(item => item.id === id))
33
+ return;
34
+ windows = windows.filter(item => item.id !== id);
35
+ emit();
36
+ }
37
+ /** Whether a dock is on the page, so a window can be minimized and still be found. */
38
+ export function useDockReady() {
39
+ return useSyncExternalStore(subscribe, getReady, getServerReady);
40
+ }
41
+ /** The minimized popups, at the bottom right of the page. Rendered by `<PopupHost />`; render it yourself only without a host. */
42
+ export function PopupDock() {
43
+ const list = useSyncExternalStore(subscribe, getWindows, getServerWindows);
44
+ const [expanded, setExpanded] = useState(false);
45
+ const rootRef = useRef(null);
46
+ const toggleRef = useRef(null);
47
+ const id = useId();
48
+ const stacked = list.length > 1;
49
+ const open = expanded && stacked;
50
+ useEffect(() => {
51
+ docks++;
52
+ emit();
53
+ return () => {
54
+ docks--;
55
+ emit();
56
+ };
57
+ }, []);
58
+ // Its height, for what else sits at the bottom right: toasts there stack above the dock (--nx-dock-space).
59
+ const hasWindows = list.length > 0;
60
+ useLayoutEffect(() => {
61
+ const root = rootRef.current;
62
+ if (!hasWindows || !root)
63
+ return;
64
+ const style = document.documentElement.style;
65
+ const observer = new ResizeObserver(() => style.setProperty("--nx-dock-space", `${Math.ceil(root.offsetHeight) + 8}px`));
66
+ observer.observe(root);
67
+ return () => {
68
+ observer.disconnect();
69
+ style.removeProperty("--nx-dock-space");
70
+ };
71
+ }, [hasWindows]);
72
+ // A list of one closes by itself, so the next stack starts closed.
73
+ useEffect(() => {
74
+ if (!stacked)
75
+ setExpanded(false);
76
+ }, [stacked]);
77
+ // An open list closes on Esc, or on a press anywhere else.
78
+ useEffect(() => {
79
+ if (!open)
80
+ return;
81
+ function onKeyDown(event) {
82
+ if (event.key !== "Escape" || event.defaultPrevented)
83
+ return;
84
+ const inside = rootRef.current?.contains(document.activeElement) ?? false;
85
+ setExpanded(false);
86
+ if (inside)
87
+ toggleRef.current?.focus();
88
+ }
89
+ function onPointerDown(event) {
90
+ if (!(event.target instanceof Node) || !rootRef.current?.contains(event.target))
91
+ setExpanded(false);
92
+ }
93
+ document.addEventListener("keydown", onKeyDown);
94
+ document.addEventListener("pointerdown", onPointerDown, true);
95
+ return () => {
96
+ document.removeEventListener("keydown", onKeyDown);
97
+ document.removeEventListener("pointerdown", onPointerDown, true);
98
+ };
99
+ }, [open]);
100
+ if (list.length === 0)
101
+ return null;
102
+ const shown = open ? list : list.slice(-1);
103
+ const depth = open ? 0 : Math.min(list.length - 1, 2);
104
+ const listId = `${id}-list`;
105
+ // The cards peeking out behind the newest bar open the list too.
106
+ function onListClick(event) {
107
+ if (event.target === event.currentTarget && depth > 0)
108
+ setExpanded(true);
109
+ }
110
+ return (_jsxs("section", { ref: rootRef, className: cx("nx-dock", stacked && "nx-dock--stacked", open && "nx-dock--open"), "aria-label": "Minimized windows", children: [stacked && (_jsxs("button", { ref: toggleRef, type: "button", className: "nx-dock__toggle", "aria-label": `${list.length} minimized windows`, "aria-expanded": open, "aria-controls": listId, title: open ? "Show only the newest" : "Show all minimized windows", onClick: () => setExpanded(!open), children: [_jsx("span", { className: "nx-dock__count", children: list.length }), _jsx("span", { children: "minimized" }), _jsx(InlineIcon, { icon: open ? iconChevronDown : iconChevronUp })] })), _jsx("ul", { id: listId, className: "nx-dock__list", "data-depth": depth || undefined, onClick: onListClick, children: shown.map(item => (_jsx(DockBar, { item: item, onRestore: () => setExpanded(false) }, item.id))) })] }));
111
+ }
112
+ // A restored window closes the list, as a taskbar's previews close after a pick.
113
+ function DockBar({ item, onRestore }) {
114
+ const id = useId();
115
+ const titleId = `${id}-title`;
116
+ const closeId = `${id}-close`;
117
+ return (_jsxs("li", { className: "nx-dock__item", children: [_jsxs("button", { type: "button", className: "nx-dock__window", title: item.label ? `Restore: ${item.label}` : "Restore", onClick: () => {
118
+ onRestore();
119
+ item.restore();
120
+ }, children: [_jsx("span", { className: "nx-dock__icon", "aria-hidden": "true", children: item.icon ?? _jsx(InlineIcon, { icon: iconAppWindow }) }), _jsx("span", { className: "nx-visually-hidden", children: "Restore " }), _jsx("span", { id: titleId, className: "nx-dock__title", children: item.title }), _jsx(InlineIcon, { className: "nx-dock__restore", icon: iconChevronUp })] }), item.close && (_jsxs("button", { type: "button", className: "nx-dock__close", "aria-labelledby": `${closeId} ${titleId}`, title: "Close", onClick: item.close, children: [_jsx("span", { id: closeId, hidden: true, children: "Close" }), _jsx(InlineIcon, { icon: iconX })] }))] }));
121
+ }
@@ -0,0 +1,56 @@
1
+ import { type ReactNode } from "react";
2
+ import { type PopupCloseReason, type PopupProps, type PopupWindowState } from "./Popup.tsx";
3
+ export interface PopupHandle {
4
+ readonly id: string;
5
+ /** Closes the popup. */
6
+ close: (reason?: PopupCloseReason) => void;
7
+ /** Changes the title, content, footer, or any other option while the popup is open. */
8
+ update: (changes: Partial<OpenPopupOptions>) => void;
9
+ /** Puts the popup aside as a bar at the bottom right; the page can be used again. */
10
+ minimize: () => void;
11
+ /** Makes the popup fill the window. */
12
+ maximize: () => void;
13
+ /** Brings a minimized popup back as it was (maximized or not), or a maximized one back to its size. */
14
+ restore: () => void;
15
+ /** The window state now: `normal`, `maximized`, or `minimized`. */
16
+ readonly windowState: PopupWindowState;
17
+ /**
18
+ * Resolves with the reason once the popup has closed: after its exit animation, when the dialog is no longer modal and
19
+ * focus is back on the page, so the code that follows can move focus or remove what the popup was about.
20
+ */
21
+ readonly closed: Promise<PopupCloseReason>;
22
+ }
23
+ export interface OpenPopupOptions extends Omit<PopupProps, "open" | "onClose" | "onClosed" | "children" | "footer" | "windowState"> {
24
+ /** The body. A function receives the handle, so buttons inside can close the popup. */
25
+ content?: ReactNode | ((popup: PopupHandle) => ReactNode);
26
+ /** The footer, right-aligned buttons. A function receives the handle. */
27
+ footer?: ReactNode | ((popup: PopupHandle) => ReactNode);
28
+ /** When the popup closes, with the reason. */
29
+ onClose?: (reason: PopupCloseReason) => void;
30
+ /**
31
+ * Asked when the user closes the popup (its close button, Esc, the backdrop, or its bar on the dock): return `false`,
32
+ * or a promise of it, to keep it open, such as after asking "Discard your changes?". Closing from code does not ask.
33
+ */
34
+ beforeClose?: (reason: PopupCloseReason) => boolean | Promise<boolean>;
35
+ /**
36
+ * One popup per key: opening a key whose popup is still open (or minimized) never opens a second one. By `reopen`,
37
+ * that popup closes (with the reason `replaced`) and a fresh one opens (`replace`, the default: `booking-new` for an
38
+ * Add form), or it comes back as it was and its handle is returned (`restore`: `booking-edit-2048`). Rules set with
39
+ * `definePopupRules` apply by key.
40
+ */
41
+ key?: string;
42
+ }
43
+ /**
44
+ * Opens a popup from code and returns its handle. Needs one `<PopupHost />` on the page. With a `key`, the rules set
45
+ * for it (`definePopupRules`) apply under the options, and a popup with the same key still open is replaced or
46
+ * restored (`reopen`).
47
+ *
48
+ * @example
49
+ * const popup = openPopup({ title: "Room 204", content: <RoomDetails id={204} />, footer: p => <Button onClick={() => p.close()}>Close</Button> });
50
+ * await popup.closed;
51
+ */
52
+ export declare function openPopup(given: OpenPopupOptions): PopupHandle;
53
+ /** Closes every popup opened from code, minimized ones too. */
54
+ export declare function closeAllPopups(): void;
55
+ /** Shows the popups opened with `openPopup`, the message boxes, and the dock of minimized popups. Put one in the root layout. */
56
+ export declare function PopupHost(): import("react").JSX.Element;
@@ -0,0 +1,163 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useLayoutEffect, useSyncExternalStore } from "react";
4
+ import { Popup } from "./Popup.js";
5
+ import { HostedPopupContext } from "./PopupContext.js";
6
+ import { PopupDock } from "./PopupDock.js";
7
+ import { withPopupRule } from "./PopupRules.js";
8
+ const EMPTY = [];
9
+ let entries = [];
10
+ let counter = 0;
11
+ let hosts = 0;
12
+ let warned = false;
13
+ const listeners = new Set();
14
+ function emit() {
15
+ for (const listener of listeners)
16
+ listener();
17
+ }
18
+ function subscribe(listener) {
19
+ listeners.add(listener);
20
+ return () => {
21
+ listeners.delete(listener);
22
+ };
23
+ }
24
+ const getSnapshot = () => entries;
25
+ const getServerSnapshot = () => EMPTY;
26
+ const findEntry = (id) => entries.find(candidate => candidate.id === id);
27
+ // The popups a host has rendered. One closed before that (closed or replaced in the same task that opened it) never
28
+ // showed, so nothing will animate it out.
29
+ const rendered = new Set();
30
+ function closeEntry(id, reason) {
31
+ const entry = findEntry(id);
32
+ if (!entry || !entry.open)
33
+ return;
34
+ entries = entries.map(candidate => (candidate.id === id ? { ...candidate, open: false, reason } : candidate));
35
+ emit();
36
+ entry.options.onClose?.(reason);
37
+ // Without a host, or before it has rendered the popup, nothing animates it out, so it is closed now.
38
+ if (hosts === 0 || !rendered.has(id))
39
+ removeEntry(id);
40
+ }
41
+ // The user asked to close: the popup's `beforeClose` may keep it open. One question at a time per popup.
42
+ const asking = new Set();
43
+ function requestClose(id, reason) {
44
+ const guard = findEntry(id)?.options.beforeClose;
45
+ // "code": the browser closed the dialog itself (a form with method="dialog"); there is nothing left to keep open.
46
+ if (!guard || reason === "code") {
47
+ closeEntry(id, reason);
48
+ return;
49
+ }
50
+ if (asking.has(id))
51
+ return;
52
+ asking.add(id);
53
+ void Promise.resolve()
54
+ .then(() => guard(reason))
55
+ .then(close => {
56
+ if (close !== false)
57
+ closeEntry(id, reason);
58
+ }, () => undefined)
59
+ .finally(() => asking.delete(id));
60
+ }
61
+ function removeEntry(id) {
62
+ const entry = findEntry(id);
63
+ rendered.delete(id);
64
+ entries = entries.filter(candidate => candidate.id !== id);
65
+ emit();
66
+ entry?.resolve(entry.reason ?? "code");
67
+ }
68
+ function setWindowState(id, state) {
69
+ const entry = findEntry(id);
70
+ if (!entry || !entry.open || entry.windowState === state)
71
+ return;
72
+ const restoreTo = entry.windowState === "minimized" ? entry.restoreTo : entry.windowState;
73
+ entries = entries.map(candidate => (candidate.id === id ? { ...candidate, windowState: state, restoreTo } : candidate));
74
+ emit();
75
+ entry.options.onWindowStateChange?.(state);
76
+ }
77
+ function restoreEntry(id) {
78
+ const entry = findEntry(id);
79
+ if (!entry)
80
+ return;
81
+ if (entry.windowState === "minimized")
82
+ setWindowState(id, entry.restoreTo);
83
+ else if (entry.windowState === "maximized")
84
+ setWindowState(id, "normal");
85
+ }
86
+ /**
87
+ * Opens a popup from code and returns its handle. Needs one `<PopupHost />` on the page. With a `key`, the rules set
88
+ * for it (`definePopupRules`) apply under the options, and a popup with the same key still open is replaced or
89
+ * restored (`reopen`).
90
+ *
91
+ * @example
92
+ * const popup = openPopup({ title: "Room 204", content: <RoomDetails id={204} />, footer: p => <Button onClick={() => p.close()}>Close</Button> });
93
+ * await popup.closed;
94
+ */
95
+ export function openPopup(given) {
96
+ const options = withPopupRule(given);
97
+ if (options.key !== undefined) {
98
+ const existing = entries.find(candidate => candidate.open && candidate.options.key === options.key);
99
+ if (existing && options.reopen === "restore") {
100
+ if (existing.windowState === "minimized")
101
+ restoreEntry(existing.id);
102
+ return existing.handle;
103
+ }
104
+ if (existing)
105
+ closeEntry(existing.id, "replaced");
106
+ }
107
+ const id = `nx-popup-${++counter}`;
108
+ let resolve = () => undefined;
109
+ const closed = new Promise(done => {
110
+ resolve = done;
111
+ });
112
+ const handle = {
113
+ id,
114
+ close: (reason = "code") => closeEntry(id, reason),
115
+ update: changes => {
116
+ entries = entries.map(candidate => (candidate.id === id ? { ...candidate, options: { ...candidate.options, ...changes } } : candidate));
117
+ emit();
118
+ },
119
+ minimize: () => setWindowState(id, "minimized"),
120
+ maximize: () => setWindowState(id, "maximized"),
121
+ restore: () => restoreEntry(id),
122
+ get windowState() {
123
+ return findEntry(id)?.windowState ?? "normal";
124
+ },
125
+ closed,
126
+ };
127
+ const initial = options.defaultWindowState ?? "normal";
128
+ entries = [...entries, { id, options, open: true, windowState: initial, restoreTo: initial, handle, resolve }];
129
+ if (hosts === 0 && !warned && typeof window !== "undefined") {
130
+ warned = true;
131
+ console.warn("openPopup(): no <PopupHost /> is mounted, so popups cannot show. Add one to the root layout.");
132
+ }
133
+ emit();
134
+ return handle;
135
+ }
136
+ /** Closes every popup opened from code, minimized ones too. */
137
+ export function closeAllPopups() {
138
+ for (const entry of entries)
139
+ closeEntry(entry.id, "code");
140
+ }
141
+ /** Shows the popups opened with `openPopup`, the message boxes, and the dock of minimized popups. Put one in the root layout. */
142
+ export function PopupHost() {
143
+ const list = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
144
+ useEffect(() => {
145
+ hosts++;
146
+ return () => {
147
+ hosts--;
148
+ // Popups that were closing when the last host went away will never finish their animation.
149
+ if (hosts === 0)
150
+ for (const entry of entries)
151
+ if (!entry.open)
152
+ removeEntry(entry.id);
153
+ };
154
+ }, []);
155
+ useLayoutEffect(() => {
156
+ for (const entry of list)
157
+ rendered.add(entry.id);
158
+ });
159
+ return (_jsxs(HostedPopupContext, { value: true, children: [list.map(entry => {
160
+ const { content, footer, onClose: _onClose, beforeClose: _beforeClose, onWindowStateChange: _onWindowStateChange, key: _key, ...rest } = entry.options;
161
+ return (_jsx(Popup, { ...rest, open: entry.open, windowState: entry.windowState, onWindowStateChange: state => setWindowState(entry.id, state), footer: typeof footer === "function" ? footer(entry.handle) : footer, onClose: reason => requestClose(entry.id, reason), onClosed: () => removeEntry(entry.id), children: typeof content === "function" ? content(entry.handle) : content }, entry.id));
162
+ }), _jsx(PopupDock, {})] }));
163
+ }
@@ -0,0 +1,30 @@
1
+ import type { PopupProps } from "./Popup.tsx";
2
+ /**
3
+ * What a popup's rule may set: `reopen` (`replace` or `restore`), the window buttons and moving (`minimizable`,
4
+ * `maximizable`, `movable`), how it closes (`closable`, `closeOnEscape`, `closeOnBackdrop`), its `size`, and its
5
+ * `defaultWindowState`.
6
+ */
7
+ export type PopupRule = Pick<PopupProps, "reopen" | "minimizable" | "maximizable" | "movable" | "closable" | "closeOnEscape" | "closeOnBackdrop" | "size" | "defaultWindowState">;
8
+ /**
9
+ * Sets rules for popups by key. A name is a key (`booking-new`), or a pattern ending in `*` for every key that starts
10
+ * with the rest (`booking-edit-*`; `*` alone matches every key). Every rule that matches a key applies, the more
11
+ * specific over the less: an exact key over a pattern, a longer pattern over a shorter one. Options given to
12
+ * `openPopup` win over all of them. Setting a name again replaces its rule. Call it in a client module, where the app
13
+ * starts or beside the code that opens the popups. Returns a function that removes these rules.
14
+ *
15
+ * @example
16
+ * definePopupRules({
17
+ * "booking-new": { reopen: "replace" }, // Add: a fresh form every time
18
+ * "booking-edit-*": { reopen: "restore" }, // Edit: back as it was, with what was typed
19
+ * "night-report": { minimizable: false, maximizable: false, movable: false },
20
+ * });
21
+ */
22
+ export declare function definePopupRules(next: Record<string, PopupRule>): () => void;
23
+ /** Removes every rule. */
24
+ export declare function clearPopupRules(): void;
25
+ /** The rule for a key: every matching rule merged, the more specific over the less. Undefined when none matches. */
26
+ export declare function popupRuleFor(key: string | undefined): PopupRule | undefined;
27
+ /** A popup's options over its key's rule: what the options leave out, or set to undefined, comes from the rule. */
28
+ export declare function withPopupRule<T extends {
29
+ key?: string;
30
+ }>(options: T): T & PopupRule;
@@ -0,0 +1,63 @@
1
+ const rules = new Map();
2
+ /**
3
+ * Sets rules for popups by key. A name is a key (`booking-new`), or a pattern ending in `*` for every key that starts
4
+ * with the rest (`booking-edit-*`; `*` alone matches every key). Every rule that matches a key applies, the more
5
+ * specific over the less: an exact key over a pattern, a longer pattern over a shorter one. Options given to
6
+ * `openPopup` win over all of them. Setting a name again replaces its rule. Call it in a client module, where the app
7
+ * starts or beside the code that opens the popups. Returns a function that removes these rules.
8
+ *
9
+ * @example
10
+ * definePopupRules({
11
+ * "booking-new": { reopen: "replace" }, // Add: a fresh form every time
12
+ * "booking-edit-*": { reopen: "restore" }, // Edit: back as it was, with what was typed
13
+ * "night-report": { minimizable: false, maximizable: false, movable: false },
14
+ * });
15
+ */
16
+ export function definePopupRules(next) {
17
+ const added = Object.entries(next);
18
+ for (const [name, rule] of added)
19
+ rules.set(name, rule);
20
+ return () => {
21
+ for (const [name, rule] of added)
22
+ if (rules.get(name) === rule)
23
+ rules.delete(name);
24
+ };
25
+ }
26
+ /** Removes every rule. */
27
+ export function clearPopupRules() {
28
+ rules.clear();
29
+ }
30
+ /** How closely a rule's name matches a key: -1 when it does not; an exact key beats any pattern. */
31
+ function specificity(name, key) {
32
+ if (name === key)
33
+ return Number.MAX_SAFE_INTEGER;
34
+ if (!name.endsWith("*"))
35
+ return -1;
36
+ const prefix = name.slice(0, -1);
37
+ return key.startsWith(prefix) ? prefix.length : -1;
38
+ }
39
+ /** The rule for a key: every matching rule merged, the more specific over the less. Undefined when none matches. */
40
+ export function popupRuleFor(key) {
41
+ if (key === undefined || rules.size === 0)
42
+ return undefined;
43
+ const matches = [...rules]
44
+ .map(([name, rule]) => ({ rule, rank: specificity(name, key) }))
45
+ .filter(match => match.rank >= 0)
46
+ .sort((a, b) => a.rank - b.rank);
47
+ if (matches.length === 0)
48
+ return undefined;
49
+ return matches.reduce((merged, match) => withDefined(merged, match.rule), {});
50
+ }
51
+ /** A popup's options over its key's rule: what the options leave out, or set to undefined, comes from the rule. */
52
+ export function withPopupRule(options) {
53
+ const rule = popupRuleFor(options.key);
54
+ return rule ? withDefined(rule, options) : options;
55
+ }
56
+ // `{ ...base, ...over }`, except that an undefined value in `over` does not hide the one in `base`.
57
+ function withDefined(base, over) {
58
+ const result = { ...base };
59
+ for (const [name, value] of Object.entries(over))
60
+ if (value !== undefined)
61
+ result[name] = value;
62
+ return result;
63
+ }
@@ -0,0 +1,13 @@
1
+ import { type ThemeOption } from "../../Interfaces/ThemeInterfaces.ts";
2
+ export interface ThemePickerProps {
3
+ /** Themes to offer. Default: every built-in theme. */
4
+ themes?: readonly ThemeOption[];
5
+ /** Show the Light / Dark / System choice. Default `true`. */
6
+ showMode?: boolean;
7
+ className?: string;
8
+ }
9
+ /**
10
+ * Light / Dark / System, and a card per theme with a live preview of its colors.
11
+ * The choice applies at once and is saved in the browser.
12
+ */
13
+ export declare function ThemePicker({ themes, showMode, className }: ThemePickerProps): import("react").JSX.Element;
@@ -0,0 +1,22 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { iconCheck, iconDeviceDesktop, iconMoon, iconSun, InlineIcon } from "nexus-icons";
4
+ import { useId } from "react";
5
+ import { cx } from "../../Helpers/ClassHelpers.js";
6
+ import { NEXUS_THEMES } from "../../Interfaces/ThemeInterfaces.js";
7
+ import { useTheme } from "../../Services/ThemeService.js";
8
+ import { TabButtons } from "../Documents/TabButtons.js";
9
+ const MODES = [
10
+ { value: "light", label: "Light", icon: _jsx(InlineIcon, { icon: iconSun }) },
11
+ { value: "dark", label: "Dark", icon: _jsx(InlineIcon, { icon: iconMoon }) },
12
+ { value: "system", label: "System", icon: _jsx(InlineIcon, { icon: iconDeviceDesktop }), title: "Follow the operating system" },
13
+ ];
14
+ /**
15
+ * Light / Dark / System, and a card per theme with a live preview of its colors.
16
+ * The choice applies at once and is saved in the browser.
17
+ */
18
+ export function ThemePicker({ themes = NEXUS_THEMES, showMode = true, className }) {
19
+ const { theme, mode, setTheme, setMode } = useTheme();
20
+ const name = useId();
21
+ return (_jsxs("div", { className: cx("nx-theme-picker", className), children: [showMode && (_jsxs("div", { className: "nx-theme-picker__group", children: [_jsx("p", { id: `${name}-mode`, className: "nx-theme-picker__legend", children: "Mode" }), _jsx(TabButtons, { "aria-labelledby": `${name}-mode`, name: `${name}-mode`, options: MODES, value: mode, onChange: setMode, block: true })] })), _jsxs("fieldset", { className: "nx-theme-picker__group", children: [_jsx("legend", { className: "nx-theme-picker__legend", children: "Theme" }), _jsx("div", { className: "nx-theme-picker__grid", children: themes.map(option => (_jsxs("label", { className: "nx-theme-option", title: option.description, children: [_jsx("input", { type: "radio", className: "nx-visually-hidden", name: `${name}-theme`, value: option.id, checked: theme === option.id, onChange: () => setTheme(option.id) }), _jsx("span", { className: "nx-theme-option__preview", "data-nx-theme": option.id, "aria-hidden": "true", children: _jsxs("span", { className: "nx-theme-option__window", children: [_jsx("span", { className: "nx-theme-option__line" }), _jsx("span", { className: "nx-theme-option__line nx-theme-option__line--muted" }), _jsxs("span", { className: "nx-theme-option__swatches", children: [_jsx("span", { className: "nx-theme-option__pill" }), _jsx("span", { className: "nx-theme-option__dot nx-theme-option__dot--success" }), _jsx("span", { className: "nx-theme-option__dot nx-theme-option__dot--warning" }), _jsx("span", { className: "nx-theme-option__dot nx-theme-option__dot--danger" })] })] }) }), _jsxs("span", { className: "nx-theme-option__name", children: [option.name, _jsx(InlineIcon, { icon: iconCheck, className: "nx-theme-option__check" })] })] }, option.id))) })] })] }));
22
+ }
@@ -0,0 +1,14 @@
1
+ import { type ThemeScriptOptions } from "../../Helpers/ThemeHelpers.ts";
2
+ export interface ThemeScriptProps extends ThemeScriptOptions {
3
+ /** Content Security Policy nonce, when the app uses one. */
4
+ nonce?: string;
5
+ }
6
+ /**
7
+ * Applies the saved theme and mode before the first paint. Render it in `<head>` of the root layout,
8
+ * and put `suppressHydrationWarning` on `<html>`, because the script sets attributes there.
9
+ *
10
+ * @example
11
+ * <html lang="en" suppressHydrationWarning>
12
+ * <head><ThemeScript /></head>
13
+ */
14
+ export declare function ThemeScript({ nonce, ...options }: ThemeScriptProps): import("react").JSX.Element;
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { themeScript } from "../../Helpers/ThemeHelpers.js";
3
+ /**
4
+ * Applies the saved theme and mode before the first paint. Render it in `<head>` of the root layout,
5
+ * and put `suppressHydrationWarning` on `<html>`, because the script sets attributes there.
6
+ *
7
+ * @example
8
+ * <html lang="en" suppressHydrationWarning>
9
+ * <head><ThemeScript /></head>
10
+ */
11
+ export function ThemeScript({ nonce, ...options }) {
12
+ return _jsx("script", { nonce: nonce, dangerouslySetInnerHTML: { __html: themeScript(options) } });
13
+ }
@@ -0,0 +1,13 @@
1
+ import { type ButtonVariant } from "../Documents/Button.tsx";
2
+ import { type ThemePickerProps } from "./ThemePicker.tsx";
3
+ export interface ThemeSwitcherProps extends ThemePickerProps {
4
+ /** Name of the button for screen readers, also shown as its tooltip. Default "Theme". */
5
+ label?: string;
6
+ /** Default `ghost`. */
7
+ variant?: ButtonVariant;
8
+ }
9
+ /**
10
+ * A palette button that opens the theme picker below it. The panel is a native popover:
11
+ * it closes with Esc or a click outside, and needs no library.
12
+ */
13
+ export declare function ThemeSwitcher({ label, variant, className, ...pickerProps }: ThemeSwitcherProps): import("react").JSX.Element;
@@ -0,0 +1,29 @@
1
+ "use client";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { iconPalette, InlineIcon } from "nexus-icons";
4
+ import { useId, useRef } from "react";
5
+ import { Button } from "../Documents/Button.js";
6
+ import { ThemePicker } from "./ThemePicker.js";
7
+ // Keep in step with the width of .nx-theme-switcher__panel.
8
+ const PANEL_WIDTH = 340;
9
+ const GAP = 8;
10
+ /**
11
+ * A palette button that opens the theme picker below it. The panel is a native popover:
12
+ * it closes with Esc or a click outside, and needs no library.
13
+ */
14
+ export function ThemeSwitcher({ label = "Theme", variant = "ghost", className, ...pickerProps }) {
15
+ const panelId = useId();
16
+ const buttonRef = useRef(null);
17
+ // Opens under the button, aligned to its right edge, and kept inside the window.
18
+ function place(event) {
19
+ const button = buttonRef.current;
20
+ if (event.newState !== "open" || !button)
21
+ return;
22
+ const box = button.getBoundingClientRect();
23
+ const width = Math.min(PANEL_WIDTH, window.innerWidth - GAP * 2);
24
+ const left = Math.min(Math.max(GAP, box.right - width), window.innerWidth - width - GAP);
25
+ event.currentTarget.style.top = `${box.bottom + GAP}px`;
26
+ event.currentTarget.style.left = `${left}px`;
27
+ }
28
+ return (_jsxs(_Fragment, { children: [_jsx(Button, { ref: buttonRef, variant: variant, className: className, "aria-label": label, title: label, popoverTarget: panelId, icon: _jsx(InlineIcon, { icon: iconPalette }) }), _jsx("div", { id: panelId, popover: "auto", role: "dialog", "aria-label": label, className: "nx-popover nx-theme-switcher__panel", onBeforeToggle: place, children: _jsx(ThemePicker, { ...pickerProps }) })] }));
29
+ }
@@ -0,0 +1,76 @@
1
+ import { type ReactNode } from "react";
2
+ export type ToastTone = "neutral" | "info" | "success" | "warning" | "danger";
3
+ export type ToastPosition = "top-right" | "top-center" | "top-left" | "bottom-right" | "bottom-center" | "bottom-left";
4
+ export interface ToastAction {
5
+ label: ReactNode;
6
+ onClick: () => void;
7
+ }
8
+ export interface ToastOptions {
9
+ /** Pass the id of a toast that is showing to update it in place. */
10
+ id?: string;
11
+ /** Text under the title. */
12
+ message?: ReactNode;
13
+ /** Default `neutral`. */
14
+ tone?: ToastTone;
15
+ /** Replaces the tone's icon; `null` shows none. */
16
+ icon?: ReactNode | null;
17
+ /** Milliseconds until the toast closes itself. Default 5000; 0 keeps it until it is closed. Pauses while hovered. */
18
+ duration?: number;
19
+ /** A button at the end, such as Undo. Clicking it closes the toast. */
20
+ action?: ToastAction;
21
+ /** Shows the close button. Default true. */
22
+ closable?: boolean;
23
+ /** Where this toast stacks. Default: the Toaster's position. */
24
+ position?: ToastPosition;
25
+ /** After the toast has left. */
26
+ onClose?: () => void;
27
+ }
28
+ export interface ToastData extends ToastOptions {
29
+ id: string;
30
+ title: ReactNode;
31
+ tone: ToastTone;
32
+ duration: number;
33
+ /** The toast is playing its exit animation. */
34
+ closing: boolean;
35
+ /** Grows on every update, so the timer starts again. */
36
+ version: number;
37
+ }
38
+ /** Shows a toast, or updates the one with the same `id`. Returns the id. */
39
+ declare function show(title: ReactNode, options?: ToastOptions | string): string;
40
+ /** Closes one toast, or every toast without an id. */
41
+ export declare function dismissToast(id?: string): void;
42
+ type ToastText<T> = ReactNode | ((value: T) => ReactNode);
43
+ declare function loading(title: ReactNode, options?: ToastOptions | string): string;
44
+ export interface ToastPromiseTexts<T> {
45
+ loading: ReactNode;
46
+ success: ToastText<T>;
47
+ error: ToastText<unknown>;
48
+ }
49
+ /** A loading toast that turns into a success or an error toast when the promise settles. Returns the promise. */
50
+ declare function promise<T>(work: Promise<T>, texts: ToastPromiseTexts<T>, options?: ToastOptions): Promise<T>;
51
+ /**
52
+ * Shows a toast: `toast("Saved")`, `toast.success("Booking confirmed", "Room 204, 3 nights")`,
53
+ * `toast.error("Could not save", { action: { label: "Retry", onClick: save } })`, `toast.promise(save(), { ... })`.
54
+ * Needs one `<Toaster />` on the page.
55
+ */
56
+ export declare const toast: typeof show & {
57
+ info: (title: ReactNode, options?: ToastOptions | string) => string;
58
+ success: (title: ReactNode, options?: ToastOptions | string) => string;
59
+ warning: (title: ReactNode, options?: ToastOptions | string) => string;
60
+ error: (title: ReactNode, options?: ToastOptions | string) => string;
61
+ loading: typeof loading;
62
+ promise: typeof promise;
63
+ dismiss: typeof dismissToast;
64
+ };
65
+ export interface ToasterProps {
66
+ /** Corner or edge where toasts stack. Default `top-right`. */
67
+ position?: ToastPosition;
68
+ /** Most toasts shown at once per position; the oldest close to make room. Default 5. */
69
+ max?: number;
70
+ }
71
+ /**
72
+ * Shows the toasts. Put one in the root layout. Toasts stack from the chosen edge, newest nearest to it,
73
+ * and the rest of the page stays clickable around them.
74
+ */
75
+ export declare function Toaster({ position, max }: ToasterProps): import("react").JSX.Element;
76
+ export {};