nexus-shared 3.0.0 → 3.2.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 (162) hide show
  1. package/CHANGELOG.md +442 -0
  2. package/README.md +1123 -1000
  3. package/dist/Client.Index.d.ts +6 -0
  4. package/dist/Client.Index.js +6 -0
  5. package/dist/Components/Chats/ChatParts.d.ts +2 -2
  6. package/dist/Components/Chats/ChatParts.js +3 -7
  7. package/dist/Components/Chats/ChatThread.js +3 -3
  8. package/dist/Components/Documents/Button.d.ts +6 -1
  9. package/dist/Components/Documents/Button.js +2 -2
  10. package/dist/Components/Documents/Menu.js +21 -9
  11. package/dist/Components/Documents/SplitButton.d.ts +3 -1
  12. package/dist/Components/Documents/SplitButton.js +7 -5
  13. package/dist/Components/Documents/TabButtons.d.ts +5 -2
  14. package/dist/Components/Documents/TabButtons.js +2 -2
  15. package/dist/Components/Forms/ApiForm.d.ts +1 -1
  16. package/dist/Components/Forms/ApiForm.js +1 -1
  17. package/dist/Components/Forms/Crud.js +486 -206
  18. package/dist/Components/Forms/CrudHistory.d.ts +66 -0
  19. package/dist/Components/Forms/CrudHistory.js +140 -0
  20. package/dist/Components/Forms/CrudParts.d.ts +5 -1
  21. package/dist/Components/Forms/CrudParts.js +38 -5
  22. package/dist/Components/Forms/ExcelImport.d.ts +23 -7
  23. package/dist/Components/Forms/ExcelImport.js +189 -60
  24. package/dist/Components/Forms/Form.js +9 -14
  25. package/dist/Components/Forms/SubmitForm.d.ts +11 -2
  26. package/dist/Components/Forms/SubmitForm.js +23 -12
  27. package/dist/Components/Inputs/CodeBox.d.ts +69 -0
  28. package/dist/Components/Inputs/CodeBox.js +316 -0
  29. package/dist/Components/Inputs/DatePicker.js +4 -1
  30. package/dist/Components/Inputs/DateRangePicker.js +4 -1
  31. package/dist/Components/Inputs/DateTimePicker.js +4 -1
  32. package/dist/Components/Inputs/Dropdown.js +4 -1
  33. package/dist/Components/Inputs/DropdownParts.d.ts +1 -1
  34. package/dist/Components/Inputs/DropdownParts.js +4 -6
  35. package/dist/Components/Inputs/InputRenderer.js +3 -0
  36. package/dist/Components/Inputs/NumberBox.js +26 -2
  37. package/dist/Components/Inputs/RowsInput.js +2 -2
  38. package/dist/Components/Inputs/Slider.js +4 -1
  39. package/dist/Components/Inputs/TabularForm.js +42 -10
  40. package/dist/Components/Inputs/TextArea.js +4 -1
  41. package/dist/Components/Inputs/TextBox.js +4 -1
  42. package/dist/Components/Inputs/TimePanel.js +4 -3
  43. package/dist/Components/Inputs/TimePicker.js +4 -1
  44. package/dist/Components/Inputs/TreeView.d.ts +55 -5
  45. package/dist/Components/Inputs/TreeView.js +372 -23
  46. package/dist/Components/Layouts/Popup.js +1 -4
  47. package/dist/Components/Layouts/ThemePicker.d.ts +21 -3
  48. package/dist/Components/Layouts/ThemePicker.js +89 -6
  49. package/dist/Components/Layouts/ThemeScript.d.ts +4 -2
  50. package/dist/Components/Layouts/ThemeScript.js +4 -2
  51. package/dist/Components/Layouts/ThemeSwitcher.d.ts +6 -3
  52. package/dist/Components/Layouts/ThemeSwitcher.js +9 -8
  53. package/dist/Components/Viewers/Avatar.d.ts +21 -0
  54. package/dist/Components/Viewers/Avatar.js +21 -0
  55. package/dist/Components/Viewers/DataTable.js +252 -62
  56. package/dist/Components/Viewers/DataTableColumns.d.ts +7 -3
  57. package/dist/Components/Viewers/DataTableColumns.js +25 -68
  58. package/dist/Components/Viewers/DataTableFilters.d.ts +21 -0
  59. package/dist/Components/Viewers/DataTableFilters.js +68 -0
  60. package/dist/Components/Viewers/DataTableParts.d.ts +28 -10
  61. package/dist/Components/Viewers/DataTableParts.js +42 -13
  62. package/dist/Helpers/AnimationHelpers.d.ts +11 -0
  63. package/dist/Helpers/AnimationHelpers.js +20 -2
  64. package/dist/Helpers/ApiModules.d.ts +3 -34
  65. package/dist/Helpers/ApiModules.js +3 -3
  66. package/dist/Helpers/ApiRoutes.d.ts +68 -26
  67. package/dist/Helpers/ApiRoutes.js +31 -27
  68. package/dist/Helpers/ArrayHelpers.d.ts +0 -0
  69. package/dist/Helpers/ArrayHelpers.js +2 -0
  70. package/dist/Helpers/CodeHelpers.d.ts +97 -0
  71. package/dist/Helpers/CodeHelpers.js +332 -0
  72. package/dist/Helpers/CrudBackend.d.ts +101 -8
  73. package/dist/Helpers/CrudBackend.js +172 -11
  74. package/dist/Helpers/CrudColumns.d.ts +51 -0
  75. package/dist/Helpers/CrudColumns.js +69 -0
  76. package/dist/Helpers/CrudHelpers.d.ts +50 -5
  77. package/dist/Helpers/CrudHelpers.js +109 -12
  78. package/dist/Helpers/ExcelBackend.d.ts +3 -3
  79. package/dist/Helpers/ExcelBackend.js +8 -2
  80. package/dist/Helpers/ExcelHelpers.d.ts +36 -8
  81. package/dist/Helpers/ExcelHelpers.js +55 -13
  82. package/dist/Helpers/FormStore.d.ts +9 -5
  83. package/dist/Helpers/FormStore.js +25 -5
  84. package/dist/Helpers/InputParamsHelpers.js +8 -0
  85. package/dist/Helpers/MessageBuilder.js +3 -0
  86. package/dist/Helpers/NexusHelpers.d.ts +0 -0
  87. package/dist/Helpers/NexusHelpers.js +1 -0
  88. package/dist/Helpers/NumberHelpers.d.ts +7 -1
  89. package/dist/Helpers/NumberHelpers.js +41 -3
  90. package/dist/Helpers/PopoverHelpers.d.ts +17 -5
  91. package/dist/Helpers/PopoverHelpers.js +52 -17
  92. package/dist/Helpers/PopoverHooks.d.ts +24 -0
  93. package/dist/Helpers/PopoverHooks.js +62 -0
  94. package/dist/Helpers/RequestCache.d.ts +35 -0
  95. package/dist/Helpers/RequestCache.js +113 -0
  96. package/dist/Helpers/RowsStore.d.ts +7 -2
  97. package/dist/Helpers/RowsStore.js +15 -3
  98. package/dist/Helpers/TableFilters.d.ts +25 -0
  99. package/dist/Helpers/TableFilters.js +162 -0
  100. package/dist/Helpers/TableHelpers.d.ts +7 -4
  101. package/dist/Helpers/TableHelpers.js +15 -6
  102. package/dist/Helpers/TableJoins.d.ts +70 -0
  103. package/dist/Helpers/TableJoins.js +250 -0
  104. package/dist/Helpers/TableStore.d.ts +16 -3
  105. package/dist/Helpers/TableStore.js +25 -5
  106. package/dist/Helpers/ThemeHelpers.d.ts +21 -4
  107. package/dist/Helpers/ThemeHelpers.js +46 -9
  108. package/dist/Helpers/TreeStore.d.ts +225 -11
  109. package/dist/Helpers/TreeStore.js +683 -90
  110. package/dist/Helpers/UserPreferences.d.ts +22 -0
  111. package/dist/Helpers/UserPreferences.js +116 -0
  112. package/dist/Interfaces/CrudInterfaces.d.ts +67 -10
  113. package/dist/Interfaces/ExcelInterfaces.d.ts +12 -0
  114. package/dist/Interfaces/FormInterfaces.d.ts +37 -6
  115. package/dist/Interfaces/InputInterfaces.d.ts +24 -0
  116. package/dist/Interfaces/MessageInterfaces.d.ts +1 -1
  117. package/dist/Interfaces/PreferenceInterfaces.d.ts +51 -0
  118. package/dist/Interfaces/PreferenceInterfaces.js +25 -0
  119. package/dist/Interfaces/TableInterfaces.d.ts +145 -16
  120. package/dist/Interfaces/ThemeInterfaces.d.ts +25 -0
  121. package/dist/Interfaces/ThemeInterfaces.js +15 -0
  122. package/dist/Services/ApiProxy.d.ts +36 -1
  123. package/dist/Services/ApiProxy.js +182 -52
  124. package/dist/Services/ApiProxyRules.d.ts +203 -0
  125. package/dist/Services/ApiProxyRules.js +426 -0
  126. package/dist/Services/ExcelApi.d.ts +3 -1
  127. package/dist/Services/ExcelApi.js +1 -1
  128. package/dist/Services/JoinSources.d.ts +48 -0
  129. package/dist/Services/JoinSources.js +74 -0
  130. package/dist/Services/ServerApi.d.ts +13 -2
  131. package/dist/Services/ServerApi.js +8 -3
  132. package/dist/Services/ThemeService.d.ts +18 -2
  133. package/dist/Services/ThemeService.js +80 -16
  134. package/dist/Shared.Index.d.ts +6 -0
  135. package/dist/Shared.Index.js +6 -0
  136. package/package.json +4 -2
  137. package/src/Styles/Nexus.Avatar.css +79 -0
  138. package/src/Styles/Nexus.Base.css +51 -4
  139. package/src/Styles/Nexus.Button.css +27 -0
  140. package/src/Styles/Nexus.Calendar.css +4 -6
  141. package/src/Styles/Nexus.Chat.css +5 -51
  142. package/src/Styles/Nexus.Choice.css +4 -6
  143. package/src/Styles/Nexus.Crud.css +16 -9
  144. package/src/Styles/Nexus.Dropdown.css +12 -61
  145. package/src/Styles/Nexus.Editor.css +4 -6
  146. package/src/Styles/Nexus.Excel.css +34 -15
  147. package/src/Styles/Nexus.Form.css +25 -67
  148. package/src/Styles/Nexus.Index.css +1 -0
  149. package/src/Styles/Nexus.Input.css +794 -637
  150. package/src/Styles/Nexus.Markdown.css +0 -28
  151. package/src/Styles/Nexus.Menu.css +8 -30
  152. package/src/Styles/Nexus.Popup.css +22 -29
  153. package/src/Styles/Nexus.RichText.css +2 -32
  154. package/src/Styles/Nexus.Rows.css +51 -10
  155. package/src/Styles/Nexus.Slider.css +3 -5
  156. package/src/Styles/Nexus.Tab.Buttons.css +20 -7
  157. package/src/Styles/Nexus.Table.css +1387 -1296
  158. package/src/Styles/Nexus.Theme.Picker.css +342 -232
  159. package/src/Styles/Nexus.Time.css +9 -12
  160. package/src/Styles/Nexus.Toast.css +6 -8
  161. package/src/Styles/Nexus.Tokens.css +17 -0
  162. package/src/Styles/Nexus.Tree.css +76 -11
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { iconMinus, iconSquare, iconSquares, iconX, InlineIcon } from "nexus-icons";
4
4
  import { useContext, useEffect, useId, useLayoutEffect, useRef, useState } from "react";
