@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.
Files changed (33) hide show
  1. package/accessibility.json +64 -1
  2. package/dist/components/DateTimePicker/DateTimePicker.d.ts +20 -0
  3. package/dist/components/DateTimePicker/DateTimePicker.js +67 -0
  4. package/dist/components/DateTimePicker/index.d.ts +2 -0
  5. package/dist/components/DateTimePicker/index.js +4 -0
  6. package/dist/components/EventCalendar/EventCalendar.d.ts +38 -0
  7. package/dist/components/EventCalendar/EventCalendar.js +204 -0
  8. package/dist/components/EventCalendar/index.d.ts +2 -0
  9. package/dist/components/EventCalendar/index.js +4 -0
  10. package/dist/components/Fieldset/Fieldset.d.ts +11 -0
  11. package/dist/components/Fieldset/Fieldset.js +23 -0
  12. package/dist/components/Fieldset/index.d.ts +2 -0
  13. package/dist/components/Fieldset/index.js +4 -0
  14. package/dist/components/Form/Form.d.ts +33 -0
  15. package/dist/components/Form/Form.js +79 -0
  16. package/dist/components/Form/index.d.ts +2 -0
  17. package/dist/components/Form/index.js +5 -0
  18. package/dist/components/FormField/FormField.d.ts +2 -0
  19. package/dist/components/FormField/FormField.js +10 -5
  20. package/dist/components/InlineEdit/InlineEdit.d.ts +21 -0
  21. package/dist/components/InlineEdit/InlineEdit.js +103 -0
  22. package/dist/components/InlineEdit/index.d.ts +2 -0
  23. package/dist/components/InlineEdit/index.js +4 -0
  24. package/dist/components/TimePicker/TimePicker.d.ts +15 -0
  25. package/dist/components/TimePicker/TimePicker.js +26 -0
  26. package/dist/components/TimePicker/index.d.ts +2 -0
  27. package/dist/components/TimePicker/index.js +4 -0
  28. package/dist/hooks/useUnsavedChanges.d.ts +6 -0
  29. package/dist/hooks/useUnsavedChanges.js +15 -0
  30. package/dist/index.d.ts +13 -0
  31. package/dist/index.js +16 -1
  32. package/dist/styles.css +1 -1
  33. 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,2 @@
1
+ export { InlineEdit } from './InlineEdit.js';
2
+ export type { InlineEditProps } from './InlineEdit.js';
@@ -0,0 +1,4 @@
1
+ import { InlineEdit } from "./InlineEdit.js";
2
+ export {
3
+ InlineEdit
4
+ };
@@ -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,2 @@
1
+ export { TimePicker } from './TimePicker.js';
2
+ export type { TimePickerProps } from './TimePicker.js';
@@ -0,0 +1,4 @@
1
+ import { TimePicker } from "./TimePicker.js";
2
+ export {
3
+ TimePicker
4
+ };
@@ -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
  };