@nexub/react 6.0.0 → 6.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.
- package/accessibility.json +64 -1
- package/dist/components/DateTimePicker/DateTimePicker.d.ts +20 -0
- package/dist/components/DateTimePicker/DateTimePicker.js +67 -0
- package/dist/components/DateTimePicker/index.d.ts +2 -0
- package/dist/components/DateTimePicker/index.js +4 -0
- package/dist/components/EventCalendar/EventCalendar.d.ts +38 -0
- package/dist/components/EventCalendar/EventCalendar.js +204 -0
- package/dist/components/EventCalendar/index.d.ts +2 -0
- package/dist/components/EventCalendar/index.js +4 -0
- package/dist/components/Fieldset/Fieldset.d.ts +11 -0
- package/dist/components/Fieldset/Fieldset.js +23 -0
- package/dist/components/Fieldset/index.d.ts +2 -0
- package/dist/components/Fieldset/index.js +4 -0
- package/dist/components/Form/Form.d.ts +33 -0
- package/dist/components/Form/Form.js +79 -0
- package/dist/components/Form/index.d.ts +2 -0
- package/dist/components/Form/index.js +5 -0
- package/dist/components/FormField/FormField.d.ts +2 -0
- package/dist/components/FormField/FormField.js +10 -5
- package/dist/components/InlineEdit/InlineEdit.d.ts +21 -0
- package/dist/components/InlineEdit/InlineEdit.js +103 -0
- package/dist/components/InlineEdit/index.d.ts +2 -0
- package/dist/components/InlineEdit/index.js +4 -0
- package/dist/components/TimePicker/TimePicker.d.ts +15 -0
- package/dist/components/TimePicker/TimePicker.js +26 -0
- package/dist/components/TimePicker/index.d.ts +2 -0
- package/dist/components/TimePicker/index.js +4 -0
- package/dist/hooks/useUnsavedChanges.d.ts +6 -0
- package/dist/hooks/useUnsavedChanges.js +15 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +16 -1
- package/dist/styles.css +1 -1
- package/package.json +6 -6
|
@@ -5,6 +5,7 @@ import { cn } from "../../utils/cn.js";
|
|
|
5
5
|
import { Label } from "../Label/Label.js";
|
|
6
6
|
import { HelperText } from "../HelperText/HelperText.js";
|
|
7
7
|
import { FormError } from "../FormError/FormError.js";
|
|
8
|
+
import { useForm } from "../Form/Form.js";
|
|
8
9
|
const FormFieldContext = React.createContext(null);
|
|
9
10
|
function useFormField() {
|
|
10
11
|
const ctx = React.useContext(FormFieldContext);
|
|
@@ -13,9 +14,12 @@ function useFormField() {
|
|
|
13
14
|
}
|
|
14
15
|
return ctx;
|
|
15
16
|
}
|
|
16
|
-
const Root = React.forwardRef(function Root2({ id: idProp, name, invalid, required, disabled, className, children, ...props }, ref) {
|
|
17
|
+
const Root = React.forwardRef(function Root2({ id: idProp, name, invalid: invalidProp, required, disabled, className, children, ...props }, ref) {
|
|
17
18
|
const generatedId = React.useId();
|
|
18
19
|
const id = idProp ?? generatedId;
|
|
20
|
+
const form = useForm();
|
|
21
|
+
const formError = name ? form?.errors[name] : void 0;
|
|
22
|
+
const invalid = invalidProp ?? (formError ? true : void 0);
|
|
19
23
|
const value = React.useMemo(
|
|
20
24
|
() => ({
|
|
21
25
|
id,
|
|
@@ -24,9 +28,10 @@ const Root = React.forwardRef(function Root2({ id: idProp, name, invalid, requir
|
|
|
24
28
|
required,
|
|
25
29
|
disabled,
|
|
26
30
|
descriptionId: `${id}-description`,
|
|
27
|
-
errorId: `${id}-error
|
|
31
|
+
errorId: `${id}-error`,
|
|
32
|
+
formError
|
|
28
33
|
}),
|
|
29
|
-
[id, name, invalid, required, disabled]
|
|
34
|
+
[id, name, invalid, required, disabled, formError]
|
|
30
35
|
);
|
|
31
36
|
return /* @__PURE__ */ jsx(FormFieldContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { ref, className: cn("flex flex-col gap-1.5", className), ...props, children }) });
|
|
32
37
|
});
|
|
@@ -79,9 +84,9 @@ function FieldError({
|
|
|
79
84
|
children,
|
|
80
85
|
...props
|
|
81
86
|
}) {
|
|
82
|
-
const { errorId, invalid } = useFormField();
|
|
87
|
+
const { errorId, invalid, formError } = useFormField();
|
|
83
88
|
if (!invalid) return null;
|
|
84
|
-
return /* @__PURE__ */ jsx(FormError, { id: errorId, className, ...props, children });
|
|
89
|
+
return /* @__PURE__ */ jsx(FormError, { id: errorId, className, ...props, children: children ?? formError });
|
|
85
90
|
}
|
|
86
91
|
FieldError.displayName = "FormField.Error";
|
|
87
92
|
const FormField = { Root, Label: FieldLabel, Control, Description, Error: FieldError };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
export interface InlineEditProps {
|
|
3
|
+
value: string;
|
|
4
|
+
/**
|
|
5
|
+
* Called with the new text on Enter or blur (only when it changed). Return a
|
|
6
|
+
* promise to keep the field open and busy until it settles; a rejection keeps
|
|
7
|
+
* it open and marks it invalid so the person can retry or press Escape.
|
|
8
|
+
*/
|
|
9
|
+
onSave: (value: string) => void | Promise<unknown>;
|
|
10
|
+
/** Names the field: the input's label and "Edit ‹label›" on the button. */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Shown when `value` is empty. */
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Text that turns into an input on click. Enter or blur saves, Escape reverts,
|
|
19
|
+
* and focus returns to the text afterwards.
|
|
20
|
+
*/
|
|
21
|
+
export declare function InlineEdit({ value, onSave, label, placeholder, disabled, className, }: InlineEditProps): React.JSX.Element;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { cn } from "../../utils/cn.js";
|
|
5
|
+
import { Input } from "../Input/Input.js";
|
|
6
|
+
import { useNexubMessages } from "../NexubProvider/NexubProvider.js";
|
|
7
|
+
function InlineEdit({
|
|
8
|
+
value,
|
|
9
|
+
onSave,
|
|
10
|
+
label,
|
|
11
|
+
placeholder,
|
|
12
|
+
disabled,
|
|
13
|
+
className
|
|
14
|
+
}) {
|
|
15
|
+
const t = useNexubMessages();
|
|
16
|
+
const [editing, setEditing] = React.useState(false);
|
|
17
|
+
const [draft, setDraft] = React.useState(value);
|
|
18
|
+
const [saving, setSaving] = React.useState(false);
|
|
19
|
+
const [failed, setFailed] = React.useState(false);
|
|
20
|
+
const buttonRef = React.useRef(null);
|
|
21
|
+
const returnFocus = React.useRef(false);
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
if (!editing && returnFocus.current) {
|
|
24
|
+
returnFocus.current = false;
|
|
25
|
+
buttonRef.current?.focus();
|
|
26
|
+
}
|
|
27
|
+
}, [editing]);
|
|
28
|
+
const close = (refocus) => {
|
|
29
|
+
returnFocus.current = refocus;
|
|
30
|
+
setFailed(false);
|
|
31
|
+
setEditing(false);
|
|
32
|
+
};
|
|
33
|
+
const save = async (refocus) => {
|
|
34
|
+
if (saving) return;
|
|
35
|
+
if (draft === value) return close(refocus);
|
|
36
|
+
const result = onSave(draft);
|
|
37
|
+
if (!(result instanceof Promise)) return close(refocus);
|
|
38
|
+
setSaving(true);
|
|
39
|
+
try {
|
|
40
|
+
await result;
|
|
41
|
+
close(refocus);
|
|
42
|
+
} catch {
|
|
43
|
+
setFailed(true);
|
|
44
|
+
} finally {
|
|
45
|
+
setSaving(false);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
if (editing) {
|
|
49
|
+
return /* @__PURE__ */ jsx(
|
|
50
|
+
Input,
|
|
51
|
+
{
|
|
52
|
+
autoFocus: true,
|
|
53
|
+
"aria-label": label,
|
|
54
|
+
value: draft,
|
|
55
|
+
invalid: failed,
|
|
56
|
+
"aria-busy": saving || void 0,
|
|
57
|
+
readOnly: saving,
|
|
58
|
+
wrapperClassName: className,
|
|
59
|
+
onChange: (e) => {
|
|
60
|
+
setDraft(e.target.value);
|
|
61
|
+
setFailed(false);
|
|
62
|
+
},
|
|
63
|
+
onKeyDown: (e) => {
|
|
64
|
+
if (e.key === "Enter") {
|
|
65
|
+
e.preventDefault();
|
|
66
|
+
void save(true);
|
|
67
|
+
} else if (e.key === "Escape") {
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
e.stopPropagation();
|
|
70
|
+
close(true);
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
onBlur: () => {
|
|
74
|
+
if (!failed) void save(false);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
return /* @__PURE__ */ jsx(
|
|
80
|
+
"button",
|
|
81
|
+
{
|
|
82
|
+
ref: buttonRef,
|
|
83
|
+
type: "button",
|
|
84
|
+
disabled,
|
|
85
|
+
"aria-label": `${t.editField(label)}, ${value || placeholder || ""}`,
|
|
86
|
+
onClick: () => {
|
|
87
|
+
setDraft(value);
|
|
88
|
+
setEditing(true);
|
|
89
|
+
},
|
|
90
|
+
className: cn(
|
|
91
|
+
"-mx-2 inline-flex min-h-control-sm max-w-full items-center rounded-shape-control px-2 text-start",
|
|
92
|
+
"text-body-md text-[var(--color-on-surface)] hover:bg-[var(--color-surface-container-high)]",
|
|
93
|
+
"disabled:cursor-not-allowed disabled:hover:bg-transparent",
|
|
94
|
+
!value && "text-[var(--color-on-surface-muted)]",
|
|
95
|
+
className
|
|
96
|
+
),
|
|
97
|
+
children: /* @__PURE__ */ jsx("span", { className: "truncate", children: value || placeholder })
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
InlineEdit
|
|
103
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { type InputProps } from '../Input/Input.js';
|
|
3
|
+
export interface TimePickerProps extends Omit<InputProps, 'type' | 'value' | 'defaultValue' | 'onChange' | 'step'> {
|
|
4
|
+
/** 24-hour `"HH:mm"`; the field shows 12 or 24 hours from the reader's locale. */
|
|
5
|
+
value?: string;
|
|
6
|
+
defaultValue?: string;
|
|
7
|
+
onValueChange?: (value: string) => void;
|
|
8
|
+
/** Minutes between steps for the arrow keys and the picker. @default 1 */
|
|
9
|
+
minuteStep?: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* A time field built on the native `<input type="time">`: locale-aware 12/24h,
|
|
13
|
+
* keyboard segments, the platform's picker on phones, and form submission.
|
|
14
|
+
*/
|
|
15
|
+
export declare const TimePicker: React.ForwardRefExoticComponent<TimePickerProps & React.RefAttributes<HTMLInputElement>>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { Input } from "../Input/Input.js";
|
|
5
|
+
import { useControllableState } from "../../hooks/useControllableState.js";
|
|
6
|
+
const TimePicker = React.forwardRef(function TimePicker2({ value: valueProp, defaultValue, onValueChange, minuteStep = 1, ...props }, ref) {
|
|
7
|
+
const [value = "", setValue] = useControllableState({
|
|
8
|
+
value: valueProp,
|
|
9
|
+
defaultValue: defaultValue ?? "",
|
|
10
|
+
onChange: onValueChange
|
|
11
|
+
});
|
|
12
|
+
return /* @__PURE__ */ jsx(
|
|
13
|
+
Input,
|
|
14
|
+
{
|
|
15
|
+
ref,
|
|
16
|
+
type: "time",
|
|
17
|
+
step: minuteStep * 60,
|
|
18
|
+
value,
|
|
19
|
+
onChange: (e) => setValue(e.target.value),
|
|
20
|
+
...props
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
});
|
|
24
|
+
export {
|
|
25
|
+
TimePicker
|
|
26
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asks the browser to confirm before the page unloads (reload, close, external
|
|
3
|
+
* link) while `dirty` is true. In-app navigation belongs to your router: block
|
|
4
|
+
* it there with the same flag.
|
|
5
|
+
*/
|
|
6
|
+
export declare function useUnsavedChanges(dirty: boolean): void;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
function useUnsavedChanges(dirty) {
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
if (!dirty) return;
|
|
6
|
+
const onBeforeUnload = (event) => {
|
|
7
|
+
event.preventDefault();
|
|
8
|
+
};
|
|
9
|
+
window.addEventListener("beforeunload", onBeforeUnload);
|
|
10
|
+
return () => window.removeEventListener("beforeunload", onBeforeUnload);
|
|
11
|
+
}, [dirty]);
|
|
12
|
+
}
|
|
13
|
+
export {
|
|
14
|
+
useUnsavedChanges
|
|
15
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { useDisclosure } from './hooks/useDisclosure.js';
|
|
|
7
7
|
export { useIsomorphicLayoutEffect } from './hooks/useIsomorphicLayoutEffect.js';
|
|
8
8
|
export { useMediaQuery } from './hooks/useMediaQuery.js';
|
|
9
9
|
export { useMergedRefs } from './hooks/useMergedRefs.js';
|
|
10
|
+
export { useUnsavedChanges } from './hooks/useUnsavedChanges.js';
|
|
10
11
|
export { usePointerSheen } from './hooks/usePointerSheen.js';
|
|
11
12
|
export { PromptInput } from './components/PromptInput/index.js';
|
|
12
13
|
export type { PromptInputProps, PromptInputMessages, PromptInputStatus } from './components/PromptInput/index.js';
|
|
@@ -213,6 +214,18 @@ export type { ErrorStateProps } from './components/ErrorState/index.js';
|
|
|
213
214
|
export { FileUpload, fileAccepted } from './components/FileUpload/index.js';
|
|
214
215
|
export type { FileUploadRootProps, FileUploadItemProps, FileRejection } from './components/FileUpload/index.js';
|
|
215
216
|
export { OTPInput } from './components/OTPInput/index.js';
|
|
217
|
+
export { Form, useForm } from './components/Form/index.js';
|
|
218
|
+
export type { FormProps, FormErrorItem, FormErrors, FormContextValue } from './components/Form/index.js';
|
|
219
|
+
export { Fieldset } from './components/Fieldset/index.js';
|
|
220
|
+
export type { FieldsetProps } from './components/Fieldset/index.js';
|
|
221
|
+
export { InlineEdit } from './components/InlineEdit/index.js';
|
|
222
|
+
export type { InlineEditProps } from './components/InlineEdit/index.js';
|
|
223
|
+
export { TimePicker } from './components/TimePicker/index.js';
|
|
224
|
+
export type { TimePickerProps } from './components/TimePicker/index.js';
|
|
225
|
+
export { DateTimePicker } from './components/DateTimePicker/index.js';
|
|
226
|
+
export type { DateTimePickerProps } from './components/DateTimePicker/index.js';
|
|
227
|
+
export { EventCalendar } from './components/EventCalendar/index.js';
|
|
228
|
+
export type { EventCalendarProps, EventCalendarView, CalendarEvent, EventTone } from './components/EventCalendar/index.js';
|
|
216
229
|
export type { OTPInputProps } from './components/OTPInput/index.js';
|
|
217
230
|
export { DatePicker } from './components/DatePicker/index.js';
|
|
218
231
|
export type { DatePickerProps } from './components/DatePicker/index.js';
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { useDisclosure } from "./hooks/useDisclosure.js";
|
|
|
6
6
|
import { useIsomorphicLayoutEffect } from "./hooks/useIsomorphicLayoutEffect.js";
|
|
7
7
|
import { useMediaQuery } from "./hooks/useMediaQuery.js";
|
|
8
8
|
import { useMergedRefs } from "./hooks/useMergedRefs.js";
|
|
9
|
+
import { useUnsavedChanges } from "./hooks/useUnsavedChanges.js";
|
|
9
10
|
import { usePointerSheen } from "./hooks/usePointerSheen.js";
|
|
10
11
|
import { PromptInput } from "./components/PromptInput/index.js";
|
|
11
12
|
import { ThinkingIndicator } from "./components/ThinkingIndicator/index.js";
|
|
@@ -120,6 +121,12 @@ import { EmptyState, EmptyStatePreset } from "./components/EmptyState/index.js";
|
|
|
120
121
|
import { ErrorState } from "./components/ErrorState/index.js";
|
|
121
122
|
import { FileUpload, fileAccepted } from "./components/FileUpload/index.js";
|
|
122
123
|
import { OTPInput } from "./components/OTPInput/index.js";
|
|
124
|
+
import { Form, useForm } from "./components/Form/index.js";
|
|
125
|
+
import { Fieldset } from "./components/Fieldset/index.js";
|
|
126
|
+
import { InlineEdit } from "./components/InlineEdit/index.js";
|
|
127
|
+
import { TimePicker } from "./components/TimePicker/index.js";
|
|
128
|
+
import { DateTimePicker } from "./components/DateTimePicker/index.js";
|
|
129
|
+
import { EventCalendar } from "./components/EventCalendar/index.js";
|
|
123
130
|
import { DatePicker } from "./components/DatePicker/index.js";
|
|
124
131
|
import { DateRangePicker } from "./components/DateRangePicker/index.js";
|
|
125
132
|
import { todayIn } from "./components/DateRangePicker/index.js";
|
|
@@ -165,6 +172,7 @@ export {
|
|
|
165
172
|
DataTable,
|
|
166
173
|
DatePicker,
|
|
167
174
|
DateRangePicker,
|
|
175
|
+
DateTimePicker,
|
|
168
176
|
Dialog,
|
|
169
177
|
DiffView,
|
|
170
178
|
DonutChart,
|
|
@@ -173,8 +181,10 @@ export {
|
|
|
173
181
|
EmptyState,
|
|
174
182
|
EmptyStatePreset,
|
|
175
183
|
ErrorState,
|
|
184
|
+
EventCalendar,
|
|
176
185
|
EyebrowChip,
|
|
177
186
|
Fade,
|
|
187
|
+
Fieldset,
|
|
178
188
|
FileUpload,
|
|
179
189
|
FilterBuilder,
|
|
180
190
|
FilterChip,
|
|
@@ -182,6 +192,7 @@ export {
|
|
|
182
192
|
Flex,
|
|
183
193
|
Float,
|
|
184
194
|
FloatingInput,
|
|
195
|
+
Form,
|
|
185
196
|
FormError,
|
|
186
197
|
FormField,
|
|
187
198
|
Funnel,
|
|
@@ -193,6 +204,7 @@ export {
|
|
|
193
204
|
HelperText,
|
|
194
205
|
HoverCard,
|
|
195
206
|
IconButton,
|
|
207
|
+
InlineEdit,
|
|
196
208
|
Input,
|
|
197
209
|
Kbd,
|
|
198
210
|
KeyboardShortcut,
|
|
@@ -259,6 +271,7 @@ export {
|
|
|
259
271
|
ThemeProvider,
|
|
260
272
|
ThemeScript,
|
|
261
273
|
ThinkingIndicator,
|
|
274
|
+
TimePicker,
|
|
262
275
|
Toast,
|
|
263
276
|
Toaster,
|
|
264
277
|
Toggle,
|
|
@@ -312,6 +325,7 @@ export {
|
|
|
312
325
|
transitionName,
|
|
313
326
|
useControllableState,
|
|
314
327
|
useDisclosure,
|
|
328
|
+
useForm,
|
|
315
329
|
useIsomorphicLayoutEffect,
|
|
316
330
|
useLocale,
|
|
317
331
|
useMediaQuery,
|
|
@@ -321,5 +335,6 @@ export {
|
|
|
321
335
|
usePrefersReducedMotion,
|
|
322
336
|
useScopedSeed,
|
|
323
337
|
useTheme,
|
|
324
|
-
useToastStore
|
|
338
|
+
useToastStore,
|
|
339
|
+
useUnsavedChanges
|
|
325
340
|
};
|