5
5
  import { flushSync } from "react-dom";
6
- import { afterAnimation } from "../../Helpers/AnimationHelpers.js";
6
+ import { afterAnimation, motionAllowed } from "../../Helpers/AnimationHelpers.js";
7
7
  import { cx } from "../../Helpers/ClassHelpers.js";
8
8
  import { cssVariablePosition, DRAG_CANCEL_SELECTOR, moveWithin, startDrag, viewportBounds } from "../../Helpers/DragHelpers.js";
9
9
  import { HostedPopupContext } from "./PopupContext.js";
@@ -12,9 +12,6 @@ const FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]):not([t
12
12
  // Where a minimized popup flies to: the newest bar of the dock, at the bottom right.
13
13
  const DOCK_EDGE = 16;
14
14
  const DOCK_BAR = { width: 300, height: 44 };
15
- function motionAllowed() {
16
- return !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
17
- }
18
15
  function popupPosition(dialog) {
19
16
  return cssVariablePosition(dialog, "--nx-popup-x", "--nx-popup-y");
20
17
  }
@@ -4,10 +4,28 @@ export interface ThemePickerProps {
4
4
  themes?: readonly ThemeOption[];
5
5
  /** Show the Light / Dark / System choice. Default `true`. */
6
6
  showMode?: boolean;
7
+ /** Show Compact / Cozy. Default `true`. */
8
+ showDensity?: boolean;
9
+ /** Show Animations: On / Off / System. Default `true`. */
10
+ showAnimations?: boolean;
11
+ /** Show the scale grid, a development tool. Default: in a development build only. */
12
+ showGrid?: boolean;
7
13
  className?: string;
8
14
  }
9
15
  /**
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.
16
+ * Two sections: the theme (Light / Dark / System and a card per theme with a live preview of its colors) and the
17
+ * appearance (the density, the animations and, in a development build, the scale grid). Each choice applies at once
18
+ * and is saved in the browser.
12
19
  */
13
- export declare function ThemePicker({ themes, showMode, className }: ThemePickerProps): import("react").JSX.Element;
20
+ export declare function ThemePicker({ themes, showMode, showDensity, showAnimations, showGrid, className }: ThemePickerProps): import("react").JSX.Element;
21
+ export interface PreferencesPickerProps {
22
+ className?: string;
23
+ }
24
+ /**
25
+ * The person's quick preferences, each applied at once and saved on this device: the interface language and the
26
+ * calendar; the date picker's language, the clock and the date format (the date settings every date component reads);
27
+ * the number scale and the thousands separator (every number box, slider and table number without a locale of its
28
+ * own); and the page size tables open with and whether a row click opens a record (every table, once chosen, over what
29
+ * the table names itself). `ThemeSwitcher` shows it under the theme.
30
+ */
31
+ export declare function PreferencesPicker({ className }: PreferencesPickerProps): import("react").JSX.Element;
@@ -1,22 +1,105 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { iconCheck, iconDeviceDesktop, iconMoon, iconSun, InlineIcon } from "nexus-icons";
3
+ import { iconArrowsMaximize, iconArrowsMinimize, iconCheck, iconCircleCheck, iconCircleX, iconDeviceDesktop, iconEyeOff, iconLayoutGrid, iconMoon, iconSun, iconTable, InlineIcon, } from "nexus-icons";
4
4
  import { useId } from "react";
5
5
  import { cx } from "../../Helpers/ClassHelpers.js";
6
+ import { formatDate } from "../../Helpers/DateHelpers.js";
7
+ import { setDatePreferences, useDatePreferences } from "../../Helpers/DatePreferences.js";
8
+ import { DEFAULT_PAGE_SIZE, DEFAULT_PAGE_SIZES } from "../../Helpers/TableHelpers.js";
9
+ import { isDevelopmentBuild } from "../../Helpers/ThemeHelpers.js";
10
+ import { setUserPreferences, useUserPreferences } from "../../Helpers/UserPreferences.js";
11
+ import { DATE_FORMATS, DEFAULT_TIME_FORMAT } from "../../Interfaces/DateInterfaces.js";
12
+ import { ENumberScales } from "../../Interfaces/PreferenceInterfaces.js";
6
13
  import { NEXUS_THEMES } from "../../Interfaces/ThemeInterfaces.js";
7
14
  import { useTheme } from "../../Services/ThemeService.js";
8
15
  import { TabButtons } from "../Documents/TabButtons.js";
16
+ import { Switch } from "../Inputs/CheckBox.js";
17
+ import { Dropdown } from "../Inputs/Dropdown.js";
18
+ import { TextBox } from "../Inputs/TextBox.js";
9
19
  const MODES = [
10
20
  { value: "light", label: "Light", icon: _jsx(InlineIcon, { icon: iconSun }) },
11
21
  { value: "dark", label: "Dark", icon: _jsx(InlineIcon, { icon: iconMoon }) },
12
22
  { value: "system", label: "System", icon: _jsx(InlineIcon, { icon: iconDeviceDesktop }), title: "Follow the operating system" },
13
23
  ];
24
+ const DENSITIES = [
25
+ { value: "compact", label: "Compact", icon: _jsx(InlineIcon, { icon: iconArrowsMinimize }) },
26
+ { value: "cozy", label: "Cozy", icon: _jsx(InlineIcon, { icon: iconArrowsMaximize }), title: "Taller controls and rows, for touch screens and large monitors" },
27
+ ];
28
+ const ANIMATIONS = [
29
+ { value: "on", label: "On", icon: _jsx(InlineIcon, { icon: iconCircleCheck }), title: "Animate, even when the operating system asks for less motion" },
30
+ { value: "off", label: "Off", icon: _jsx(InlineIcon, { icon: iconCircleX }), title: "Nothing moves" },
31
+ { value: "system", label: "System", icon: _jsx(InlineIcon, { icon: iconDeviceDesktop }), title: "Follow the operating system's animations setting" },
32
+ ];
33
+ const GRIDS = [
34
+ { value: "off", label: "Off", icon: _jsx(InlineIcon, { icon: iconEyeOff }) },
35
+ { value: "px", label: "px", icon: _jsx(InlineIcon, { icon: iconLayoutGrid }), title: "A line every 4px, a solid one every 48px" },
36
+ { value: "cm", label: "mm", icon: _jsx(InlineIcon, { icon: iconTable }), title: "A line every millimetre, a solid one every centimetre" },
37
+ ];
38
+ /** A small heading and what it names: a row of tab buttons by default, or the control given. */
39
+ function Choice({ id, legend, options, value, onChange, children }) {
40
+ return (_jsxs("div", { className: "nx-theme-picker__group", children: [_jsx("p", { id: id, className: "nx-theme-picker__legend", children: legend }), children ?? (options && value !== undefined && onChange && _jsx(TabButtons, { "aria-labelledby": id, name: id, options: options, value: value, onChange: onChange, block: true }))] }));
41
+ }
42
+ /** A group of settings under its name: one section of the panel, a line above it. */
43
+ function Section({ title, wide, children }) {
44
+ const id = useId();
45
+ return (_jsxs("section", { className: cx("nx-theme-picker__block", wide && "nx-theme-picker__block--wide"), "aria-labelledby": id, children: [_jsx("h3", { id: id, className: "nx-theme-picker__section", children: title }), children] }));
46
+ }
47
+ /**
48
+ * Two sections: the theme (Light / Dark / System and a card per theme with a live preview of its colors) and the
49
+ * appearance (the density, the animations and, in a development build, the scale grid). Each choice applies at once
50
+ * and is saved in the browser.
51
+ */
52
+ export function ThemePicker({ themes = NEXUS_THEMES, showMode = true, showDensity = true, showAnimations = true, showGrid = isDevelopmentBuild(), className }) {
53
+ const { theme, mode, density, animations, grid, setTheme, setMode, setDensity, setAnimations, setGrid } = useTheme();
54
+ const name = useId();
55
+ return (_jsxs("div", { className: cx("nx-theme-picker", className), children: [_jsxs(Section, { title: "Theme", wide: true, children: [showMode && _jsx(Choice, { id: `${name}-mode`, legend: "Mode", options: MODES, value: mode, onChange: setMode }), _jsxs("fieldset", { className: "nx-theme-picker__group", children: [_jsx("legend", { className: "nx-theme-picker__legend", children: "Colors" }), _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))) })] })] }), (showDensity || showAnimations || showGrid) && (_jsxs(Section, { title: "Appearance", children: [showDensity && _jsx(Choice, { id: `${name}-density`, legend: "Density", options: DENSITIES, value: density, onChange: setDensity }), showAnimations && _jsx(Choice, { id: `${name}-animations`, legend: "Animations", options: ANIMATIONS, value: animations, onChange: setAnimations }), showGrid && _jsx(Choice, { id: `${name}-grid`, legend: "Scale grid", options: GRIDS, value: grid, onChange: setGrid })] }))] }));
56
+ }
57
+ /* The quick preferences: language, dates, numbers and tables, beside the theme. */
58
+ const LANGUAGES = [
59
+ { value: "en", label: "English" },
60
+ { value: "ne", label: _jsx("span", { lang: "ne", children: "\u0928\u0947\u092A\u093E\u0932\u0940" }), title: "Nepali" },
61
+ ];
62
+ const CALENDARS = [
63
+ { value: "ad", label: "AD", title: "Gregorian (AD)" },
64
+ { value: "bs", label: "BS", title: "Bikram Sambat (BS)" },
65
+ ];
66
+ const SCRIPTS = [
67
+ { value: "latin", label: "English", title: "Month names and digits in English" },
68
+ { value: "devanagari", label: _jsx("span", { lang: "ne", children: "\u0928\u0947\u092A\u093E\u0932\u0940" }), title: "Month names and digits in Nepali" },
69
+ ];
70
+ const CLOCKS = [
71
+ { value: "12", label: "12-hour", title: "2:30 PM" },
72
+ { value: "24", label: "24-hour", title: "14:30" },
73
+ ];
74
+ const SCALES = [
75
+ { value: String(ENumberScales.International), label: "International", title: "1,234,567: thousand, million, billion" },
76
+ { value: String(ENumberScales.SouthAsian), label: "South Asian", title: "12,34,567: thousand, lakh, crore" },
77
+ ];
78
+ const SEPARATORS = [
79
+ { value: "comma", label: "1,200", title: "Comma" },
80
+ { value: "period", label: "1.200", title: "Period, with a comma for decimals" },
81
+ { value: "space", label: "1\u00a0200", title: "Space" },
82
+ { value: "none", label: "1200", title: "None" },
83
+ ];
84
+ const PAGE_SIZES = DEFAULT_PAGE_SIZES.map(size => ({ value: size, label: `${size} rows` }));
85
+ const DESIGNS = [
86
+ { value: "standard", label: "Standard", title: "The label above the box" },
87
+ { value: "outlined", label: "Outlined", title: "The label on the box's edge" },
88
+ { value: "inbox", label: "Inbox", title: "The label inside the top of the box" },
89
+ ];
90
+ // A fixed day, so the server and the browser write the same examples, and one past the 12th, so day and month differ.
91
+ const SAMPLE_DATE = "2026-09-27";
14
92
  /**
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.
93
+ * The person's quick preferences, each applied at once and saved on this device: the interface language and the
94
+ * calendar; the date picker's language, the clock and the date format (the date settings every date component reads);
95
+ * the number scale and the thousands separator (every number box, slider and table number without a locale of its
96
+ * own); and the page size tables open with and whether a row click opens a record (every table, once chosen, over what
97
+ * the table names itself). `ThemeSwitcher` shows it under the theme.
17
98
  */
