nexus-shared 2.0.0 → 3.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.
- package/CHANGELOG.md +446 -0
- package/LICENSE +21 -21
- package/README.md +1084 -903
- package/dist/Client.Index.d.ts +14 -0
- package/dist/Client.Index.js +14 -0
- package/dist/Components/Chats/Chat.d.ts +28 -0
- package/dist/Components/Chats/Chat.js +19 -0
- package/dist/Components/Chats/ChatButton.d.ts +26 -0
- package/dist/Components/Chats/ChatButton.js +41 -0
- package/dist/Components/Chats/ChatComposer.d.ts +41 -0
- package/dist/Components/Chats/ChatComposer.js +177 -0
- package/dist/Components/Chats/ChatConversations.d.ts +35 -0
- package/dist/Components/Chats/ChatConversations.js +38 -0
- package/dist/Components/Chats/ChatPanel.d.ts +66 -0
- package/dist/Components/Chats/ChatPanel.js +94 -0
- package/dist/Components/Chats/ChatParts.d.ts +87 -0
- package/dist/Components/Chats/ChatParts.js +96 -0
- package/dist/Components/Chats/ChatThread.d.ts +22 -0
- package/dist/Components/Chats/ChatThread.js +96 -0
- package/dist/Components/Documents/Button.d.ts +6 -1
- package/dist/Components/Documents/Button.js +2 -2
- package/dist/Components/Documents/Menu.js +44 -28
- package/dist/Components/Documents/SplitButton.d.ts +3 -1
- package/dist/Components/Documents/SplitButton.js +12 -8
- package/dist/Components/Documents/TabButtons.d.ts +29 -8
- package/dist/Components/Documents/TabButtons.js +24 -5
- package/dist/Components/Forms/ApiForm.d.ts +6 -4
- package/dist/Components/Forms/ApiForm.js +15 -14
- package/dist/Components/Forms/Crud.js +342 -71
- package/dist/Components/Forms/CrudParts.d.ts +5 -1
- package/dist/Components/Forms/CrudParts.js +37 -5
- package/dist/Components/Forms/ExcelImport.d.ts +58 -0
- package/dist/Components/Forms/ExcelImport.js +319 -0
- package/dist/Components/Forms/Form.js +9 -13
- package/dist/Components/Forms/SubmitForm.d.ts +11 -2
- package/dist/Components/Forms/SubmitForm.js +23 -12
- package/dist/Components/Inputs/DateTimePicker.js +2 -1
- package/dist/Components/Inputs/DropdownParts.d.ts +1 -1
- package/dist/Components/Inputs/DropdownParts.js +4 -6
- package/dist/Components/Inputs/GroupForm.js +4 -2
- package/dist/Components/Inputs/InputRenderer.d.ts +2 -0
- package/dist/Components/Inputs/InputRenderer.js +1 -1
- package/dist/Components/Inputs/NumberBox.js +24 -2
- package/dist/Components/Inputs/ReadOnlyNotice.js +2 -1
- package/dist/Components/Inputs/RowsInput.d.ts +12 -1
- package/dist/Components/Inputs/RowsInput.js +55 -6
- package/dist/Components/Inputs/Slider.js +4 -1
- package/dist/Components/Inputs/TabularForm.d.ts +7 -3
- package/dist/Components/Inputs/TabularForm.js +117 -20
- package/dist/Components/Inputs/TimePanel.js +4 -3
- package/dist/Components/Inputs/TimePicker.js +2 -1
- package/dist/Components/Layouts/Popup.js +1 -4
- package/dist/Components/Layouts/ThemePicker.d.ts +20 -3
- package/dist/Components/Layouts/ThemePicker.js +77 -6
- package/dist/Components/Layouts/ThemeScript.d.ts +4 -2
- package/dist/Components/Layouts/ThemeScript.js +4 -2
- package/dist/Components/Layouts/ThemeSwitcher.d.ts +6 -3
- package/dist/Components/Layouts/ThemeSwitcher.js +39 -18
- package/dist/Components/Viewers/Avatar.d.ts +21 -0
- package/dist/Components/Viewers/Avatar.js +21 -0
- package/dist/Components/Viewers/DataTable.js +262 -60
- package/dist/Components/Viewers/DataTableColumns.d.ts +32 -0
- package/dist/Components/Viewers/DataTableColumns.js +200 -0
- package/dist/Components/Viewers/DataTableFilters.d.ts +21 -0
- package/dist/Components/Viewers/DataTableFilters.js +68 -0
- package/dist/Components/Viewers/DataTableParts.d.ts +8 -4
- package/dist/Components/Viewers/DataTableParts.js +24 -12
- package/dist/Helpers/AnimationHelpers.d.ts +11 -0
- package/dist/Helpers/AnimationHelpers.js +20 -2
- package/dist/Helpers/ApiClient.d.ts +22 -0
- package/dist/Helpers/ApiClient.js +4 -1
- package/dist/Helpers/ApiFormHelpers.d.ts +27 -4
- package/dist/Helpers/ApiFormHelpers.js +73 -9
- package/dist/Helpers/ApiModules.d.ts +0 -4
- package/dist/Helpers/ApiModules.js +1 -0
- package/dist/Helpers/ApiResponses.d.ts +14 -4
- package/dist/Helpers/ApiResponses.js +116 -25
- package/dist/Helpers/ApiRoutes.d.ts +0 -10
- package/dist/Helpers/ApiRoutes.js +2 -10
- package/dist/Helpers/ChatBackend.d.ts +134 -0
- package/dist/Helpers/ChatBackend.js +332 -0
- package/dist/Helpers/ChatHelpers.d.ts +189 -0
- package/dist/Helpers/ChatHelpers.js +486 -0
- package/dist/Helpers/ChatHooks.d.ts +81 -0
- package/dist/Helpers/ChatHooks.js +175 -0
- package/dist/Helpers/ChatStore.d.ts +132 -0
- package/dist/Helpers/ChatStore.js +394 -0
- package/dist/Helpers/ChatSummaries.d.ts +39 -0
- package/dist/Helpers/ChatSummaries.js +118 -0
- package/dist/Helpers/CrudBackend.d.ts +54 -9
- package/dist/Helpers/CrudBackend.js +103 -6
- package/dist/Helpers/CrudHelpers.d.ts +39 -4
- package/dist/Helpers/CrudHelpers.js +76 -11
- package/dist/Helpers/ExcelBackend.d.ts +106 -0
- package/dist/Helpers/ExcelBackend.js +296 -0
- package/dist/Helpers/ExcelHelpers.d.ts +126 -0
- package/dist/Helpers/ExcelHelpers.js +344 -0
- package/dist/Helpers/FormStore.d.ts +11 -5
- package/dist/Helpers/FormStore.js +44 -6
- package/dist/Helpers/MessageBuilder.js +1 -2
- package/dist/Helpers/NumberHelpers.d.ts +7 -1
- package/dist/Helpers/NumberHelpers.js +41 -3
- package/dist/Helpers/PopoverHelpers.d.ts +46 -9
- package/dist/Helpers/PopoverHelpers.js +163 -24
- package/dist/Helpers/PopoverHooks.d.ts +24 -0
- package/dist/Helpers/PopoverHooks.js +62 -0
- package/dist/Helpers/RowsStore.d.ts +14 -3
- package/dist/Helpers/RowsStore.js +37 -3
- package/dist/Helpers/TableColumns.d.ts +7 -0
- package/dist/Helpers/TableColumns.js +41 -13
- package/dist/Helpers/TableExport.js +12 -12
- package/dist/Helpers/TableFilters.d.ts +25 -0
- package/dist/Helpers/TableFilters.js +162 -0
- package/dist/Helpers/TableHelpers.d.ts +15 -4
- package/dist/Helpers/TableHelpers.js +28 -4
- package/dist/Helpers/TableStore.d.ts +11 -3
- package/dist/Helpers/TableStore.js +16 -5
- package/dist/Helpers/ThemeHelpers.d.ts +21 -4
- package/dist/Helpers/ThemeHelpers.js +46 -9
- package/dist/Helpers/UserPreferences.d.ts +14 -0
- package/dist/Helpers/UserPreferences.js +99 -0
- package/dist/Interfaces/ApiInterfaces.d.ts +29 -4
- package/dist/Interfaces/ApiInterfaces.js +16 -10
- package/dist/Interfaces/ChatInterfaces.d.ts +240 -0
- package/dist/Interfaces/ChatInterfaces.js +1 -0
- package/dist/Interfaces/CrudInterfaces.d.ts +92 -12
- package/dist/Interfaces/ExcelInterfaces.d.ts +227 -0
- package/dist/Interfaces/ExcelInterfaces.js +45 -0
- package/dist/Interfaces/FormInterfaces.d.ts +85 -7
- package/dist/Interfaces/MessageInterfaces.d.ts +1 -1
- package/dist/Interfaces/PreferenceInterfaces.d.ts +43 -0
- package/dist/Interfaces/PreferenceInterfaces.js +28 -0
- package/dist/Interfaces/TableInterfaces.d.ts +116 -9
- package/dist/Interfaces/ThemeInterfaces.d.ts +25 -0
- package/dist/Interfaces/ThemeInterfaces.js +15 -0
- package/dist/Services/BrowserApi.d.ts +18 -1
- package/dist/Services/BrowserApi.js +18 -0
- package/dist/Services/ChatApi.d.ts +37 -0
- package/dist/Services/ChatApi.js +98 -0
- package/dist/Services/ExcelApi.d.ts +48 -0
- package/dist/Services/ExcelApi.js +196 -0
- package/dist/Services/ServerApi.d.ts +6 -1
- package/dist/Services/ServerApi.js +3 -0
- package/dist/Services/ThemeService.d.ts +18 -2
- package/dist/Services/ThemeService.js +80 -16
- package/dist/Shared.Index.d.ts +10 -0
- package/dist/Shared.Index.js +10 -0
- package/package.json +85 -82
- package/src/Styles/Nexus.Avatar.css +79 -0
- package/src/Styles/Nexus.Base.css +117 -70
- package/src/Styles/Nexus.Button.css +180 -151
- package/src/Styles/Nexus.Calendar.css +273 -275
- package/src/Styles/Nexus.Chat.css +1443 -0
- package/src/Styles/Nexus.Choice.css +343 -345
- package/src/Styles/Nexus.Crud.css +168 -84
- package/src/Styles/Nexus.DateTime.css +81 -81
- package/src/Styles/Nexus.Dropdown.css +725 -774
- package/src/Styles/Nexus.Editor.css +295 -297
- package/src/Styles/Nexus.Excel.css +389 -0
- package/src/Styles/Nexus.Form.css +279 -298
- package/src/Styles/Nexus.Index.css +37 -34
- package/src/Styles/Nexus.Input.css +602 -637
- package/src/Styles/Nexus.Markdown.css +237 -265
- package/src/Styles/Nexus.Menu.css +237 -249
- package/src/Styles/Nexus.Popup.css +724 -731
- package/src/Styles/Nexus.RichText.css +2 -32
- package/src/Styles/Nexus.Rows.css +637 -566
- package/src/Styles/Nexus.Slider.css +276 -278
- package/src/Styles/Nexus.Tab.Buttons.css +432 -364
- package/src/Styles/Nexus.Table.css +1386 -1014
- package/src/Styles/Nexus.Theme.Picker.css +286 -159
- package/src/Styles/Nexus.Themes.css +194 -194
- package/src/Styles/Nexus.Time.css +144 -147
- package/src/Styles/Nexus.Toast.css +263 -265
- package/src/Styles/Nexus.Tokens.css +89 -72
- package/src/Styles/Nexus.Tree.css +431 -435
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { iconChevronDown, InlineIcon } from "nexus-icons";
|
|
4
4
|
import { useId, useLayoutEffect, useRef, useState } from "react";
|
|
5
5
|
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
6
|
-
import { followAnchor, isPopoverOpen, placeUnderAnchor } from "../../Helpers/PopoverHelpers.js";
|
|
6
|
+
import { followAnchor, isPopoverOpen, placeUnderAnchor, showPanel } from "../../Helpers/PopoverHelpers.js";
|
|
7
7
|
import { Button } from "./Button.js";
|
|
8
8
|
import { MenuPanel } from "./Menu.js";
|
|
9
9
|
const STORAGE_PREFIX = "nx-split:";
|
|
@@ -37,7 +37,7 @@ function saveChoice(key, value) {
|
|
|
37
37
|
* { value: "draft", label: "Create draft pull request", description: "Cannot be merged until marked ready" },
|
|
38
38
|
* ]} />
|
|
39
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, }) {
|
|
40
|
+
export function SplitButton({ options, onAction, defaultValue, value, onValueChange, storageKey, runOnSelect = false, fixed = false, variant = "primary", size = "md", loading = false, disabled = false, block = false, type = "button", form, name, menuLabel = "More actions", align = "end", className, style, }) {
|
|
41
41
|
const id = useId();
|
|
42
42
|
const panelId = `${id}-menu`;
|
|
43
43
|
const toggleRef = useRef(null);
|
|
@@ -58,15 +58,17 @@ export function SplitButton({ options, onAction, defaultValue, value, onValueCha
|
|
|
58
58
|
return null;
|
|
59
59
|
function place() {
|
|
60
60
|
const panel = panelRef.current;
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
const toggle = toggleRef.current;
|
|
62
|
+
const anchor = toggle?.parentElement;
|
|
63
|
+
// The menu lines up with the whole button, and its arrow points at the half that opened it.
|
|
64
|
+
if (panel && toggle && anchor)
|
|
65
|
+
placeUnderAnchor(panel, anchor.getBoundingClientRect(), { align, arrowAt: toggle.getBoundingClientRect() });
|
|
64
66
|
}
|
|
65
67
|
function show(focusFirst) {
|
|
66
68
|
const panel = panelRef.current;
|
|
67
69
|
if (!panel || typeof panel.showPopover !== "function" || isPopoverOpen(panel))
|
|
68
70
|
return;
|
|
69
|
-
panel
|
|
71
|
+
showPanel(panel);
|
|
70
72
|
place();
|
|
71
73
|
if (focusFirst)
|
|
72
74
|
panel.querySelector('.nx-menu__item:not([aria-disabled="true"])')?.focus();
|
|
@@ -90,6 +92,8 @@ export function SplitButton({ options, onAction, defaultValue, value, onValueCha
|
|
|
90
92
|
const option = usable(next);
|
|
91
93
|
if (!option || !current)
|
|
92
94
|
return;
|
|
95
|
+
if (fixed)
|
|
96
|
+
return onAction(next, option);
|
|
93
97
|
if (value === undefined)
|
|
94
98
|
setChosen(next);
|
|
95
99
|
saveChoice(storageKey, next);
|
|
@@ -98,7 +102,7 @@ export function SplitButton({ options, onAction, defaultValue, value, onValueCha
|
|
|
98
102
|
if (runOnSelect)
|
|
99
103
|
onAction(next, option);
|
|
100
104
|
}
|
|
101
|
-
const items = options.map(option => ({
|
|
105
|
+
const items = (fixed ? options.filter(option => option.value !== current.value) : options).map(option => ({
|
|
102
106
|
value: option.value,
|
|
103
107
|
label: option.label,
|
|
104
108
|
description: option.description,
|
|
@@ -123,5 +127,5 @@ export function SplitButton({ options, onAction, defaultValue, value, onValueCha
|
|
|
123
127
|
event.preventDefault();
|
|
124
128
|
show(true);
|
|
125
129
|
}
|
|
126
|
-
} }), _jsx(MenuPanel, { ref: panelRef, id: panelId, kind: "radio", size: size, items: items, onSelect: pick, "aria-label": menuLabel, onToggle: onToggle, className: "nx-split__menu" })] }));
|
|
130
|
+
} }), _jsx(MenuPanel, { ref: panelRef, id: panelId, kind: fixed ? "menu" : "radio", size: size, items: items, onSelect: pick, "aria-label": menuLabel, onToggle: onToggle, className: "nx-split__menu" })] }));
|
|
127
131
|
}
|
|
@@ -13,15 +13,15 @@ export interface TabButtonOption<T extends string = string> {
|
|
|
13
13
|
/** Tooltip. With `labels="selected"`, a text label is the default tooltip. */
|
|
14
14
|
title?: string;
|
|
15
15
|
}
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* `all`: every option shows its label. `selected`: options show only their icon, and the selected one adds its label.
|
|
18
|
+
* `auto`: every label while its container (a data table) is at least 900px wide, `selected` below that.
|
|
19
|
+
*/
|
|
20
|
+
export type TabButtonLabels = "all" | "selected" | "auto";
|
|
18
21
|
/** `nav`: a horizontal track with a sliding indicator. `list`: a vertical list of rows, for settings pages and side panels. */
|
|
19
22
|
export type TabButtonDesign = "nav" | "list";
|
|
20
23
|
interface TabButtonsBaseProps<T extends string> {
|
|
21
24
|
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
25
|
/** Default `nav`. `list` stacks the options as full-width rows with a bar at the selected one. */
|
|
26
26
|
design?: TabButtonDesign;
|
|
27
27
|
/** Default `md`. */
|
|
@@ -35,7 +35,7 @@ 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
|
-
/** Radio group name, for forms. Default: a unique id. */
|
|
38
|
+
/** Radio (or checkbox) group name, for forms. Default: a unique id. */
|
|
39
39
|
name?: string;
|
|
40
40
|
/**
|
|
41
41
|
* Id of the form the choice belongs to, as the HTML `form` attribute. An id that matches no form keeps the choice out
|
|
@@ -44,8 +44,26 @@ interface TabButtonsBaseProps<T extends string> {
|
|
|
44
44
|
form?: string;
|
|
45
45
|
className?: string;
|
|
46
46
|
}
|
|
47
|
+
/** One option at a time: radio buttons, and an indicator that slides to the selection. */
|
|
48
|
+
interface TabButtonsOneProps<T extends string> {
|
|
49
|
+
multiple?: false;
|
|
50
|
+
/** The selected option. A value that matches no option leaves every option unselected. */
|
|
51
|
+
value: T;
|
|
52
|
+
onChange: (value: T) => void;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Several options at once: checkboxes, each chosen option raised on its own, and the options wrap onto more rows when
|
|
56
|
+
* they do not fit, so a picker of 26 letters or 33 symbols stays one control on a phone.
|
|
57
|
+
*/
|
|
58
|
+
interface TabButtonsManyProps<T extends string> {
|
|
59
|
+
multiple: true;
|
|
60
|
+
/** The chosen options. Values that match no option are ignored, and never handed back. */
|
|
61
|
+
value: readonly T[];
|
|
62
|
+
/** The chosen values after each change, in option order. */
|
|
63
|
+
onChange: (values: T[]) => void;
|
|
64
|
+
}
|
|
47
65
|
/** 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> & ({
|
|
66
|
+
export type TabButtonsProps<T extends string = string> = TabButtonsBaseProps<T> & (TabButtonsOneProps<T> | TabButtonsManyProps<T>) & ({
|
|
49
67
|
"aria-label": string;
|
|
50
68
|
"aria-labelledby"?: never;
|
|
51
69
|
} | {
|
|
@@ -57,6 +75,9 @@ export type TabButtonsProps<T extends string = string> = TabButtonsBaseProps<T>
|
|
|
57
75
|
* The `nav` design slides an indicator to the selection; the `list` design stacks the options as rows, each with an
|
|
58
76
|
* icon, a label, a description, and a badge, for settings pages and side panels. Built on native radio buttons, so
|
|
59
77
|
* arrow keys move the selection and screen readers announce it. To switch between page panels, use tabs with panels instead.
|
|
78
|
+
*
|
|
79
|
+
* `multiple` picks several at once, such as the weekdays a rule runs on: native checkboxes (Tab moves between them,
|
|
80
|
+
* Space toggles one), every chosen option raised as the single choice's indicator is, and the options wrap.
|
|
60
81
|
*/
|
|
61
|
-
export declare function TabButtons<T extends string>(
|
|
82
|
+
export declare function TabButtons<T extends string>(props: TabButtonsProps<T>): import("react").JSX.Element;
|
|
62
83
|
export {};
|
|
@@ -7,13 +7,32 @@ import { cx } from "../../Helpers/ClassHelpers.js";
|
|
|
7
7
|
* The `nav` design slides an indicator to the selection; the `list` design stacks the options as rows, each with an
|
|
8
8
|
* icon, a label, a description, and a badge, for settings pages and side panels. Built on native radio buttons, so
|
|
9
9
|
* arrow keys move the selection and screen readers announce it. To switch between page panels, use tabs with panels instead.
|
|
10
|
+
*
|
|
11
|
+
* `multiple` picks several at once, such as the weekdays a rule runs on: native checkboxes (Tab moves between them,
|
|
12
|
+
* Space toggles one), every chosen option raised as the single choice's indicator is, and the options wrap.
|
|
10
13
|
*/
|
|
11
|
-
export function TabButtons(
|
|
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;
|
|
12
16
|
const generatedName = useId();
|
|
13
17
|
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
18
|
const list = design === "list";
|
|
17
|
-
const labelsSelected = !list && labels
|
|
18
|
-
|
|
19
|
+
const labelsSelected = !list && labels !== "all";
|
|
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);
|
|
23
|
+
const style = { "--nx-tabs-count": Math.max(options.length, 1), "--nx-tabs-index": Math.max(index, 0) };
|
|
24
|
+
const isChecked = (option) => (chosen ? chosen.has(option.value) : option.value === props.value);
|
|
25
|
+
const toggle = (option, checked) => {
|
|
26
|
+
if (!props.multiple) {
|
|
27
|
+
props.onChange(option.value);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const next = new Set(props.value);
|
|
31
|
+
if (checked)
|
|
32
|
+
next.add(option.value);
|
|
33
|
+
else
|
|
34
|
+
next.delete(option.value);
|
|
35
|
+
props.onChange(options.filter(one => next.has(one.value)).map(one => one.value));
|
|
36
|
+
};
|
|
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)))] }));
|
|
19
38
|
}
|
|
@@ -2,10 +2,12 @@ import type { ApiFormProps, FormValues } from "../../Interfaces/FormInterfaces.t
|
|
|
2
2
|
/**
|
|
3
3
|
* A `SubmitForm` that sends its values to an API: an endpoint (`ROOMS.update`, or `{ module, path, method }` in place),
|
|
4
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* a function that sends them with a client of the app's own. The answer is read as Nexus API responses
|
|
6
|
+
* (`{ isSuccess, errorMessages: [{ message, inputName }], result }`, or one message at the top level), ASP.NET Core problem
|
|
7
|
+
* details, or any JSON by its status. On success a toast says so and the values become the form's own; on failure the
|
|
8
|
+
* server's own words show - every message it sent, on the inputs they name and the rest under the form, with our message
|
|
9
|
+
* by status only for an answer that explains nothing - and the first input with a message takes focus. `loadValue` loads
|
|
10
|
+
* the record to edit: the inputs show disabled until it lands, and a failure offers Retry.
|
|
9
11
|
*
|
|
10
12
|
* @example
|
|
11
13
|
* const ROOMS = apiController(NexusModule.Reservation, "Room", "room");
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { iconAlertCircle, iconRefresh, InlineIcon } from "nexus-icons";
|
|
4
4
|
import { useEffect, useRef, useState } from "react";
|
|
5
|
-
import { abortError, apiRequestOptions,
|
|
6
|
-
import {
|
|
5
|
+
import { abortError, apiRequestOptions, formErrorsFromResult, toApiResult } from "../../Helpers/ApiFormHelpers.js";
|
|
6
|
+
import { errorMessage, isAbortError, readApiResponse } from "../../Helpers/ApiResponses.js";
|
|
7
7
|
import { isApiEndpoint } from "../../Helpers/ApiRoutes.js";
|
|
8
8
|
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
9
9
|
import { messages } from "../../Helpers/MessageBuilder.js";
|
|
10
|
-
import { api as browserApi } from "../../Services/BrowserApi.js";
|
|
10
|
+
import { api as browserApi, getApiReader } from "../../Services/BrowserApi.js";
|
|
11
11
|
import { Button } from "../Documents/Button.js";
|
|
12
12
|
import { toast } from "../Layouts/Toaster.js";
|
|
13
13
|
import { useFormStore } from "./Form.js";
|
|
@@ -15,10 +15,12 @@ import { SubmitForm } from "./SubmitForm.js";
|
|
|
15
15
|
/**
|
|
16
16
|
* A `SubmitForm` that sends its values to an API: an endpoint (`ROOMS.update`, or `{ module, path, method }` in place),
|
|
17
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
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* a function that sends them with a client of the app's own. The answer is read as Nexus API responses
|
|
19
|
+
* (`{ isSuccess, errorMessages: [{ message, inputName }], result }`, or one message at the top level), ASP.NET Core problem
|
|
20
|
+
* details, or any JSON by its status. On success a toast says so and the values become the form's own; on failure the
|
|
21
|
+
* server's own words show - every message it sent, on the inputs they name and the rest under the form, with our message
|
|
22
|
+
* by status only for an answer that explains nothing - and the first input with a message takes focus. `loadValue` loads
|
|
23
|
+
* the record to edit: the inputs show disabled until it lands, and a failure offers Retry.
|
|
22
24
|
*
|
|
23
25
|
* @example
|
|
24
26
|
* const ROOMS = apiController(NexusModule.Reservation, "Room", "room");
|
|
@@ -58,12 +60,13 @@ export function ApiForm(props) {
|
|
|
58
60
|
return () => controller.abort();
|
|
59
61
|
}, [store, attempt]);
|
|
60
62
|
async function send(values, context) {
|
|
61
|
-
|
|
63
|
+
// No `readResponse` means the client's own reader: the app's (`configureApi({ read })`), else the built-in one.
|
|
64
|
+
const { api, transform, readResponse } = latest.current;
|
|
62
65
|
answerRef.current = null;
|
|
63
66
|
const body = transform ? await transform(values, context) : values;
|
|
64
67
|
let answer;
|
|
65
68
|
if (typeof api === "function")
|
|
66
|
-
answer = await toApiResult(await api(body, context), readResponse);
|
|
69
|
+
answer = await toApiResult(await api(body, context), readResponse ?? getApiReader() ?? readApiResponse);
|
|
67
70
|
else {
|
|
68
71
|
// The form shows its own messages, so the call shows no toasts.
|
|
69
72
|
const options = isApiEndpoint(api) ? { body, signal: context.signal, read: readResponse } : apiRequestOptions(api, body, { signal: context.signal, read: readResponse });
|
|
@@ -76,12 +79,10 @@ export function ApiForm(props) {
|
|
|
76
79
|
return { ok: true };
|
|
77
80
|
}
|
|
78
81
|
latest.current.onError?.(answer, values);
|
|
82
|
+
// Every message the server sent: the ones it put on inputs (Nexus `inputName`) show there, the rest under the form.
|
|
79
83
|
const names = store.inputs.filter(params => !params.hidden).map(params => params.name);
|
|
80
|
-
const {
|
|
81
|
-
|
|
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)) };
|
|
84
|
+
const { errors, message } = formErrorsFromResult(answer, names);
|
|
85
|
+
return { ok: false, errors, message };
|
|
85
86
|
}
|
|
86
87
|
function saved(values) {
|
|
87
88
|
const answer = answerRef.current;
|