nexus-shared 1.1.20 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 -34
- package/src/client.ts +0 -31
- package/src/components/documents/button.tsx +0 -142
- 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 -59
- 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 -57
- 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 -49
- 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 -161
- 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.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,69 @@
|
|
|
1
|
+
import { type CSSProperties, type ReactNode } from "react";
|
|
2
|
+
import type { CalendarDay, CalendarType, DateInput, DateRange, DateScript } from "../../Interfaces/DateInterfaces.ts";
|
|
3
|
+
import type { Size } from "../../Interfaces/TypeInterfaces.ts";
|
|
4
|
+
export type { DateRange } from "../../Interfaces/DateInterfaces.ts";
|
|
5
|
+
export interface CalendarProps {
|
|
6
|
+
/** Picks one day (default) or a start and an end. */
|
|
7
|
+
mode?: "single" | "range";
|
|
8
|
+
/** The chosen day, as an ISO date. With `value`, the calendar is controlled. */
|
|
9
|
+
value?: DateInput;
|
|
10
|
+
defaultValue?: DateInput;
|
|
11
|
+
/** After a day is chosen or cleared, with the ISO date or `null`. */
|
|
12
|
+
onValueChange?: (value: string | null) => void;
|
|
13
|
+
/** Range mode: the chosen range. With `range`, the calendar is controlled. */
|
|
14
|
+
range?: DateRange;
|
|
15
|
+
defaultRange?: DateRange;
|
|
16
|
+
/** Range mode: after the start is chosen (end `null`), after the end is chosen, and after a clear. */
|
|
17
|
+
onRangeChange?: (range: [string | null, string | null]) => void;
|
|
18
|
+
/** After a pick is complete: a day, or the end of a range. A popover closes on this. */
|
|
19
|
+
onSelect?: (value: string) => void;
|
|
20
|
+
/** The calendar shown: `ad` or `bs`. Default: the user's setting. Values are ISO dates in either case. */
|
|
21
|
+
calendar?: CalendarType;
|
|
22
|
+
/** Digits and names of Nepali dates. Default: the user's setting. */
|
|
23
|
+
script?: DateScript;
|
|
24
|
+
/** Earliest day that can be picked. */
|
|
25
|
+
min?: DateInput;
|
|
26
|
+
/** Latest day that can be picked. */
|
|
27
|
+
max?: DateInput;
|
|
28
|
+
/** Days that cannot be picked, such as booked nights. */
|
|
29
|
+
isDateDisabled?: (iso: string) => boolean;
|
|
30
|
+
/** The month shown, as any day in it. With `month`, moving between months goes through `onMonthChange`. */
|
|
31
|
+
month?: DateInput;
|
|
32
|
+
defaultMonth?: DateInput;
|
|
33
|
+
/** After the shown month changes, with its first day. */
|
|
34
|
+
onMonthChange?: (firstDay: string) => void;
|
|
35
|
+
/** Months shown side by side, from the one in `month`: 1 (default) or 2, for ranges. With more than one, outside days are not drawn. */
|
|
36
|
+
months?: number;
|
|
37
|
+
/** First column of the week: 0 Sunday (default), 1 Monday, 6 Saturday. */
|
|
38
|
+
weekStart?: number;
|
|
39
|
+
/** Weekdays drawn as holidays (0 Sunday to 6 Saturday). Default: Saturday in the Nepali calendar, none otherwise. */
|
|
40
|
+
weekendDays?: readonly number[];
|
|
41
|
+
/** Shows the days of the months before and after that fill the first and last rows. Default true. */
|
|
42
|
+
showOutsideDays?: boolean;
|
|
43
|
+
/** Draws the inside of a day, e.g. the number with event markers under it. Default: the day number. */
|
|
44
|
+
renderDay?: (day: CalendarDay) => ReactNode;
|
|
45
|
+
/** Today and Clear buttons under the grid. Default true. */
|
|
46
|
+
footer?: boolean;
|
|
47
|
+
/** The day marked and picked as today, as an ISO date. Default: today where the browser is; a date and time picker showing another time zone passes that zone's day. */
|
|
48
|
+
today?: DateInput;
|
|
49
|
+
/** Default `md`. */
|
|
50
|
+
size?: Size;
|
|
51
|
+
readOnly?: boolean;
|
|
52
|
+
disabled?: boolean;
|
|
53
|
+
/** Focuses the calendar's current day when it mounts. */
|
|
54
|
+
autoFocus?: boolean;
|
|
55
|
+
id?: string;
|
|
56
|
+
className?: string;
|
|
57
|
+
style?: CSSProperties;
|
|
58
|
+
"aria-label"?: string;
|
|
59
|
+
"aria-labelledby"?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A month of days to pick from, in the English (AD) or Nepali (BS) calendar, with month and year menus, arrow-key
|
|
63
|
+
* navigation, a range mode with a live preview, two months side by side with `months`, and a `renderDay` slot for
|
|
64
|
+
* event markers. Values are ISO dates in the Gregorian calendar whatever calendar is shown. It is the panel of
|
|
65
|
+
* `DatePicker` and `DateRangePicker`, and works on its own in a page.
|
|
66
|
+
*
|
|
67
|
+
* @example <Calendar value={date} onValueChange={setDate} min={todayIsoDate()} />
|
|
68
|
+
*/
|
|
69
|
+
export declare function Calendar(props: CalendarProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { InlineIcon, iconChevronLeft, iconChevronRight } from "nexus-icons";
|
|
4
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
5
|
+
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
6
|
+
import { addDays, addMonths, CALENDAR_RANGE, daysInMonth, formatDateParts, fromCalendar, getMonthNames, getWeekdayNames, toCalendar, toDevanagariDigits, todayIsoDate, toIsoDate, usesDevanagari, weekdayOf, } from "../../Helpers/DateHelpers.js";
|
|
7
|
+
import { useDatePreferences } from "../../Helpers/DatePreferences.js";
|
|
8
|
+
import { BS_FIRST_YEAR, BS_LAST_YEAR } from "../../Helpers/NepaliCalendar.js";
|
|
9
|
+
import { Button } from "../Documents/Button.js";
|
|
10
|
+
const ROWS = 6;
|
|
11
|
+
const LABEL_FORMAT = "dddd, MMMM D, YYYY";
|
|
12
|
+
const yearsWindow = (calendar, today, min, max) => {
|
|
13
|
+
const first = calendar === "bs" ? BS_FIRST_YEAR : today.year - 100;
|
|
14
|
+
const last = calendar === "bs" ? BS_LAST_YEAR : today.year + 50;
|
|
15
|
+
return { first: min ? Math.max(min.year, first) : first, last: max ? Math.min(max.year, last) : last };
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* A month of days to pick from, in the English (AD) or Nepali (BS) calendar, with month and year menus, arrow-key
|
|
19
|
+
* navigation, a range mode with a live preview, two months side by side with `months`, and a `renderDay` slot for
|
|
20
|
+
* event markers. Values are ISO dates in the Gregorian calendar whatever calendar is shown. It is the panel of
|
|
21
|
+
* `DatePicker` and `DateRangePicker`, and works on its own in a page.
|
|
22
|
+
*
|
|
23
|
+
* @example <Calendar value={date} onValueChange={setDate} min={todayIsoDate()} />
|
|
24
|
+
*/
|
|
25
|
+
export function Calendar(props) {
|
|
26
|
+
const { mode = "single", value, defaultValue, onValueChange, range, defaultRange, onRangeChange, onSelect, min, max, isDateDisabled, month, defaultMonth, onMonthChange, months = 1, weekStart = 0, weekendDays, showOutsideDays = true, renderDay, footer = true, size = "md", readOnly = false, disabled = false, autoFocus = false, id, className, style, } = props;
|
|
27
|
+
const preferences = useDatePreferences();
|
|
28
|
+
const calendar = props.calendar ?? preferences.calendar;
|
|
29
|
+
const script = props.script ?? preferences.script;
|
|
30
|
+
const devanagari = usesDevanagari(calendar, script);
|
|
31
|
+
const lang = devanagari ? "ne" : undefined;
|
|
32
|
+
const isRange = mode === "range";
|
|
33
|
+
const inert = readOnly || disabled;
|
|
34
|
+
const monthCount = Math.max(1, Math.floor(months));
|
|
35
|
+
// Two months share their edges, so the days of a neighbour are drawn once, in their own month.
|
|
36
|
+
const outsideDays = showOutsideDays && monthCount === 1;
|
|
37
|
+
const generatedId = useId();
|
|
38
|
+
const rootId = id ?? generatedId;
|
|
39
|
+
const gridRef = useRef(null);
|
|
40
|
+
const focusPendingRef = useRef(false);
|
|
41
|
+
// The value: one day, or a start and an end.
|
|
42
|
+
const [ownValue, setOwnValue] = useState(() => toIsoDate(defaultValue));
|
|
43
|
+
const [ownRange, setOwnRange] = useState(() => [toIsoDate(defaultRange?.[0]), toIsoDate(defaultRange?.[1])]);
|
|
44
|
+
const selected = value !== undefined ? toIsoDate(value) : ownValue;
|
|
45
|
+
const [rangeStart, rangeEnd] = range !== undefined ? [toIsoDate(range[0]), toIsoDate(range[1])] : ownRange;
|
|
46
|
+
const picking = isRange && rangeStart !== null && rangeEnd === null;
|
|
47
|
+
const [hoverEnd, setHoverEnd] = useState(null);
|
|
48
|
+
// Limits, as ISO dates, kept inside the days the calendar covers.
|
|
49
|
+
const today = toIsoDate(props.today) ?? todayIsoDate();
|
|
50
|
+
const minIso = toIsoDate(min) ?? CALENDAR_RANGE[calendar].min;
|
|
51
|
+
const maxIso = toIsoDate(max) ?? CALENDAR_RANGE[calendar].max;
|
|
52
|
+
const clampView = (iso) => (iso < CALENDAR_RANGE[calendar].min ? CALENDAR_RANGE[calendar].min : iso > CALENDAR_RANGE[calendar].max ? CALENDAR_RANGE[calendar].max : iso);
|
|
53
|
+
// The month shown is remembered as a day in it, so a change of calendar keeps showing the month of that day.
|
|
54
|
+
const [ownView, setOwnView] = useState(() => toIsoDate(defaultMonth) ?? (isRange ? rangeStart : selected) ?? today);
|
|
55
|
+
const viewIso = clampView((month !== undefined ? toIsoDate(month) : null) ?? ownView);
|
|
56
|
+
const view = toCalendar(viewIso, calendar) ?? toCalendar(CALENDAR_RANGE[calendar].min, calendar);
|
|
57
|
+
const viewStart = { year: view.year, month: view.month, day: 1 };
|
|
58
|
+
const firstOfMonth = fromCalendar(viewStart, calendar);
|
|
59
|
+
// The first day after the last month shown. At the end of the Nepali table, the day after its last day.
|
|
60
|
+
const afterShown = fromCalendar(addMonths(viewStart, monthCount, calendar), calendar);
|
|
61
|
+
const shownEnd = afterShown && afterShown > firstOfMonth ? afterShown : addDays(CALENDAR_RANGE[calendar].max, 1);
|
|
62
|
+
// The day that takes focus, in the roving tab order of the grid: the chosen day when it is on screen, else today,
|
|
63
|
+
// else the first day of the month shown.
|
|
64
|
+
const inShown = (iso) => iso !== null && iso >= firstOfMonth && iso < shownEnd;
|
|
65
|
+
const [focused, setFocused] = useState(() => {
|
|
66
|
+
const chosen = isRange ? rangeStart : selected;
|
|
67
|
+
return inShown(chosen) ? chosen : inShown(today) ? today : firstOfMonth;
|
|
68
|
+
});
|
|
69
|
+
const isDisabled = (iso) => iso < minIso || iso > maxIso || (isDateDisabled?.(iso) ?? false);
|
|
70
|
+
function showMonth(iso) {
|
|
71
|
+
const next = clampView(iso);
|
|
72
|
+
if (month === undefined)
|
|
73
|
+
setOwnView(next);
|
|
74
|
+
const parts = toCalendar(next, calendar);
|
|
75
|
+
if (parts && (parts.year !== view.year || parts.month !== view.month))
|
|
76
|
+
onMonthChange?.(fromCalendar({ year: parts.year, month: parts.month, day: 1 }, calendar));
|
|
77
|
+
}
|
|
78
|
+
/** Shows a month picked in the menus of the month at `index`, so that month keeps its place. */
|
|
79
|
+
function showMonthAt(parts, index) {
|
|
80
|
+
showMonth(fromCalendar(addMonths({ year: parts.year, month: parts.month, day: 1 }, -index, calendar), calendar) ?? firstOfMonth);
|
|
81
|
+
}
|
|
82
|
+
function moveFocus(iso) {
|
|
83
|
+
if (iso < CALENDAR_RANGE[calendar].min || iso > CALENDAR_RANGE[calendar].max)
|
|
84
|
+
return;
|
|
85
|
+
setFocused(iso);
|
|
86
|
+
focusPendingRef.current = true;
|
|
87
|
+
if (iso < firstOfMonth) {
|
|
88
|
+
showMonth(iso);
|
|
89
|
+
}
|
|
90
|
+
else if (iso >= shownEnd) {
|
|
91
|
+
// Moving forward, the day lands in the last month shown.
|
|
92
|
+
const parts = toCalendar(iso, calendar);
|
|
93
|
+
if (parts)
|
|
94
|
+
showMonth(fromCalendar(addMonths({ year: parts.year, month: parts.month, day: 1 }, 1 - monthCount, calendar), calendar) ?? iso);
|
|
95
|
+
}
|
|
96
|
+
if (picking)
|
|
97
|
+
setHoverEnd(iso);
|
|
98
|
+
}
|
|
99
|
+
function pick(iso) {
|
|
100
|
+
if (inert || isDisabled(iso))
|
|
101
|
+
return;
|
|
102
|
+
setFocused(iso);
|
|
103
|
+
if (!isRange) {
|
|
104
|
+
if (value === undefined)
|
|
105
|
+
setOwnValue(iso);
|
|
106
|
+
onValueChange?.(iso);
|
|
107
|
+
onSelect?.(iso);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (!picking) {
|
|
111
|
+
if (range === undefined)
|
|
112
|
+
setOwnRange([iso, null]);
|
|
113
|
+
setHoverEnd(null);
|
|
114
|
+
onRangeChange?.([iso, null]);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const next = iso < rangeStart ? [iso, rangeStart] : [rangeStart, iso];
|
|
118
|
+
if (range === undefined)
|
|
119
|
+
setOwnRange(next);
|
|
120
|
+
setHoverEnd(null);
|
|
121
|
+
onRangeChange?.(next);
|
|
122
|
+
onSelect?.(next[1]);
|
|
123
|
+
}
|
|
124
|
+
function clear() {
|
|
125
|
+
if (inert)
|
|
126
|
+
return;
|
|
127
|
+
if (isRange) {
|
|
128
|
+
if (range === undefined)
|
|
129
|
+
setOwnRange([null, null]);
|
|
130
|
+
setHoverEnd(null);
|
|
131
|
+
onRangeChange?.([null, null]);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
if (value === undefined)
|
|
135
|
+
setOwnValue(null);
|
|
136
|
+
onValueChange?.(null);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function goToday() {
|
|
140
|
+
showMonth(today);
|
|
141
|
+
moveFocus(today);
|
|
142
|
+
if (!isRange)
|
|
143
|
+
pick(today);
|
|
144
|
+
}
|
|
145
|
+
// Focus follows the roving day after the grid renders it, e.g. after an arrow key moved to another month.
|
|
146
|
+
useEffect(() => {
|
|
147
|
+
if (!focusPendingRef.current || !focused)
|
|
148
|
+
return;
|
|
149
|
+
focusPendingRef.current = false;
|
|
150
|
+
gridRef.current?.querySelector(`[data-iso="${focused}"]`)?.focus();
|
|
151
|
+
});
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (autoFocus)
|
|
154
|
+
gridRef.current?.querySelector('[role="gridcell"][tabindex="0"]')?.focus();
|
|
155
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
156
|
+
}, []);
|
|
157
|
+
function onKeyDown(event) {
|
|
158
|
+
const target = event.target;
|
|
159
|
+
const current = target.dataset.iso;
|
|
160
|
+
if (!current)
|
|
161
|
+
return;
|
|
162
|
+
const { key } = event;
|
|
163
|
+
let next = null;
|
|
164
|
+
if (key === "ArrowLeft")
|
|
165
|
+
next = addDays(current, -1);
|
|
166
|
+
else if (key === "ArrowRight")
|
|
167
|
+
next = addDays(current, 1);
|
|
168
|
+
else if (key === "ArrowUp")
|
|
169
|
+
next = addDays(current, -7);
|
|
170
|
+
else if (key === "ArrowDown")
|
|
171
|
+
next = addDays(current, 7);
|
|
172
|
+
else if (key === "Home")
|
|
173
|
+
next = addDays(current, -((weekdayOf(current) - weekStart + 7) % 7));
|
|
174
|
+
else if (key === "End")
|
|
175
|
+
next = addDays(current, 6 - ((weekdayOf(current) - weekStart + 7) % 7));
|
|
176
|
+
else if (key === "PageUp" || key === "PageDown") {
|
|
177
|
+
const parts = toCalendar(current, calendar);
|
|
178
|
+
if (parts)
|
|
179
|
+
next = fromCalendar(addMonths(parts, (key === "PageUp" ? -1 : 1) * (event.shiftKey ? 12 : 1), calendar), calendar);
|
|
180
|
+
}
|
|
181
|
+
else if (key === "Enter" || key === " ") {
|
|
182
|
+
event.preventDefault();
|
|
183
|
+
pick(current);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (next) {
|
|
187
|
+
event.preventDefault();
|
|
188
|
+
moveFocus(next);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
function onPointerOver(event) {
|
|
192
|
+
if (!picking)
|
|
193
|
+
return;
|
|
194
|
+
const iso = event.target.closest("[data-iso]")?.dataset.iso ?? null;
|
|
195
|
+
if (iso !== hoverEnd)
|
|
196
|
+
setHoverEnd(iso);
|
|
197
|
+
}
|
|
198
|
+
// The range band: between the two ends, or between the start and the day under the pointer while picking.
|
|
199
|
+
const bandEnd = rangeEnd ?? (picking ? hoverEnd : null);
|
|
200
|
+
const [bandStart, bandStop] = rangeStart && bandEnd ? (bandEnd < rangeStart ? [bandEnd, rangeStart] : [rangeStart, bandEnd]) : [null, null];
|
|
201
|
+
const holidays = weekendDays ?? (calendar === "bs" ? [6] : []);
|
|
202
|
+
const monthNames = getMonthNames(calendar, script);
|
|
203
|
+
const digits = (n) => (devanagari ? toDevanagariDigits(String(n)) : String(n));
|
|
204
|
+
const monthLabel = (m) => monthNames[m - 1] ?? "";
|
|
205
|
+
// The grids: always six rows each, so the panel keeps its height from month to month.
|
|
206
|
+
const monthViews = [];
|
|
207
|
+
for (let index = 0; index < monthCount; index++) {
|
|
208
|
+
const parts = addMonths(viewStart, index, calendar);
|
|
209
|
+
const first = fromCalendar(parts, calendar);
|
|
210
|
+
if (!first || (index > 0 && first <= monthViews[index - 1].first))
|
|
211
|
+
break;
|
|
212
|
+
const monthLength = daysInMonth(parts.year, parts.month, calendar);
|
|
213
|
+
const lead = (weekdayOf(first) - weekStart + 7) % 7;
|
|
214
|
+
const cells = [];
|
|
215
|
+
for (let cell = 0; cell < ROWS * 7; cell++) {
|
|
216
|
+
const iso = addDays(first, cell - lead);
|
|
217
|
+
const outside = cell < lead || cell >= lead + monthLength;
|
|
218
|
+
const dayParts = toCalendar(iso, calendar);
|
|
219
|
+
if (!dayParts || (outside && !outsideDays)) {
|
|
220
|
+
cells.push(null);
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const weekday = weekdayOf(iso);
|
|
224
|
+
cells.push({
|
|
225
|
+
iso,
|
|
226
|
+
parts: dayParts,
|
|
227
|
+
weekday,
|
|
228
|
+
outside,
|
|
229
|
+
today: iso === today,
|
|
230
|
+
selected: isRange ? iso === rangeStart || iso === rangeEnd : iso === selected,
|
|
231
|
+
inRange: bandStart !== null && iso > bandStart && iso < bandStop,
|
|
232
|
+
disabled: inert || isDisabled(iso),
|
|
233
|
+
rangeStart: isRange && iso === bandStart && bandStop !== bandStart,
|
|
234
|
+
rangeEnd: isRange && iso === bandStop && bandStop !== bandStart,
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
monthViews.push({ first, parts, title: `${monthLabel(parts.month)} ${digits(parts.year)}`, cells });
|
|
238
|
+
}
|
|
239
|
+
const focusIso = focused && monthViews.some(view => view.cells.some(cell => cell?.iso === focused)) ? focused : (monthViews[0]?.cells.find(cell => cell && !cell.outside)?.iso ?? null);
|
|
240
|
+
// Header menus and arrows
|
|
241
|
+
const todayParts = toCalendar(today, calendar) ?? view;
|
|
242
|
+
const years = yearsWindow(calendar, todayParts, toIsoDate(min) ? toCalendar(minIso, calendar) : null, toIsoDate(max) ? toCalendar(maxIso, calendar) : null);
|
|
243
|
+
const lastShownYear = monthViews[monthViews.length - 1]?.parts.year ?? view.year;
|
|
244
|
+
const yearOptions = [];
|
|
245
|
+
for (let year = Math.min(years.first, view.year); year <= Math.max(years.last, lastShownYear); year++)
|
|
246
|
+
yearOptions.push(year);
|
|
247
|
+
const prevMonth = fromCalendar(addMonths(viewStart, -1, calendar), calendar) ?? firstOfMonth;
|
|
248
|
+
const nextMonth = fromCalendar(addMonths(viewStart, 1, calendar), calendar) ?? firstOfMonth;
|
|
249
|
+
const canGoPrev = prevMonth < firstOfMonth && addDays(firstOfMonth, -1) >= minIso;
|
|
250
|
+
const canGoNext = nextMonth > firstOfMonth && shownEnd <= maxIso;
|
|
251
|
+
const title = monthViews.map(view => view.title).join(" – ");
|
|
252
|
+
const weekdayNames = getWeekdayNames(calendar, script);
|
|
253
|
+
const weekdayMinNames = getWeekdayNames(calendar, script, "min");
|
|
254
|
+
return (_jsxs("div", { id: rootId, className: cx("nx-cal", `nx-cal--${size}`, isRange && "nx-cal--range", monthViews.length > 1 && "nx-cal--months", className), style: style, "data-disabled": disabled || undefined, "data-readonly": readOnly || undefined, "aria-label": props["aria-label"], "aria-labelledby": props["aria-labelledby"], children: [_jsx("div", { ref: gridRef, className: "nx-cal__months", children: monthViews.map((monthView, index) => (_jsxs("div", { className: "nx-cal__month", children: [_jsxs("div", { className: "nx-cal__head", children: [index === 0 ? (_jsx("button", { type: "button", className: "nx-cal__nav", "aria-label": "Previous month", disabled: disabled || !canGoPrev, onClick: () => showMonth(prevMonth), children: _jsx(InlineIcon, { icon: iconChevronLeft }) })) : (_jsx("span", { className: "nx-cal__nav nx-cal__nav--space", "aria-hidden": "true" })), _jsxs("div", { className: "nx-cal__menus", children: [_jsx("select", { className: "nx-cal__select", "aria-label": "Month", lang: lang, value: monthView.parts.month, disabled: disabled, onChange: event => showMonthAt({ year: monthView.parts.year, month: Number(event.target.value), day: 1 }, index), children: monthNames.map((name, monthIndex) => (_jsx("option", { value: monthIndex + 1, children: name }, name))) }), _jsx("select", { className: "nx-cal__select", "aria-label": "Year", lang: lang, value: monthView.parts.year, disabled: disabled, onChange: event => showMonthAt({ year: Number(event.target.value), month: monthView.parts.month, day: 1 }, index), children: yearOptions.map(year => (_jsx("option", { value: year, children: digits(year) }, year))) })] }), index === monthViews.length - 1 ? (_jsx("button", { type: "button", className: "nx-cal__nav", "aria-label": "Next month", disabled: disabled || !canGoNext, onClick: () => showMonth(nextMonth), children: _jsx(InlineIcon, { icon: iconChevronRight }) })) : (_jsx("span", { className: "nx-cal__nav nx-cal__nav--space", "aria-hidden": "true" }))] }), _jsxs("div", { className: "nx-cal__grid", role: "grid", "aria-label": monthView.title, "aria-multiselectable": isRange || undefined, onKeyDown: onKeyDown, onPointerOver: onPointerOver, onPointerLeave: () => picking && setHoverEnd(null), children: [_jsx("div", { className: "nx-cal__row", role: "row", children: Array.from({ length: 7 }, (_, column) => {
|
|
255
|
+
const weekday = (weekStart + column) % 7;
|
|
256
|
+
return (_jsx("div", { className: "nx-cal__weekday", role: "columnheader", lang: lang, "data-weekend": holidays.includes(weekday) || undefined, "aria-label": weekdayNames[weekday], title: weekdayNames[weekday], children: weekdayMinNames[weekday] }, weekday));
|
|
257
|
+
}) }), Array.from({ length: ROWS }, (_, row) => (_jsx("div", { className: "nx-cal__row", role: "row", children: monthView.cells.slice(row * 7, row * 7 + 7).map((cell, column) => cell ? (_jsx("button", { type: "button", role: "gridcell", className: "nx-cal__day", lang: lang, "data-iso": cell.iso, "data-outside": cell.outside || undefined, "data-today": cell.today || undefined, "data-weekend": holidays.includes(cell.weekday) || undefined, "data-in-range": cell.inRange || undefined, "data-range-start": cell.rangeStart || undefined, "data-range-end": cell.rangeEnd || undefined, "aria-selected": cell.selected, "aria-disabled": cell.disabled || undefined, "aria-current": cell.today ? "date" : undefined, "aria-label": formatDateParts(cell.parts, calendar, LABEL_FORMAT, script, cell.weekday), tabIndex: cell.iso === focusIso ? 0 : -1, onClick: () => pick(cell.iso), onFocus: () => setFocused(cell.iso), children: renderDay ? renderDay(cell) : _jsx("span", { className: "nx-cal__num", children: digits(cell.parts.day) }) }, cell.iso)) : (_jsx("div", { className: "nx-cal__day nx-cal__day--empty", role: "gridcell", "aria-hidden": "true" }, `empty-${row}-${column}`))) }, row)))] })] }, monthView.first))) }), _jsx("p", { className: "nx-visually-hidden", "aria-live": "polite", lang: lang, children: title }), footer && (_jsxs("div", { className: "nx-cal__foot", children: [_jsx(Button, { size: "sm", variant: "ghost", hoverTone: "primary", disabled: inert || isDisabled(today), onClick: goToday, children: "Today" }), _jsx(Button, { size: "sm", variant: "ghost", disabled: inert || (isRange ? rangeStart === null : selected === null), onClick: clear, children: "Clear" })] }))] }));
|
|
258
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type ChangeEvent, type ComponentPropsWithRef, type ReactNode } from "react";
|
|
2
|
+
import type { CheckRules, ValidateOn } from "../../Interfaces/InputInterfaces.ts";
|
|
3
|
+
import type { Size } from "../../Interfaces/TypeInterfaces.ts";
|
|
4
|
+
type NativeInputProps = Omit<ComponentPropsWithRef<"input">, "type" | "size" | "children" | keyof CheckRules>;
|
|
5
|
+
export interface CheckBoxProps extends NativeInputProps, CheckRules {
|
|
6
|
+
label?: ReactNode;
|
|
7
|
+
/** A second, muted line under the label. */
|
|
8
|
+
description?: ReactNode;
|
|
9
|
+
/** An error from outside, such as a server message. */
|
|
10
|
+
error?: string | null;
|
|
11
|
+
/** Default `md`. */
|
|
12
|
+
size?: Size;
|
|
13
|
+
/** `end` (default) puts the label after the box. `start` puts it first and fills the width, as in a settings list. */
|
|
14
|
+
labelPosition?: "end" | "start";
|
|
15
|
+
/** Can be clicked and focused but not changed; still sent with a form when checked. A lock follows the label, and a click shows a note. */
|
|
16
|
+
readOnly?: boolean;
|
|
17
|
+
/** What a read-only box says when someone tries to change it. Default "Read-only: this option cannot be changed." ("this setting" for a switch). */
|
|
18
|
+
readOnlyMessage?: string;
|
|
19
|
+
/** Name used in messages: "Terms must be checked." Without it: "Check this box to continue." */
|
|
20
|
+
fieldName?: string;
|
|
21
|
+
/** When the message shows. Default `blur`; submitting a form always shows it. */
|
|
22
|
+
validateOn?: ValidateOn;
|
|
23
|
+
/** The new state after each change. */
|
|
24
|
+
onValueChange?: (checked: boolean, event: ChangeEvent<HTMLInputElement>) => void;
|
|
25
|
+
/** Class of the outer field. `inputClassName` goes on the input. */
|
|
26
|
+
className?: string;
|
|
27
|
+
inputClassName?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface CheckBoxOnlyProps {
|
|
30
|
+
/** Shows a dash for "some chosen", e.g. a select-all box. A click clears it. */
|
|
31
|
+
indeterminate?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A checkbox with a label. Uncontrolled by default (`defaultChecked`); `checked` with `onValueChange` also works.
|
|
35
|
+
* `required` means it must be checked. Drawn from the native input, so it stays one element and works in any form.
|
|
36
|
+
*
|
|
37
|
+
* @example <CheckBox name="terms" label="I accept the booking terms" required />
|
|
38
|
+
*/
|
|
39
|
+
export declare function CheckBox({ indeterminate, ...props }: CheckBoxProps & CheckBoxOnlyProps): import("react").JSX.Element;
|
|
40
|
+
/**
|
|
41
|
+
* An on/off switch for a setting that takes effect at once. The same props as `CheckBox`, announced as a switch.
|
|
42
|
+
*
|
|
43
|
+
* @example <Switch name="breakfast" label="Breakfast included" labelPosition="start" defaultChecked />
|
|
44
|
+
*/
|
|
45
|
+
export declare function Switch(props: CheckBoxProps): import("react").JSX.Element;
|
|
46
|
+
/**
|
|
47
|
+
* One radio button that is on or off, with the same props as `CheckBox`. A click only turns it on. Tabular and group
|
|
48
|
+
* forms use it for a value one row has at a time, such as the primary address; for a choice from a list, use `RadioGroup`.
|
|
49
|
+
*
|
|
50
|
+
* @example <RadioButton label="Primary" defaultChecked onValueChange={on => on && makePrimary(row)} />
|
|
51
|
+
*/
|
|
52
|
+
export declare function RadioButton(props: CheckBoxProps): import("react").JSX.Element;
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useLayoutEffect, useRef } from "react";
|
|
4
|
+
import { cx } from "../../Helpers/ClassHelpers.js";
|
|
5
|
+
import { validateCheck } from "../../Helpers/ValidationHelpers.js";
|
|
6
|
+
import { assignRef, GroupFooter, leavesField, OPTION_ATTRIBUTE, optionAnchor, preventChange, useChoiceField } from "./ChoiceField.js";
|
|
7
|
+
import { hasContent } from "./Field.js";
|
|
8
|
+
import { useReadOnlyNotice } from "./ReadOnlyNotice.js";
|
|
9
|
+
/**
|
|
10
|
+
* A checkbox with a label. Uncontrolled by default (`defaultChecked`); `checked` with `onValueChange` also works.
|
|
11
|
+
* `required` means it must be checked. Drawn from the native input, so it stays one element and works in any form.
|
|
12
|
+
*
|
|
13
|
+
* @example <CheckBox name="terms" label="I accept the booking terms" required />
|
|
14
|
+
*/
|
|
15
|
+
export function CheckBox({ indeterminate, ...props }) {
|
|
16
|
+
return _jsx(Toggle, { ...props, kind: "checkbox", indeterminate: indeterminate });
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* An on/off switch for a setting that takes effect at once. The same props as `CheckBox`, announced as a switch.
|
|
20
|
+
*
|
|
21
|
+
* @example <Switch name="breakfast" label="Breakfast included" labelPosition="start" defaultChecked />
|
|
22
|
+
*/
|
|
23
|
+
export function Switch(props) {
|
|
24
|
+
return _jsx(Toggle, { ...props, kind: "switch" });
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* One radio button that is on or off, with the same props as `CheckBox`. A click only turns it on. Tabular and group
|
|
28
|
+
* forms use it for a value one row has at a time, such as the primary address; for a choice from a list, use `RadioGroup`.
|
|
29
|
+
*
|
|
30
|
+
* @example <RadioButton label="Primary" defaultChecked onValueChange={on => on && makePrimary(row)} />
|
|
31
|
+
*/
|
|
32
|
+
export function RadioButton(props) {
|
|
33
|
+
return _jsx(Toggle, { ...props, kind: "radio" });
|
|
34
|
+
}
|
|
35
|
+
function Toggle(props) {
|
|
36
|
+
const { kind, label, description, error, size = "md", labelPosition = "end", indeterminate, readOnly = false, readOnlyMessage = kind === "switch" ? "Read-only: this setting cannot be changed." : kind === "radio" ? "Read-only: this choice cannot be changed." : "Read-only: this option cannot be changed.", required, validate, fieldName, validateOn, onValueChange, className, inputClassName, style, id, checked, disabled, ref, onChange, onClick, onKeyDown, ...inputProps } = props;
|
|
37
|
+
const inputRef = useRef(null);
|
|
38
|
+
const field = useChoiceField({ id, label, fieldName, error, validateOn, nameFromLabel: false }, {
|
|
39
|
+
check: ([input], name) => (input ? validateCheck(input.checked, { required, validate }, name) : null),
|
|
40
|
+
checkKeys: [checked, required, validate],
|
|
41
|
+
});
|
|
42
|
+
const setRef = useCallback((node) => {
|
|
43
|
+
inputRef.current = node;
|
|
44
|
+
assignRef(ref, node);
|
|
45
|
+
}, [ref]);
|
|
46
|
+
useLayoutEffect(() => {
|
|
47
|
+
if (inputRef.current && kind === "checkbox")
|
|
48
|
+
inputRef.current.indeterminate = Boolean(indeterminate);
|
|
49
|
+
}, [indeterminate, kind]);
|
|
50
|
+
// A lock after the label, and a note under the row when someone tries to change it. With the label first, the
|
|
51
|
+
// control sits at the end of the row, so the note lines up with that end.
|
|
52
|
+
const readOnlyNotice = useReadOnlyNotice(readOnly && !disabled, inputRef, readOnlyMessage, {
|
|
53
|
+
anchor: optionAnchor,
|
|
54
|
+
align: labelPosition === "start" ? "end" : "start",
|
|
55
|
+
typing: false,
|
|
56
|
+
lock: "label",
|
|
57
|
+
});
|
|
58
|
+
function handleClick(event) {
|
|
59
|
+
if (!readOnly) {
|
|
60
|
+
onClick?.(event);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
preventChange(event);
|
|
64
|
+
readOnlyNotice.onClick(event);
|
|
65
|
+
}
|
|
66
|
+
function handleKeyDown(event) {
|
|
67
|
+
readOnlyNotice.onKeyDown(event);
|
|
68
|
+
onKeyDown?.(event);
|
|
69
|
+
}
|
|
70
|
+
function handleBlur(event) {
|
|
71
|
+
if (leavesField(event))
|
|
72
|
+
readOnlyNotice.hide();
|
|
73
|
+
field.onBlur(event);
|
|
74
|
+
}
|
|
75
|
+
function handleChange(event) {
|
|
76
|
+
if (readOnly)
|
|
77
|
+
return;
|
|
78
|
+
field.onChange();
|
|
79
|
+
onChange?.(event);
|
|
80
|
+
onValueChange?.(event.currentTarget.checked, event);
|
|
81
|
+
}
|
|
82
|
+
const descriptionId = hasContent(description) ? `${field.fieldId}-description` : undefined;
|
|
83
|
+
// With a description, the name comes from the label text alone; the description is read as a description.
|
|
84
|
+
const labelId = descriptionId && hasContent(label) && !inputProps["aria-label"] ? `${field.fieldId}-label` : undefined;
|
|
85
|
+
const describedBy = [inputProps["aria-describedby"], descriptionId, field.error && field.messageId].filter(Boolean).join(" ") || undefined;
|
|
86
|
+
return (_jsxs("div", { ref: field.containerRef, className: cx("nx-choice-field", className), style: style, "data-invalid": field.error ? true : undefined, "data-readonly": readOnly || undefined, onBlur: handleBlur, children: [_jsxs("label", { className: cx("nx-choice", `nx-choice--${size}`, labelPosition === "start" && "nx-choice--start"), children: [_jsx("input", { ...inputProps, [OPTION_ATTRIBUTE]: 0, ref: setRef, id: field.fieldId, type: kind === "radio" ? "radio" : "checkbox", role: kind === "switch" ? "switch" : undefined, className: cx("nx-choice__control", `nx-choice__control--${kind}`, inputClassName), checked: checked, disabled: disabled, "aria-invalid": field.error ? true : undefined, "aria-required": required || undefined, "aria-readonly": readOnly || undefined, "aria-labelledby": inputProps["aria-labelledby"] ?? labelId, "aria-describedby": describedBy, onClick: handleClick, onKeyDown: handleKeyDown, onChange: handleChange, onInvalid: field.onInvalid }), (hasContent(label) || descriptionId) && (_jsxs("span", { className: "nx-choice__text", children: [hasContent(label) && (_jsxs("span", { id: labelId, className: "nx-choice__label", children: [label, readOnlyNotice.lock] })), descriptionId && (_jsx("span", { id: descriptionId, className: "nx-choice__description", children: description }))] }))] }), _jsx(GroupFooter, { id: field.messageId, error: field.error }), readOnlyNotice.notice] }));
|
|
87
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ChangeEvent, type ReactNode } from "react";
|
|
2
|
+
import type { OptionValue, SelectionRules } from "../../Interfaces/InputInterfaces.ts";
|
|
3
|
+
import { type ChoiceGroupProps } from "./ChoiceField.tsx";
|
|
4
|
+
export interface CheckBoxGroupProps<T extends OptionValue = string> extends ChoiceGroupProps<T>, Omit<SelectionRules<T>, "required"> {
|
|
5
|
+
/** The chosen values. With `value`, the group is controlled; pair it with `onValueChange`. */
|
|
6
|
+
value?: readonly T[];
|
|
7
|
+
defaultValue?: readonly T[];
|
|
8
|
+
/** Adds a "Select all" box above the options (`true`), or one with this label. Not shown with `max`. */
|
|
9
|
+
selectAll?: boolean | ReactNode;
|
|
10
|
+
/** The chosen values, in option order, after each change. */
|
|
11
|
+
onValueChange?: (values: T[], event: ChangeEvent<HTMLElement>) => void;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Several choices from a list of options, such as amenities or permissions. `min` and `max` limit the count; at `max`,
|
|
15
|
+
* the other options are disabled and a count shows under the group. Clicking an option re-renders nothing. Read-only
|
|
16
|
+
* (default note "Read-only: these options cannot be changed.") shows a lock after the label and a note under a clicked option.
|
|
17
|
+
*
|
|
18
|
+
* @example <CheckBoxGroup name="amenities" label="Amenities" columns={3} options={[{ value: "wifi", label: "Wi-Fi" }]} />
|
|
19
|
+
*/
|
|
20
|
+
export declare function CheckBoxGroup<T extends OptionValue = string>(props: CheckBoxGroupProps<T>): import("react").JSX.Element;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useRef } from "react";
|
|
4
|
+
import { validateSelection } from "../../Helpers/ValidationHelpers.js";
|
|
5
|
+
import { ChoiceItem, ChoiceLegend, GroupFooter, groupLayout, leavesField, OPTION_ATTRIBUTE, optionAnchor, useChoiceField } from "./ChoiceField.js";
|
|
6
|
+
import { useReadOnlyNotice } from "./ReadOnlyNotice.js";
|
|
7
|
+
/**
|
|
8
|
+
* Several choices from a list of options, such as amenities or permissions. `min` and `max` limit the count; at `max`,
|
|
9
|
+
* the other options are disabled and a count shows under the group. Clicking an option re-renders nothing. Read-only
|
|
10
|
+
* (default note "Read-only: these options cannot be changed.") shows a lock after the label and a note under a clicked option.
|
|
11
|
+
*
|
|
12
|
+
* @example <CheckBoxGroup name="amenities" label="Amenities" columns={3} options={[{ value: "wifi", label: "Wi-Fi" }]} />
|
|
13
|
+
*/
|
|
14
|
+
export function CheckBoxGroup(props) {
|
|
15
|
+
const { options, name, label, hint, required, min, max, validate, value, defaultValue, selectAll, readOnly = false, disabled, onValueChange } = props;
|
|
16
|
+
const { readOnlyMessage = "Read-only: these options cannot be changed." } = props;
|
|
17
|
+
const { size = "md" } = props;
|
|
18
|
+
const allRef = useRef(null);
|
|
19
|
+
const countRef = useRef(null);
|
|
20
|
+
const chosen = (inputs) => inputs.filter(input => input.checked).map(input => options[Number(input.getAttribute(OPTION_ATTRIBUTE))].value);
|
|
21
|
+
const field = useChoiceField(props, {
|
|
22
|
+
check: (inputs, fieldName) => validateSelection(chosen(inputs), { required, min, max, validate }, fieldName),
|
|
23
|
+
checkKeys: [value, required, min, max, validate, options],
|
|
24
|
+
sync,
|
|
25
|
+
});
|
|
26
|
+
/** Writes the count, the select-all state, and the limit straight to the DOM. */
|
|
27
|
+
function sync(inputs) {
|
|
28
|
+
const count = inputs.filter(input => input.checked).length;
|
|
29
|
+
if (countRef.current)
|
|
30
|
+
countRef.current.textContent = String(count);
|
|
31
|
+
if (max !== undefined) {
|
|
32
|
+
for (const input of inputs) {
|
|
33
|
+
const limited = count >= max && !input.checked;
|
|
34
|
+
if (limited && !input.disabled) {
|
|
35
|
+
input.disabled = true;
|
|
36
|
+
input.dataset.nxLimit = "";
|
|
37
|
+
}
|
|
38
|
+
else if (!limited && input.dataset.nxLimit !== undefined) {
|
|
39
|
+
input.disabled = false;
|
|
40
|
+
delete input.dataset.nxLimit;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const all = allRef.current;
|
|
45
|
+
if (all) {
|
|
46
|
+
const available = inputs.filter(input => !input.disabled);
|
|
47
|
+
const availableChosen = available.filter(input => input.checked).length;
|
|
48
|
+
all.checked = available.length > 0 && availableChosen === available.length;
|
|
49
|
+
all.indeterminate = availableChosen > 0 && availableChosen < available.length;
|
|
50
|
+
all.disabled = available.length === 0;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function toggleAll(event) {
|
|
54
|
+
if (readOnly)
|
|
55
|
+
return;
|
|
56
|
+
const checked = event.currentTarget.checked;
|
|
57
|
+
for (const input of field.inputs())
|
|
58
|
+
if (!input.disabled)
|
|
59
|
+
input.checked = checked;
|
|
60
|
+
}
|
|
61
|
+
function handleChange(event) {
|
|
62
|
+
if (readOnly)
|
|
63
|
+
return;
|
|
64
|
+
field.onChange();
|
|
65
|
+
onValueChange?.(chosen(field.inputs()), event);
|
|
66
|
+
}
|
|
67
|
+
// Clicks and focus bubble to the fieldset, so one handler covers every option.
|
|
68
|
+
const readOnlyNotice = useReadOnlyNotice(readOnly && !disabled, field.containerRef, readOnlyMessage, { anchor: optionAnchor, typing: false, lock: "label" });
|
|
69
|
+
function handleBlur(event) {
|
|
70
|
+
if (leavesField(event))
|
|
71
|
+
readOnlyNotice.hide();
|
|
72
|
+
field.onBlur(event);
|
|
73
|
+
}
|
|
74
|
+
const invalid = Boolean(field.error);
|
|
75
|
+
const layout = groupLayout(props);
|
|
76
|
+
const groupName = name ?? field.fieldId;
|
|
77
|
+
const selected = value ?? defaultValue ?? [];
|
|
78
|
+
const showAll = Boolean(selectAll) && max === undefined;
|
|
79
|
+
return (_jsxs("fieldset", { ref: field.containerRef, id: field.fieldId, className: layout.className, style: props.style, disabled: disabled, "data-invalid": invalid || undefined, "data-readonly": readOnly || undefined, "aria-label": props["aria-label"], "aria-labelledby": props["aria-labelledby"], "aria-describedby": field.error || hint ? field.messageId : undefined, onChange: handleChange, onClick: readOnlyNotice.onClick, onKeyDown: readOnlyNotice.onKeyDown, onBlur: handleBlur, children: [_jsx(ChoiceLegend, { label: label, required: required, lock: readOnlyNotice.lock }), showAll && (_jsxs("label", { className: `nx-choice nx-choice--${size} nx-choices__all`, children: [_jsx("input", { ref: allRef, type: "checkbox", className: "nx-choice__control nx-choice__control--checkbox", "aria-controls": options.map((_, index) => `${field.fieldId}-${index}`).join(" "), onClick: readOnly ? event => event.preventDefault() : undefined, onChange: toggleAll }), _jsx("span", { className: "nx-choice__text", children: _jsx("span", { className: "nx-choice__label", children: selectAll === true ? "Select all" : selectAll }) })] })), _jsx("div", { className: "nx-choices__options", style: layout.optionsStyle, children: options.map((option, index) => (_jsx(ChoiceItem, { type: "checkbox", groupId: field.fieldId, name: groupName, option: option, index: index, size: size, checked: value ? value.includes(option.value) : undefined, defaultChecked: defaultValue?.includes(option.value), invalid: invalid, readOnly: readOnly, onInvalid: field.onInvalid }, String(option.value)))) }), _jsx(GroupFooter, { id: field.messageId, hint: hint, error: field.error, aside: max !== undefined && (_jsxs(_Fragment, { children: [_jsx("span", { ref: countRef, children: options.filter(option => selected.includes(option.value)).length }), "/", max] })) }), readOnlyNotice.notice] }));
|
|
80
|
+
}
|