18
- export function ThemePicker({ themes = NEXUS_THEMES, showMode = true, className }) {
19
- const { theme, mode, setTheme, setMode } = useTheme();
99
+ export function PreferencesPicker({ className }) {
100
+ const dates = useDatePreferences();
101
+ const preferences = useUserPreferences();
20
102
  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))) })] })] }));
103
+ const formats = DATE_FORMATS.map(format => ({ value: format, label: formatDate(SAMPLE_DATE, { ...dates, format }), description: format }));
104
+ return (_jsxs("div", { className: cx("nx-theme-picker", className), children: [_jsxs(Section, { title: "Language and region", children: [_jsx(Choice, { id: `${name}-language`, legend: "Interface language", options: LANGUAGES, value: preferences.language, onChange: language => setUserPreferences({ language }) }), _jsx(Choice, { id: `${name}-calendar`, legend: "Calendar", options: CALENDARS, value: dates.calendar, onChange: calendar => setDatePreferences({ calendar }) })] }), _jsxs(Section, { title: "Dates and times", children: [_jsx(Choice, { id: `${name}-script`, legend: "Date picker language", options: SCRIPTS, value: dates.script, onChange: script => setDatePreferences({ script }) }), _jsx(Choice, { id: `${name}-clock`, legend: "Clock", options: CLOCKS, value: /H/.test(dates.timeFormat) ? "24" : "12", onChange: clock => setDatePreferences({ timeFormat: clock === "24" ? "HH:mm" : DEFAULT_TIME_FORMAT }) }), _jsx(Choice, { id: `${name}-format`, legend: "Display date format", children: _jsx(Dropdown, { "aria-labelledby": `${name}-format`, options: formats, value: dates.format, clearable: false, searchable: false, onValueChange: format => format && setDatePreferences({ format }) }) })] }), _jsxs(Section, { title: "Numbers", children: [_jsx(Choice, { id: `${name}-scale`, legend: "Number scale", options: SCALES, value: String(preferences.numberScale), onChange: scale => setUserPreferences({ numberScale: Number(scale) === ENumberScales.SouthAsian ? ENumberScales.SouthAsian : ENumberScales.International }) }), _jsx(Choice, { id: `${name}-thousands`, legend: "Thousands separator", options: SEPARATORS, value: preferences.thousands, onChange: thousands => setUserPreferences({ thousands }) })] }), _jsxs(Section, { title: "Tables", children: [_jsx(Choice, { id: `${name}-page-size`, legend: "Default page size", children: _jsx(Dropdown, { "aria-labelledby": `${name}-page-size`, options: PAGE_SIZES, value: preferences.pageSize ?? DEFAULT_PAGE_SIZE, clearable: false, searchable: false, onValueChange: pageSize => pageSize && setUserPreferences({ pageSize }) }) }), _jsx(Switch, { label: "A row click opens the record", labelPosition: "start", checked: preferences.rowClick ?? false, onValueChange: rowClick => setUserPreferences({ rowClick }) })] }), _jsxs(Section, { title: "Forms", children: [_jsx(Choice, { id: `${name}-design`, legend: "Input design", options: DESIGNS, value: preferences.inputDesign ?? "standard", onChange: inputDesign => setUserPreferences({ inputDesign }) }), _jsx(TextBox, { label: "Example", placeholder: "Type to try it" })] })] }));
22
105
  }
