nexus-shared 1.1.21 → 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.
- package/LICENSE +21 -0
- package/README.md +903 -0
- package/dist/Client.Index.d.ts +44 -0
- package/dist/Client.Index.js +49 -0
- package/dist/Components/Documents/Button.d.ts +29 -0
- package/dist/Components/Documents/Button.js +11 -0
- package/dist/Components/Documents/Menu.d.ts +102 -0
- package/dist/Components/Documents/Menu.js +238 -0
- package/dist/Components/Documents/SplitButton.d.ts +64 -0
- package/dist/Components/Documents/SplitButton.js +127 -0
- package/dist/Components/Documents/TabButtons.d.ts +62 -0
- package/dist/Components/Documents/TabButtons.js +19 -0
- package/dist/Components/Forms/ApiForm.d.ts +21 -0
- package/dist/Components/Forms/ApiForm.js +99 -0
- package/dist/Components/Forms/Crud.d.ts +17 -0
- package/dist/Components/Forms/Crud.js +556 -0
- package/dist/Components/Forms/CrudParts.d.ts +15 -0
- package/dist/Components/Forms/CrudParts.js +36 -0
- package/dist/Components/Forms/Form.d.ts +52 -0
- package/dist/Components/Forms/Form.js +319 -0
- package/dist/Components/Forms/SubmitForm.d.ts +65 -0
- package/dist/Components/Forms/SubmitForm.js +198 -0
- package/dist/Components/Inputs/Calendar.d.ts +69 -0
- package/dist/Components/Inputs/Calendar.js +258 -0
- package/dist/Components/Inputs/CheckBox.d.ts +53 -0
- package/dist/Components/Inputs/CheckBox.js +87 -0
- package/dist/Components/Inputs/CheckBoxGroup.d.ts +20 -0
- package/dist/Components/Inputs/CheckBoxGroup.js +80 -0
- package/dist/Components/Inputs/ChoiceField.d.ts +122 -0
- package/dist/Components/Inputs/ChoiceField.js +169 -0
- package/dist/Components/Inputs/DatePicker.d.ts +54 -0
- package/dist/Components/Inputs/DatePicker.js +246 -0
- package/dist/Components/Inputs/DateRangePicker.d.ts +62 -0
- package/dist/Components/Inputs/DateRangePicker.js +283 -0
- package/dist/Components/Inputs/DateTimePicker.d.ts +75 -0
- package/dist/Components/Inputs/DateTimePicker.js +399 -0
- package/dist/Components/Inputs/Dropdown.d.ts +117 -0
- package/dist/Components/Inputs/Dropdown.js +839 -0
- package/dist/Components/Inputs/DropdownParts.d.ts +84 -0
- package/dist/Components/Inputs/DropdownParts.js +102 -0
- package/dist/Components/Inputs/EditorShell.d.ts +45 -0
- package/dist/Components/Inputs/EditorShell.js +214 -0
- package/dist/Components/Inputs/Field.d.ts +53 -0
- package/dist/Components/Inputs/Field.js +16 -0
- package/dist/Components/Inputs/GroupForm.d.ts +20 -0
- package/dist/Components/Inputs/GroupForm.js +170 -0
- package/dist/Components/Inputs/InputField.d.ts +59 -0
- package/dist/Components/Inputs/InputField.js +169 -0
- package/dist/Components/Inputs/InputRenderer.d.ts +35 -0
- package/dist/Components/Inputs/InputRenderer.js +99 -0
- package/dist/Components/Inputs/MarkdownEditor.d.ts +67 -0
- package/dist/Components/Inputs/MarkdownEditor.js +289 -0
- package/dist/Components/Inputs/NumberBox.d.ts +53 -0
- package/dist/Components/Inputs/NumberBox.js +228 -0
- package/dist/Components/Inputs/RadioGroup.d.ts +20 -0
- package/dist/Components/Inputs/RadioGroup.js +43 -0
- package/dist/Components/Inputs/ReadOnlyNotice.d.ts +36 -0
- package/dist/Components/Inputs/ReadOnlyNotice.js +116 -0
- package/dist/Components/Inputs/RichTextEditor.d.ts +103 -0
- package/dist/Components/Inputs/RichTextEditor.js +1170 -0
- package/dist/Components/Inputs/RichTextParts.d.ts +63 -0
- package/dist/Components/Inputs/RichTextParts.js +157 -0
- package/dist/Components/Inputs/RowsInput.d.ts +104 -0
- package/dist/Components/Inputs/RowsInput.js +259 -0
- package/dist/Components/Inputs/Slider.d.ts +87 -0
- package/dist/Components/Inputs/Slider.js +216 -0
- package/dist/Components/Inputs/TabularForm.d.ts +23 -0
- package/dist/Components/Inputs/TabularForm.js +283 -0
- package/dist/Components/Inputs/TextArea.d.ts +43 -0
- package/dist/Components/Inputs/TextArea.js +31 -0
- package/dist/Components/Inputs/TextBox.d.ts +52 -0
- package/dist/Components/Inputs/TextBox.js +58 -0
- package/dist/Components/Inputs/TextField.d.ts +31 -0
- package/dist/Components/Inputs/TextField.js +35 -0
- package/dist/Components/Inputs/TimePanel.d.ts +34 -0
- package/dist/Components/Inputs/TimePanel.js +288 -0
- package/dist/Components/Inputs/TimePicker.d.ts +54 -0
- package/dist/Components/Inputs/TimePicker.js +239 -0
- package/dist/Components/Inputs/TreeView.d.ts +160 -0
- package/dist/Components/Inputs/TreeView.js +947 -0
- package/dist/Components/Layouts/MessageBox.d.ts +71 -0
- package/dist/Components/Layouts/MessageBox.js +104 -0
- package/dist/Components/Layouts/Popup.d.ts +92 -0
- package/dist/Components/Layouts/Popup.js +376 -0
- package/dist/Components/Layouts/PopupContext.d.ts +2 -0
- package/dist/Components/Layouts/PopupContext.js +4 -0
- package/dist/Components/Layouts/PopupDock.d.ts +22 -0
- package/dist/Components/Layouts/PopupDock.js +121 -0
- package/dist/Components/Layouts/PopupHost.d.ts +56 -0
- package/dist/Components/Layouts/PopupHost.js +163 -0
- package/dist/Components/Layouts/PopupRules.d.ts +30 -0
- package/dist/Components/Layouts/PopupRules.js +63 -0
- package/dist/Components/Layouts/ThemePicker.d.ts +13 -0
- package/dist/Components/Layouts/ThemePicker.js +22 -0
- package/dist/Components/Layouts/ThemeScript.d.ts +14 -0
- package/dist/Components/Layouts/ThemeScript.js +13 -0
- package/dist/Components/Layouts/ThemeSwitcher.d.ts +13 -0
- package/dist/Components/Layouts/ThemeSwitcher.js +29 -0
- package/dist/Components/Layouts/Toaster.d.ts +76 -0
- package/dist/Components/Layouts/Toaster.js +172 -0
- package/dist/Components/Viewers/DataTable.d.ts +25 -0
- package/dist/Components/Viewers/DataTable.js +542 -0
- package/dist/Components/Viewers/DataTableParts.d.ts +134 -0
- package/dist/Components/Viewers/DataTableParts.js +145 -0
- package/dist/Components/Viewers/MarkdownView.d.ts +26 -0
- package/dist/Components/Viewers/MarkdownView.js +79 -0
- package/dist/Components/Viewers/RichTextView.d.ts +18 -0
- package/dist/Components/Viewers/RichTextView.js +17 -0
- package/dist/Helpers/AnimationHelpers.d.ts +6 -0
- package/dist/Helpers/AnimationHelpers.js +35 -0
- package/dist/Helpers/ApiClient.d.ts +74 -0
- package/dist/Helpers/ApiClient.js +197 -0
- package/dist/Helpers/ApiFormHelpers.d.ts +26 -0
- package/dist/Helpers/ApiFormHelpers.js +56 -0
- package/dist/Helpers/ApiModules.d.ts +135 -0
- package/dist/Helpers/ApiModules.js +194 -0
- package/dist/Helpers/ApiResponses.d.ts +41 -0
- package/dist/Helpers/ApiResponses.js +159 -0
- package/dist/Helpers/ApiRoutes.d.ts +150 -0
- package/dist/Helpers/ApiRoutes.js +191 -0
- package/dist/Helpers/ApiToasts.d.ts +21 -0
- package/dist/Helpers/ApiToasts.js +22 -0
- package/dist/Helpers/ClassHelpers.d.ts +3 -0
- package/dist/Helpers/ClassHelpers.js +4 -0
- package/dist/Helpers/CrudBackend.d.ts +56 -0
- package/dist/Helpers/CrudBackend.js +119 -0
- package/dist/Helpers/CrudHelpers.d.ts +76 -0
- package/dist/Helpers/CrudHelpers.js +202 -0
- package/dist/Helpers/DateHelpers.d.ts +88 -0
- package/dist/Helpers/DateHelpers.js +363 -0
- package/dist/Helpers/DatePreferences.d.ts +14 -0
- package/dist/Helpers/DatePreferences.js +94 -0
- package/dist/Helpers/DateTimeHelpers.d.ts +87 -0
- package/dist/Helpers/DateTimeHelpers.js +286 -0
- package/dist/Helpers/DragHelpers.d.ts +129 -0
- package/dist/Helpers/DragHelpers.js +281 -0
- package/dist/Helpers/DropdownHelpers.d.ts +56 -0
- package/dist/Helpers/DropdownHelpers.js +180 -0
- package/dist/Helpers/FormDrafts.d.ts +18 -0
- package/dist/Helpers/FormDrafts.js +76 -0
- package/dist/Helpers/FormStore.d.ts +231 -0
- package/dist/Helpers/FormStore.js +734 -0
- package/dist/Helpers/InputParamsHelpers.d.ts +79 -0
- package/dist/Helpers/InputParamsHelpers.js +388 -0
- package/dist/Helpers/MarkdownEditing.d.ts +27 -0
- package/dist/Helpers/MarkdownEditing.js +250 -0
- package/dist/Helpers/MarkdownHelpers.d.ts +82 -0
- package/dist/Helpers/MarkdownHelpers.js +775 -0
- package/dist/Helpers/MessageBuilder.d.ts +74 -0
- package/dist/Helpers/MessageBuilder.js +254 -0
- package/dist/Helpers/NepaliCalendar.d.ts +25 -0
- package/dist/Helpers/NepaliCalendar.js +107 -0
- package/dist/Helpers/NumberHelpers.d.ts +71 -0
- package/dist/Helpers/NumberHelpers.js +202 -0
- package/dist/Helpers/Permissions.d.ts +62 -0
- package/dist/Helpers/Permissions.js +133 -0
- package/dist/Helpers/PopoverHelpers.d.ts +43 -0
- package/dist/Helpers/PopoverHelpers.js +91 -0
- package/dist/Helpers/RichTextHelpers.d.ts +77 -0
- package/dist/Helpers/RichTextHelpers.js +1072 -0
- package/dist/Helpers/RowsStore.d.ts +160 -0
- package/dist/Helpers/RowsStore.js +584 -0
- package/dist/Helpers/TableColumns.d.ts +40 -0
- package/dist/Helpers/TableColumns.js +254 -0
- package/dist/Helpers/TableExport.d.ts +30 -0
- package/dist/Helpers/TableExport.js +97 -0
- package/dist/Helpers/TableHelpers.d.ts +102 -0
- package/dist/Helpers/TableHelpers.js +281 -0
- package/dist/Helpers/TableStore.d.ts +142 -0
- package/dist/Helpers/TableStore.js +530 -0
- package/dist/Helpers/ThemeHelpers.d.ts +15 -0
- package/dist/Helpers/ThemeHelpers.js +16 -0
- package/dist/Helpers/TimeHelpers.d.ts +73 -0
- package/dist/Helpers/TimeHelpers.js +253 -0
- package/dist/Helpers/TreeStore.d.ts +188 -0
- package/dist/Helpers/TreeStore.js +577 -0
- package/dist/Helpers/ValidationHelpers.d.ts +124 -0
- package/dist/Helpers/ValidationHelpers.js +301 -0
- package/dist/Interfaces/ApiInterfaces.d.ts +149 -0
- package/dist/Interfaces/ApiInterfaces.js +24 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +171 -0
- package/{src/sso-client.tsx → dist/Interfaces/CrudInterfaces.js} +1 -1
- package/dist/Interfaces/DateInterfaces.d.ts +154 -0
- package/dist/Interfaces/DateInterfaces.js +29 -0
- package/dist/Interfaces/FormInterfaces.d.ts +352 -0
- package/dist/Interfaces/FormInterfaces.js +1 -0
- package/dist/Interfaces/InputInterfaces.d.ts +141 -0
- package/dist/Interfaces/InputInterfaces.js +1 -0
- package/dist/Interfaces/MessageInterfaces.d.ts +111 -0
- package/dist/Interfaces/MessageInterfaces.js +5 -0
- package/dist/Interfaces/TableInterfaces.d.ts +291 -0
- package/dist/Interfaces/TableInterfaces.js +1 -0
- package/dist/Interfaces/ThemeInterfaces.d.ts +17 -0
- package/dist/Interfaces/ThemeInterfaces.js +17 -0
- package/dist/Interfaces/TypeInterfaces.d.ts +4 -0
- package/dist/Interfaces/TypeInterfaces.js +1 -0
- package/dist/Server.Index.d.ts +2 -0
- package/dist/Server.Index.js +5 -0
- package/dist/Services/ApiProxy.d.ts +32 -0
- package/dist/Services/ApiProxy.js +111 -0
- package/dist/Services/BrowserApi.d.ts +78 -0
- package/dist/Services/BrowserApi.js +111 -0
- package/dist/Services/ServerApi.d.ts +49 -0
- package/dist/Services/ServerApi.js +75 -0
- package/dist/Services/ThemeService.d.ts +17 -0
- package/dist/Services/ThemeService.js +83 -0
- package/dist/Shared.Index.d.ts +43 -0
- package/dist/Shared.Index.js +47 -0
- package/package.json +82 -33
- package/src/Styles/Nexus.Base.css +70 -0
- package/src/Styles/Nexus.Button.css +151 -0
- package/src/Styles/Nexus.Calendar.css +275 -0
- package/src/Styles/Nexus.Choice.css +345 -0
- package/src/Styles/Nexus.Crud.css +84 -0
- package/src/Styles/Nexus.DateTime.css +81 -0
- package/src/Styles/Nexus.Dropdown.css +774 -0
- package/src/Styles/Nexus.Editor.css +297 -0
- package/src/Styles/Nexus.Form.css +298 -0
- package/src/Styles/Nexus.Index.css +34 -0
- package/src/Styles/Nexus.Input.css +637 -0
- package/src/Styles/Nexus.Markdown.css +265 -0
- package/src/Styles/Nexus.Menu.css +249 -0
- package/src/Styles/Nexus.Popup.css +731 -0
- package/src/Styles/Nexus.RichText.css +463 -0
- package/src/Styles/Nexus.Rows.css +566 -0
- package/src/Styles/Nexus.Slider.css +278 -0
- package/src/Styles/Nexus.Tab.Buttons.css +364 -0
- package/src/Styles/Nexus.Table.css +1014 -0
- package/src/Styles/Nexus.Theme.Picker.css +159 -0
- package/src/Styles/Nexus.Themes.css +194 -0
- package/src/Styles/Nexus.Time.css +147 -0
- package/src/Styles/Nexus.Toast.css +265 -0
- package/src/Styles/Nexus.Tokens.css +72 -0
- package/src/Styles/Nexus.Tree.css +435 -0
- package/src/api-services/authentication-service.tsx +0 -23
- package/src/api-services/preference-service.tsx +0 -5
- package/src/api-services/system-service.tsx +0 -29
- package/src/client.ts +0 -31
- package/src/components/documents/button.tsx +0 -133
- package/src/components/documents/icon-box.tsx +0 -93
- package/src/components/documents/page-title.tsx +0 -7
- package/src/components/documents/tab-button.tsx +0 -172
- package/src/components/documents/tag.tsx +0 -30
- package/src/components/index.js +0 -0
- package/src/components/inputs/checkbox-input.tsx +0 -66
- package/src/components/inputs/input-box.tsx +0 -48
- package/src/components/inputs/input-element.tsx +0 -65
- package/src/components/inputs/input-form.tsx +0 -164
- package/src/components/inputs/input.tsx +0 -181
- package/src/components/inputs/number-input.tsx +0 -108
- package/src/components/inputs/radiobox-input.tsx +0 -53
- package/src/components/inputs/textarea-input.tsx +0 -47
- package/src/components/inputs/textbox-input.tsx +0 -45
- package/src/components/layouts/global-dialogbox.tsx +0 -431
- package/src/components/layouts/global-layout.tsx +0 -64
- package/src/components/layouts/layout-helpers.tsx +0 -20
- package/src/components/layouts/panels/user-panel.tsx +0 -112
- package/src/components/layouts/utility-menu.tsx +0 -49
- package/src/components/panels/theme-panel.tsx +0 -46
- package/src/helpers/bitwise-helpers.tsx +0 -11
- package/src/helpers/browser-helpers.tsx +0 -156
- package/src/helpers/datasource-helpers.tsx +0 -99
- package/src/helpers/element-helpers.tsx +0 -123
- package/src/helpers/input-helpers.tsx +0 -74
- package/src/helpers/string-helpers.tsx +0 -28
- package/src/helpers/utility-helpers.tsx +0 -45
- package/src/helpers/validation-helpers.tsx +0 -302
- package/src/index.ts +0 -26
- package/src/interface.ts +0 -19
- package/src/interfaces/auth-token-interfaces.tsx +0 -6
- package/src/interfaces/browser-interfaces.tsx +0 -36
- package/src/interfaces/button-interfaces.tsx +0 -63
- package/src/interfaces/datasource-interfaces.tsx +0 -22
- package/src/interfaces/datatable-interfaces.tsx +0 -25
- package/src/interfaces/dialogbox-interfaces.tsx +0 -5
- package/src/interfaces/exception-interfaces.tsx +0 -99
- package/src/interfaces/http-interfaces.tsx +0 -129
- package/src/interfaces/icon-interfaces.tsx +0 -129
- package/src/interfaces/input-interfaces.tsx +0 -410
- package/src/interfaces/layout-interfaces.tsx +0 -191
- package/src/interfaces/menu-interfaces.tsx +0 -53
- package/src/interfaces/message-interfaces.tsx +0 -32
- package/src/interfaces/permission-interfaces.tsx +0 -9
- package/src/interfaces/storage-interfaces.tsx +0 -5
- package/src/interfaces/system-interfaces.tsx +0 -22
- package/src/interfaces/theme-interfaces.tsx +0 -123
- package/src/interfaces/type-interfaces.tsx +0 -28
- package/src/interfaces/user-interfaces.tsx +0 -47
- package/src/nexus-client.tsx +0 -21
- package/src/nexus.environments.tsx +0 -66
- package/src/proxy-api/proxy-backend.tsx +0 -60
- package/src/proxy-api/proxy-constants.tsx +0 -28
- package/src/proxy-api/proxy-helpers.tsx +0 -91
- package/src/proxy-api-client.tsx +0 -1
- package/src/proxy-api-server.tsx +0 -2
- package/src/services/http-client-services.tsx +0 -75
- package/src/services/http-services.tsx +0 -158
- package/src/services/loader-service.tsx +0 -185
- package/src/services/localstorage-service.tsx +0 -119
- package/src/services/message-services.tsx +0 -383
- package/src/services/theme-service.tsx +0 -163
- package/src/services/user-services.tsx +0 -10
- package/src/sso-config/auth-token-validation.ts +0 -16
- package/src/sso-config/callback-route.tsx +0 -35
- package/src/sso-config/cookie-encryption.ts +0 -51
- package/src/sso-config/cookie-helpers.tsx +0 -102
- package/src/sso-config/forgot-password-route.tsx +0 -36
- package/src/sso-config/oauth-callback-state.ts +0 -61
- package/src/sso-config/pkce-helpers.ts +0 -9
- package/src/sso-config/provider-complete-route.tsx +0 -18
- package/src/sso-config/redirect-context-actions.tsx +0 -45
- package/src/sso-config/redirect-context-helpers.tsx +0 -159
- package/src/sso-config/redirect-context-persist.tsx +0 -18
- package/src/sso-config/redirect-context-route.tsx +0 -45
- package/src/sso-config/refresh-route.tsx +0 -23
- package/src/sso-config/sign-in-route.tsx +0 -74
- package/src/sso-config/sign-out-route.tsx +0 -57
- package/src/sso-config/sign-up-route.tsx +0 -57
- package/src/sso-config/sso-backend-flow.ts +0 -38
- package/src/sso-config/sso-complete-route.tsx +0 -50
- package/src/sso-config/sso-config.ts +0 -59
- package/src/sso-config/sso-interfaces.tsx +0 -99
- package/src/sso-config/sso-response-helpers.tsx +0 -20
- package/src/sso-server.tsx +0 -21
- package/src/styles/nexus.animation.css +0 -269
- package/src/styles/nexus.core.css +0 -119
- package/src/styles/nexus.dialog.css +0 -144
- package/src/styles/nexus.grid-view.css +0 -42
- package/src/styles/nexus.icon.css +0 -51
- package/src/styles/nexus.input.css +0 -207
- package/src/styles/nexus.loader.css +0 -11
- package/src/styles/nexus.logic.css +0 -47
- package/src/styles/nexus.utility.css +0 -347
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { iconChevronDown, InlineIcon } from "nexus-icons";
|
|
4
|
+
import { useId, useLayoutEffect, useRef, useState } from "react";
|
|
5
|
+
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
6
|
+
import { followAnchor, isPopoverOpen, placeUnderAnchor } from "../../Helpers/PopoverHelpers.js";
|
|
7
|
+
import { Button } from "./Button.js";
|
|
8
|
+
import { MenuPanel } from "./Menu.js";
|
|
9
|
+
const STORAGE_PREFIX = "nx-split:";
|
|
10
|
+
function readChoice(key) {
|
|
11
|
+
if (!key)
|
|
12
|
+
return null;
|
|
13
|
+
try {
|
|
14
|
+
return localStorage.getItem(STORAGE_PREFIX + key);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function saveChoice(key, value) {
|
|
21
|
+
if (!key)
|
|
22
|
+
return;
|
|
23
|
+
try {
|
|
24
|
+
localStorage.setItem(STORAGE_PREFIX + key, value);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Storage blocked (private mode, for example): the choice lasts until the page reloads.
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A main action with a menu of alternatives, like "Create pull request" with "Create draft pull request".
|
|
32
|
+
* Picking an alternative makes it the main button, and with `storageKey` the browser remembers it for next time.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* <SplitButton storageKey="pull-request" onAction={create} options={[
|
|
36
|
+
* { value: "create", label: "Create pull request" },
|
|
37
|
+
* { value: "draft", label: "Create draft pull request", description: "Cannot be merged until marked ready" },
|
|
38
|
+
* ]} />
|
|
39
|
+
*/
|
|
40
|
+
export function SplitButton({ options, onAction, defaultValue, value, onValueChange, storageKey, runOnSelect = false, variant = "primary", size = "md", loading = false, disabled = false, block = false, type = "button", form, name, menuLabel = "More actions", align = "end", className, style, }) {
|
|
41
|
+
const id = useId();
|
|
42
|
+
const panelId = `${id}-menu`;
|
|
43
|
+
const toggleRef = useRef(null);
|
|
44
|
+
const panelRef = useRef(null);
|
|
45
|
+
const dismissingRef = useRef(false);
|
|
46
|
+
const [open, setOpen] = useState(false);
|
|
47
|
+
const [chosen, setChosen] = useState(defaultValue);
|
|
48
|
+
const usable = (candidate) => options.find(option => option.value === candidate && !option.disabled);
|
|
49
|
+
// The remembered choice applies before the first paint, so the button never shows the default and then switches.
|
|
50
|
+
useLayoutEffect(() => {
|
|
51
|
+
const saved = usable(readChoice(storageKey));
|
|
52
|
+
if (saved)
|
|
53
|
+
setChosen(saved.value);
|
|
54
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
55
|
+
}, [storageKey]);
|
|
56
|
+
const current = usable(value ?? chosen) ?? options.find(option => !option.disabled) ?? options[0];
|
|
57
|
+
if (!current)
|
|
58
|
+
return null;
|
|
59
|
+
function place() {
|
|
60
|
+
const panel = panelRef.current;
|
|
61
|
+
const anchor = toggleRef.current?.parentElement;
|
|
62
|
+
if (panel && anchor)
|
|
63
|
+
placeUnderAnchor(panel, anchor.getBoundingClientRect(), { align });
|
|
64
|
+
}
|
|
65
|
+
function show(focusFirst) {
|
|
66
|
+
const panel = panelRef.current;
|
|
67
|
+
if (!panel || typeof panel.showPopover !== "function" || isPopoverOpen(panel))
|
|
68
|
+
return;
|
|
69
|
+
panel.showPopover();
|
|
70
|
+
place();
|
|
71
|
+
if (focusFirst)
|
|
72
|
+
panel.querySelector('.nx-menu__item:not([aria-disabled="true"])')?.focus();
|
|
73
|
+
else
|
|
74
|
+
panel.focus();
|
|
75
|
+
}
|
|
76
|
+
function hide() {
|
|
77
|
+
if (isPopoverOpen(panelRef.current))
|
|
78
|
+
panelRef.current?.hidePopover();
|
|
79
|
+
}
|
|
80
|
+
useLayoutEffect(() => {
|
|
81
|
+
if (!open)
|
|
82
|
+
return;
|
|
83
|
+
return followAnchor(place);
|
|
84
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
85
|
+
}, [open, align]);
|
|
86
|
+
function onToggle(event) {
|
|
87
|
+
setOpen(event.newState === "open");
|
|
88
|
+
}
|
|
89
|
+
function pick(next) {
|
|
90
|
+
const option = usable(next);
|
|
91
|
+
if (!option || !current)
|
|
92
|
+
return;
|
|
93
|
+
if (value === undefined)
|
|
94
|
+
setChosen(next);
|
|
95
|
+
saveChoice(storageKey, next);
|
|
96
|
+
if (next !== current.value)
|
|
97
|
+
onValueChange?.(next, option);
|
|
98
|
+
if (runOnSelect)
|
|
99
|
+
onAction(next, option);
|
|
100
|
+
}
|
|
101
|
+
const items = options.map(option => ({
|
|
102
|
+
value: option.value,
|
|
103
|
+
label: option.label,
|
|
104
|
+
description: option.description,
|
|
105
|
+
icon: option.icon,
|
|
106
|
+
disabled: option.disabled,
|
|
107
|
+
checked: option.value === current.value,
|
|
108
|
+
}));
|
|
109
|
+
const blocked = disabled || loading;
|
|
110
|
+
return (_jsxs("div", { className: cx("nx-split", `nx-split--${variant}`, `nx-split--${size}`, block && "nx-split--block", className), style: style, children: [_jsx(Button, { type: type, form: form, variant: variant, size: size, icon: current.icon, loading: loading, disabled: disabled, className: "nx-split__main", onClick: () => onAction(current.value, current), children: current.label }), name && _jsx("input", { type: "hidden", name: name, form: form, value: current.value }), _jsx(Button, { ref: toggleRef, variant: variant, size: size, disabled: blocked, className: "nx-split__toggle", "aria-label": menuLabel, title: menuLabel, "aria-haspopup": "menu", "aria-expanded": open, "aria-controls": panelId, icon: _jsx(InlineIcon, { icon: iconChevronDown }), onPointerDown: () => {
|
|
111
|
+
dismissingRef.current = isPopoverOpen(panelRef.current);
|
|
112
|
+
}, onClick: event => {
|
|
113
|
+
if (dismissingRef.current) {
|
|
114
|
+
dismissingRef.current = false;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (isPopoverOpen(panelRef.current))
|
|
118
|
+
hide();
|
|
119
|
+
else
|
|
120
|
+
show(event.detail === 0);
|
|
121
|
+
}, onKeyDown: event => {
|
|
122
|
+
if (event.key === "ArrowDown") {
|
|
123
|
+
event.preventDefault();
|
|
124
|
+
show(true);
|
|
125
|
+
}
|
|
126
|
+
} }), _jsx(MenuPanel, { ref: panelRef, id: panelId, kind: "radio", size: size, items: items, onSelect: pick, "aria-label": menuLabel, onToggle: onToggle, className: "nx-split__menu" })] }));
|
|
127
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { Size } from "../../Interfaces/TypeInterfaces.ts";
|
|
3
|
+
export interface TabButtonOption<T extends string = string> {
|
|
4
|
+
value: T;
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
/** Shown before the label, e.g. `<InlineIcon icon={iconSun} />`. It takes the primary color while selected. */
|
|
7
|
+
icon?: ReactNode;
|
|
8
|
+
/** A second line in muted text. Shown by the `list` design only. */
|
|
9
|
+
description?: ReactNode;
|
|
10
|
+
/** A count or short tag at the end of the row, e.g. `3`. Shown by the `list` design only. */
|
|
11
|
+
badge?: ReactNode;
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
/** Tooltip. With `labels="selected"`, a text label is the default tooltip. */
|
|
14
|
+
title?: string;
|
|
15
|
+
}
|
|
16
|
+
/** `all`: every option shows its label. `selected`: options show only their icon, and the selected one adds its label. */
|
|
17
|
+
export type TabButtonLabels = "all" | "selected";
|
|
18
|
+
/** `nav`: a horizontal track with a sliding indicator. `list`: a vertical list of rows, for settings pages and side panels. */
|
|
19
|
+
export type TabButtonDesign = "nav" | "list";
|
|
20
|
+
interface TabButtonsBaseProps<T extends string> {
|
|
21
|
+
options: readonly TabButtonOption<T>[];
|
|
22
|
+
/** The selected option. A value that matches no option leaves every option unselected. */
|
|
23
|
+
value: T;
|
|
24
|
+
onChange: (value: T) => void;
|
|
25
|
+
/** Default `nav`. `list` stacks the options as full-width rows with a bar at the selected one. */
|
|
26
|
+
design?: TabButtonDesign;
|
|
27
|
+
/** Default `md`. */
|
|
28
|
+
size?: Size;
|
|
29
|
+
/** `neutral` (default): a raised indicator. `primary`: a solid primary indicator, for a choice that matters. */
|
|
30
|
+
tone?: "neutral" | "primary";
|
|
31
|
+
/**
|
|
32
|
+
* Default `all`. `selected` keeps a toolbar compact: icons only, and the selected option grows to show its label.
|
|
33
|
+
* Every option needs an icon; hidden labels are still read by screen readers and shown as tooltips. `nav` design only.
|
|
34
|
+
*/
|
|
35
|
+
labels?: TabButtonLabels;
|
|
36
|
+
/** Fills the width of its container. With `labels="all"`, options share the width equally. A `list` always fills it. */
|
|
37
|
+
block?: boolean;
|
|
38
|
+
/** Radio group name, for forms. Default: a unique id. */
|
|
39
|
+
name?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Id of the form the choice belongs to, as the HTML `form` attribute. An id that matches no form keeps the choice out
|
|
42
|
+
* of every form, for a switch inside a form that is not one of its values, such as an editor's view.
|
|
43
|
+
*/
|
|
44
|
+
form?: string;
|
|
45
|
+
className?: string;
|
|
46
|
+
}
|
|
47
|
+
/** A label is required: pass `aria-label`, or `aria-labelledby` pointing at a visible heading. */
|
|
48
|
+
export type TabButtonsProps<T extends string = string> = TabButtonsBaseProps<T> & ({
|
|
49
|
+
"aria-label": string;
|
|
50
|
+
"aria-labelledby"?: never;
|
|
51
|
+
} | {
|
|
52
|
+
"aria-labelledby": string;
|
|
53
|
+
"aria-label"?: never;
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* Tab buttons: pick one option from a small set, such as Light / Dark / System.
|
|
57
|
+
* The `nav` design slides an indicator to the selection; the `list` design stacks the options as rows, each with an
|
|
58
|
+
* icon, a label, a description, and a badge, for settings pages and side panels. Built on native radio buttons, so
|
|
59
|
+
* arrow keys move the selection and screen readers announce it. To switch between page panels, use tabs with panels instead.
|
|
60
|
+
*/
|
|
61
|
+
export declare function TabButtons<T extends string>({ options, value, onChange, design, size, tone, labels, block, name, form, className, ...labelling }: TabButtonsProps<T>): import("react").JSX.Element;
|
|
62
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useId } from "react";
|
|
4
|
+
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
5
|
+
/**
|
|
6
|
+
* Tab buttons: pick one option from a small set, such as Light / Dark / System.
|
|
7
|
+
* The `nav` design slides an indicator to the selection; the `list` design stacks the options as rows, each with an
|
|
8
|
+
* icon, a label, a description, and a badge, for settings pages and side panels. Built on native radio buttons, so
|
|
9
|
+
* arrow keys move the selection and screen readers announce it. To switch between page panels, use tabs with panels instead.
|
|
10
|
+
*/
|
|
11
|
+
export function TabButtons({ options, value, onChange, design = "nav", size = "md", tone = "neutral", labels = "all", block = false, name, form, className, ...labelling }) {
|
|
12
|
+
const generatedName = useId();
|
|
13
|
+
const groupName = name ?? generatedName;
|
|
14
|
+
const index = options.findIndex(option => option.value === value);
|
|
15
|
+
const style = { "--nx-tabs-count": Math.max(options.length, 1), "--nx-tabs-index": Math.max(index, 0) };
|
|
16
|
+
const list = design === "list";
|
|
17
|
+
const labelsSelected = !list && labels === "selected";
|
|
18
|
+
return (_jsxs("div", { role: "radiogroup", "aria-orientation": list ? "vertical" : undefined, ...labelling, className: cx("nx-tabs", `nx-tabs--${size}`, list && "nx-tabs--list", tone === "primary" && "nx-tabs--primary", labelsSelected && "nx-tabs--labels-selected", block && "nx-tabs--block", className), style: list ? 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: "radio", className: "nx-visually-hidden", name: groupName, form: form, value: option.value, checked: option.value === value, disabled: option.disabled, onChange: () => onChange(option.value) }), 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)))] }));
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ApiFormProps, FormValues } from "../../Interfaces/FormInterfaces.ts";
|
|
2
|
+
/**
|
|
3
|
+
* A `SubmitForm` that sends its values to an API: an endpoint (`ROOMS.update`, or `{ module, path, method }` in place),
|
|
4
|
+
* a URL outside the app's modules (`{ url, method }`, as JSON), both sent through the app's `api` without its toasts, or
|
|
5
|
+
* a function that sends them with a client of the app's own. The answer is read as Nexus API responses (`{ isSuccess, message,
|
|
6
|
+
* inputName, result }`), ASP.NET Core problem details, or any JSON by its status. On success a toast says so and the
|
|
7
|
+
* values become the form's own; on failure the server's messages show on the inputs they name, the rest under the form,
|
|
8
|
+
* and the first input with a message takes focus. `loadValue` loads the record to edit, with placeholders and Retry.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const ROOMS = apiController(NexusModule.Reservation, "Room", "room");
|
|
12
|
+
*
|
|
13
|
+
* <ApiForm
|
|
14
|
+
* inputs={ROOM_INPUTS}
|
|
15
|
+
* loadValue={signal => api.get<Room>(ROOMS.details, { params: { id }, signal, toast: false }).then(unwrapApiResult)}
|
|
16
|
+
* api={ROOMS.update}
|
|
17
|
+
* onSuccess={room => table.updateRow(room)}
|
|
18
|
+
* onCancel={close}
|
|
19
|
+
* />
|
|
20
|
+
*/
|
|
21
|
+
export declare function ApiForm<TValues extends FormValues = FormValues>(props: ApiFormProps<TValues>): import("react").JSX.Element;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { iconAlertCircle, iconRefresh, InlineIcon } from "nexus-icons";
|
|
4
|
+
import { useEffect, useRef, useState } from "react";
|
|
5
|
+
import { abortError, apiRequestOptions, matchFieldErrors, toApiResult } from "../../Helpers/ApiFormHelpers.js";
|
|
6
|
+
import { apiStatusMessage, errorMessage, isAbortError, readApiResponse } from "../../Helpers/ApiResponses.js";
|
|
7
|
+
import { isApiEndpoint } from "../../Helpers/ApiRoutes.js";
|
|
8
|
+
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
9
|
+
import { messages } from "../../Helpers/MessageBuilder.js";
|
|
10
|
+
import { api as browserApi } from "../../Services/BrowserApi.js";
|
|
11
|
+
import { Button } from "../Documents/Button.js";
|
|
12
|
+
import { toast } from "../Layouts/Toaster.js";
|
|
13
|
+
import { useFormStore } from "./Form.js";
|
|
14
|
+
import { SubmitForm } from "./SubmitForm.js";
|
|
15
|
+
/**
|
|
16
|
+
* A `SubmitForm` that sends its values to an API: an endpoint (`ROOMS.update`, or `{ module, path, method }` in place),
|
|
17
|
+
* a URL outside the app's modules (`{ url, method }`, as JSON), both sent through the app's `api` without its toasts, or
|
|
18
|
+
* a function that sends them with a client of the app's own. The answer is read as Nexus API responses (`{ isSuccess, message,
|
|
19
|
+
* inputName, result }`), ASP.NET Core problem details, or any JSON by its status. On success a toast says so and the
|
|
20
|
+
* values become the form's own; on failure the server's messages show on the inputs they name, the rest under the form,
|
|
21
|
+
* and the first input with a message takes focus. `loadValue` loads the record to edit, with placeholders and Retry.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* const ROOMS = apiController(NexusModule.Reservation, "Room", "room");
|
|
25
|
+
*
|
|
26
|
+
* <ApiForm
|
|
27
|
+
* inputs={ROOM_INPUTS}
|
|
28
|
+
* loadValue={signal => api.get<Room>(ROOMS.details, { params: { id }, signal, toast: false }).then(unwrapApiResult)}
|
|
29
|
+
* api={ROOMS.update}
|
|
30
|
+
* onSuccess={room => table.updateRow(room)}
|
|
31
|
+
* onCancel={close}
|
|
32
|
+
* />
|
|
33
|
+
*/
|
|
34
|
+
export function ApiForm(props) {
|
|
35
|
+
const { api: _api, transform: _transform, readResponse: _readResponse, onSuccess: _onSuccess, onError: _onError, successMessage: _successMessage, loadValue, ...rest } = props;
|
|
36
|
+
const store = useFormStore(props.form);
|
|
37
|
+
const latest = useRef(props);
|
|
38
|
+
latest.current = props;
|
|
39
|
+
const answerRef = useRef(null);
|
|
40
|
+
const [load, setLoad] = useState(() => (loadValue ? { state: "loading" } : { state: "done" }));
|
|
41
|
+
const [attempt, setAttempt] = useState(0);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const loader = latest.current.loadValue;
|
|
44
|
+
if (!loader)
|
|
45
|
+
return;
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
setLoad(current => (current.state === "loading" ? current : { state: "loading" }));
|
|
48
|
+
loader(controller.signal).then(values => {
|
|
49
|
+
if (controller.signal.aborted)
|
|
50
|
+
return;
|
|
51
|
+
store.load(values ?? {});
|
|
52
|
+
setLoad({ state: "done" });
|
|
53
|
+
}, error => {
|
|
54
|
+
if (controller.signal.aborted || isAbortError(error))
|
|
55
|
+
return;
|
|
56
|
+
setLoad({ state: "failed", message: errorMessage(error, "The server did not answer.") });
|
|
57
|
+
});
|
|
58
|
+
return () => controller.abort();
|
|
59
|
+
}, [store, attempt]);
|
|
60
|
+
async function send(values, context) {
|
|
61
|
+
const { api, transform, readResponse = readApiResponse } = latest.current;
|
|
62
|
+
answerRef.current = null;
|
|
63
|
+
const body = transform ? await transform(values, context) : values;
|
|
64
|
+
let answer;
|
|
65
|
+
if (typeof api === "function")
|
|
66
|
+
answer = await toApiResult(await api(body, context), readResponse);
|
|
67
|
+
else {
|
|
68
|
+
// The form shows its own messages, so the call shows no toasts.
|
|
69
|
+
const options = isApiEndpoint(api) ? { body, signal: context.signal, read: readResponse } : apiRequestOptions(api, body, { signal: context.signal, read: readResponse });
|
|
70
|
+
answer = await browserApi.send(isApiEndpoint(api) ? api : api.url, { ...options, toast: false });
|
|
71
|
+
if (answer.aborted)
|
|
72
|
+
throw abortError();
|
|
73
|
+
}
|
|
74
|
+
if (answer.ok) {
|
|
75
|
+
answerRef.current = answer;
|
|
76
|
+
return { ok: true };
|
|
77
|
+
}
|
|
78
|
+
latest.current.onError?.(answer, values);
|
|
79
|
+
const names = store.inputs.filter(params => !params.hidden).map(params => params.name);
|
|
80
|
+
const { fields, others } = matchFieldErrors(answer.errors, names);
|
|
81
|
+
// A message the server put on inputs (Nexus `inputName`) shows there, not twice.
|
|
82
|
+
const onInputs = Boolean(answer.message) && Object.values(fields).includes(answer.message);
|
|
83
|
+
const message = [onInputs ? undefined : answer.message, ...others].filter(Boolean).join(" ");
|
|
84
|
+
return { ok: false, errors: fields, message: message || (Object.keys(fields).length ? null : apiStatusMessage(answer.status)) };
|
|
85
|
+
}
|
|
86
|
+
function saved(values) {
|
|
87
|
+
const answer = answerRef.current;
|
|
88
|
+
const { successMessage, onSuccess } = latest.current;
|
|
89
|
+
const { api } = latest.current;
|
|
90
|
+
const endpoint = isApiEndpoint(api) ? api : undefined;
|
|
91
|
+
if (successMessage !== false)
|
|
92
|
+
toast.success(successMessage ?? answer?.message ?? messages.success(endpoint?.action ?? "save", endpoint?.subject));
|
|
93
|
+
onSuccess?.(answer?.result, values, store);
|
|
94
|
+
}
|
|
95
|
+
if (load.state === "failed") {
|
|
96
|
+
return (_jsxs("div", { className: cx("nx-form-load-failed", rest.className), style: rest.style, role: "alert", children: [_jsx(InlineIcon, { icon: iconAlertCircle }), _jsxs("p", { children: [_jsx("strong", { children: "Could not load the values." }), " ", load.message] }), _jsxs("div", { className: "nx-form-load-failed__actions", children: [rest.onCancel && (_jsx(Button, { size: "sm", onClick: rest.onCancel, children: rest.cancelLabel ?? "Cancel" })), _jsx(Button, { size: "sm", icon: _jsx(InlineIcon, { icon: iconRefresh }), onClick: () => setAttempt(count => count + 1), children: "Retry" })] })] }));
|
|
97
|
+
}
|
|
98
|
+
return _jsx(SubmitForm, { ...rest, form: store, loading: Boolean(rest.loading) || load.state === "loading", onSubmit: send, onSaved: saved });
|
|
99
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CrudProps } from "../../Interfaces/CrudInterfaces.ts";
|
|
2
|
+
import type { FormValues } from "../../Interfaces/FormInterfaces.ts";
|
|
3
|
+
/**
|
|
4
|
+
* A CRUD page in one component: a data table of a controller's records with its lists, Add and Edit popups, details,
|
|
5
|
+
* and actions on one record or many, each shown only when its endpoint exists and the user may call it.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const GUESTS = MODULES.controller(NexusModule.Reservation, "Guest", "guest");
|
|
9
|
+
*
|
|
10
|
+
* <Crud<Guest>
|
|
11
|
+
* api={GUESTS}
|
|
12
|
+
* columns={[{ key: "fullName", label: "Name" }, { key: "email", label: "Email" }, { key: "visits", label: "Visits", format: "number" }]}
|
|
13
|
+
* inputs={GUEST_INPUTS}
|
|
14
|
+
* paging="server"
|
|
15
|
+
* />
|
|
16
|
+
*/
|
|
17
|
+
export declare function Crud<TRow extends FormValues = FormValues>(props: CrudProps<TRow>): import("react").JSX.Element;
|