@@ -4,8 +4,10 @@ export interface ThemeScriptProps extends ThemeScriptOptions {
4
4
  nonce?: string;
5
5
  }
6
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.
7
+ * Applies the saved theme, mode, density, animations and (in a development build) scale grid before the first paint,
8
+ * and keeps `data-nx-motion` in step with the system's reduced-motion setting: the stylesheets read that attribute,
9
+ * not the media query, so an app without this script animates whatever the system says. Render it in `<head>` of the
10
+ * root layout, and put `suppressHydrationWarning` on `<html>`, because the script sets attributes there.
9
11
  *
10
12
  * @example
11
13
  * <html lang="en" suppressHydrationWarning>
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { themeScript } from "../../Helpers/ThemeHelpers.js";
3
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.
4
+ * Applies the saved theme, mode, density, animations and (in a development build) scale grid before the first paint,
5
+ * and keeps `data-nx-motion` in step with the system's reduced-motion setting: the stylesheets read that attribute,
6
+ * not the media query, so an app without this script animates whatever the system says. Render it in `<head>` of the
7
+ * root layout, and put `suppressHydrationWarning` on `<html>`, because the script sets attributes there.
6
8
  *
7
9
  * @example
8
10
  * <html lang="en" suppressHydrationWarning>
@@ -5,9 +5,12 @@ export interface ThemeSwitcherProps extends ThemePickerProps {
5
5
  label?: string;
6
6
  /** Default `ghost`. */
7
7
  variant?: ButtonVariant;
8
+ /** The quick preferences under the theme: language, dates, numbers and tables (`PreferencesPicker`). Default true. */
9
+ preferences?: boolean;
8
10
  }
9
11
  /**
10
- * A palette button that opens the theme picker below it, with the panel's arrow on the button. The panel is a native
11
- * popover: it closes with Esc or a click outside, and needs no library.
12
+ * A palette button that opens a quick preference panel below it, with the panel's arrow on the button: the theme
13
+ * (`ThemePicker`) and, under it, the language, dates, numbers and tables (`PreferencesPicker`). The panel is a native
14
+ * popover: it closes with Esc or a click outside, stays inside the window and scrolls inside itself.
12
15
  */
13
- export declare function ThemeSwitcher({ label, variant, className, ...pickerProps }: ThemeSwitcherProps): import("react").JSX.Element;
16
+ export declare function ThemeSwitcher({ label, variant, className, preferences, ...pickerProps }: ThemeSwitcherProps): import("react").JSX.Element;
@@ -1,15 +1,16 @@
1
1
  "use client";
2
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { iconPalette, InlineIcon } from "nexus-icons";
4
4
  import { useId, useLayoutEffect, useRef, useState } from "react";
5
- import { followAnchor, isPopoverOpen, placeUnderAnchor } from "../../Helpers/PopoverHelpers.js";
5
+ import { followAnchor, isPopoverOpen, placeUnderAnchor, showPanel } from "../../Helpers/PopoverHelpers.js";
6
6
  import { Button } from "../Documents/Button.js";
7
- import { ThemePicker } from "./ThemePicker.js";
7
+ import { PreferencesPicker, ThemePicker } from "./ThemePicker.js";
8
8
  /**
9
- * A palette button that opens the theme picker below it, with the panel's arrow on the button. The panel is a native
10
- * popover: it closes with Esc or a click outside, and needs no library.
9
+ * A palette button that opens a quick preference panel below it, with the panel's arrow on the button: the theme
10
+ * (`ThemePicker`) and, under it, the language, dates, numbers and tables (`PreferencesPicker`). The panel is a native
11
+ * popover: it closes with Esc or a click outside, stays inside the window and scrolls inside itself.
11
12
  */
12
- export function ThemeSwitcher({ label = "Theme", variant = "ghost", className, ...pickerProps }) {
13
+ export function ThemeSwitcher({ label = "Theme", variant = "ghost", className, preferences = true, ...pickerProps }) {
13
14
  const panelId = useId();
14
15
  const buttonRef = useRef(null);
15
16
  const panelRef = useRef(null);
@@ -42,8 +43,8 @@ export function ThemeSwitcher({ label = "Theme", variant = "ghost", className, .
42
43
  else if (isPopoverOpen(panel))
43
44
  panel?.hidePopover();
44
45
  else if (panel && typeof panel.showPopover === "function") {
45
- panel.showPopover();
46
+ showPanel(panel);
46
47
  setOpen(true);
47
48
  }
48
- } }), _jsx("div", { ref: panelRef, id: panelId, popover: "auto", role: "dialog", "aria-label": label, className: "nx-popover nx-theme-switcher__panel", onToggle: onToggle, children: _jsx(ThemePicker, { ...pickerProps }) })] }));
49
+ } }), _jsx("div", { ref: panelRef, id: panelId, popover: "auto", role: "dialog", "aria-label": label, className: "nx-popover nx-theme-switcher__panel", onToggle: onToggle, children: _jsxs("div", { className: "nx-popover__scroll nx-theme-switcher__body", children: [_jsx(ThemePicker, { ...pickerProps }), preferences && _jsx(PreferencesPicker, {})] }) })] }));
49
50
  }
@@ -0,0 +1,21 @@
1
+ export interface AvatarProps {
2
+ /** Whose picture it is: the initials stand in without one, and the name picks their tone. */
3
+ name: string;
4
+ /** Address of the picture. Anything but a non-empty string is no picture. */
5
+ src?: unknown;
6
+ /** `sm` 22px, `md` 28px, `lg` 44px. Default `md`; `--nx-avatar-size` sets any other. */
7
+ size?: "sm" | "md" | "lg";
8
+ className?: string;
9
+ /** Makes the picture a button that offers to change it: a camera shows over it on hover, and a click calls this. */
10
+ onChangePicture?: () => void;
11
+ /** The button's name. Default "Change picture". */
12
+ changeLabel?: string;
13
+ /** For the button inside a menu: `role="menuitem"` and `tabIndex={-1}` let the arrow keys reach it. */
14
+ role?: string;
15
+ tabIndex?: number;
16
+ }
17
+ /**
18
+ * A person: a round picture, or their initials on a soft tone when there is none or it fails to load. The chat,
19
+ * the dropdown's avatar column and the admin bar all draw this one.
20
+ */
21
+ export declare function Avatar({ name, src, size, className, onChangePicture, changeLabel, role, tabIndex }: AvatarProps): import("react").JSX.Element;
@@ -0,0 +1,21 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Icon } from "nexus-icons";
4
+ import { useState } from "react";
5
+ import { cx } from "../../Helpers/ClassHelpers.js";
6
+ import { avatarTone, initials } from "../../Helpers/DropdownHelpers.js";
7
+ /**
8
+ * A person: a round picture, or their initials on a soft tone when there is none or it fails to load. The chat,
9
+ * the dropdown's avatar column and the admin bar all draw this one.
10
+ */
11
+ export function Avatar({ name, src, size = "md", className, onChangePicture, changeLabel = "Change picture", role, tabIndex }) {
12
+ const url = typeof src === "string" ? src.trim() : "";
13
+ const [failed, setFailed] = useState(null);
14
+ const photo = url !== "" && failed !== url;
15
+ const face = photo ? _jsx("img", { src: url, alt: "", loading: "lazy", decoding: "async", onError: () => setFailed(url) }) : initials(name);
16
+ const shared = { className: cx("nx-avatar", size !== "md" && `nx-avatar--${size}`, className), "data-tone": photo ? undefined : avatarTone(name) };
17
+ if (!onChangePicture) {
18
+ return (_jsx("span", { ...shared, "aria-hidden": "true", children: face }));
19
+ }
20
+ return (_jsxs("button", { ...shared, type: "button", role: role, tabIndex: tabIndex, "aria-label": changeLabel, title: changeLabel, onClick: onChangePicture, children: [face, _jsx("span", { className: "nx-avatar__veil", "aria-hidden": "true", children: _jsx(Icon, { name: "camera", size: "40%" }) })] }));
21
+ }