@boxcustodia/library 2.0.0-alpha.22 → 2.0.0-alpha.23
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/dist/components/calendar/calendar.cjs.js +1 -1
- package/dist/components/calendar/calendar.es.js +43 -44
- package/dist/components/date-picker/date-input.cjs.js +1 -1
- package/dist/components/date-picker/date-input.es.js +160 -140
- package/dist/components/pagination/pagination.cjs.js +1 -1
- package/dist/components/pagination/pagination.es.js +37 -35
- package/dist/components/scroll-area/scroll-area.cjs.js +1 -1
- package/dist/components/scroll-area/scroll-area.es.js +4 -4
- package/dist/components/select/select.cjs.js +1 -1
- package/dist/components/select/select.es.js +94 -90
- package/dist/hooks/use-action/use-action.cjs.js +1 -0
- package/dist/hooks/use-action/use-action.es.js +41 -0
- package/dist/hooks/use-pagination/use-pagination.cjs.js +1 -1
- package/dist/hooks/use-pagination/use-pagination.es.js +77 -32
- package/dist/hooks/use-range-pagination/use-range-pagination.cjs.js +1 -1
- package/dist/hooks/use-range-pagination/use-range-pagination.es.js +8 -5
- package/dist/hooks/use-selection/use-selection.cjs.js +1 -1
- package/dist/hooks/use-selection/use-selection.es.js +95 -33
- package/dist/hooks/use-session-storage/use-session-storage.cjs.js +1 -0
- package/dist/hooks/use-session-storage/use-session-storage.es.js +57 -0
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +61 -63
- package/dist/src/components/select/select.d.ts +9 -2
- package/dist/src/hooks/index.d.ts +2 -3
- package/dist/src/hooks/internal/index.d.ts +1 -0
- package/dist/src/hooks/internal/serializer.d.ts +4 -0
- package/dist/src/hooks/use-action/index.d.ts +1 -0
- package/dist/src/hooks/use-action/use-action.d.ts +22 -0
- package/dist/src/hooks/use-local-storage/use-local-storage.d.ts +2 -4
- package/dist/src/hooks/use-pagination/use-pagination.d.ts +47 -32
- package/dist/src/hooks/use-range-pagination/use-range-pagination.d.ts +16 -10
- package/dist/src/hooks/use-selection/use-selection.d.ts +39 -45
- package/dist/src/hooks/use-session-storage/index.d.ts +1 -0
- package/dist/src/hooks/use-session-storage/use-session-storage.d.ts +11 -0
- package/package.json +1 -1
- package/src/components/calendar/calendar.tsx +10 -8
- package/src/components/combobox/combobox.stories.tsx +16 -0
- package/src/components/date-picker/date-input.tsx +23 -2
- package/src/components/form/form.tsx +3 -2
- package/src/components/pagination/pagination.tsx +5 -3
- package/src/components/scroll-area/scroll-area.tsx +2 -2
- package/src/components/select/select.tsx +14 -3
- package/src/hooks/index.ts +2 -3
- package/src/hooks/internal/index.ts +1 -0
- package/src/hooks/internal/serializer.ts +4 -0
- package/src/hooks/use-action/index.ts +1 -0
- package/src/hooks/{use-mutation/use-mutation.stories.tsx → use-action/use-action.stories.tsx} +34 -34
- package/src/hooks/{use-mutation/use-mutation.test.ts → use-action/use-action.test.ts} +53 -53
- package/src/hooks/{use-mutation/use-mutation.ts → use-action/use-action.ts} +20 -20
- package/src/hooks/use-click-outside/use-click-outside.stories.tsx +0 -1
- package/src/hooks/use-clipboard/use-clipboard.stories.tsx +0 -1
- package/src/hooks/use-document-title/use-document-title.stories.tsx +0 -1
- package/src/hooks/use-is-visible/use-is-visible.test.tsx +1 -1
- package/src/hooks/use-local-storage/use-local-storage.stories.tsx +0 -1
- package/src/hooks/use-local-storage/use-local-storage.ts +2 -5
- package/src/hooks/use-media-query/use-media-query.stories.tsx +0 -1
- package/src/hooks/use-pagination/use-pagination.stories.tsx +720 -57
- package/src/hooks/use-pagination/use-pagination.test.tsx +560 -48
- package/src/hooks/use-pagination/use-pagination.ts +266 -0
- package/src/hooks/use-prevent-page-close/use-prevent-page-close.stories.tsx +0 -1
- package/src/hooks/use-range-pagination/use-range-pagination.test.tsx +2 -2
- package/src/hooks/use-range-pagination/use-range-pagination.tsx +24 -21
- package/src/hooks/use-selection/use-selection.stories.tsx +339 -84
- package/src/hooks/use-selection/use-selection.test.tsx +417 -2
- package/src/hooks/use-selection/use-selection.ts +212 -102
- package/src/hooks/use-session-storage/index.ts +1 -0
- package/src/hooks/use-session-storage/use-session-storage.stories.tsx +122 -0
- package/src/hooks/use-session-storage/use-session-storage.test.ts +164 -0
- package/src/hooks/use-session-storage/use-session-storage.ts +115 -0
- package/dist/hooks/use-async/use-async.cjs.js +0 -1
- package/dist/hooks/use-async/use-async.es.js +0 -57
- package/dist/hooks/use-focus-trap/scope-tab.cjs.js +0 -1
- package/dist/hooks/use-focus-trap/scope-tab.es.js +0 -21
- package/dist/hooks/use-focus-trap/tabbable.cjs.js +0 -1
- package/dist/hooks/use-focus-trap/tabbable.es.js +0 -38
- package/dist/hooks/use-focus-trap/use-focus-trap.cjs.js +0 -1
- package/dist/hooks/use-focus-trap/use-focus-trap.es.js +0 -34
- package/dist/hooks/use-mutation/use-mutation.cjs.js +0 -1
- package/dist/hooks/use-mutation/use-mutation.es.js +0 -41
- package/dist/src/hooks/use-async/index.d.ts +0 -1
- package/dist/src/hooks/use-async/use-async.d.ts +0 -21
- package/dist/src/hooks/use-focus-trap/index.d.ts +0 -1
- package/dist/src/hooks/use-focus-trap/scope-tab.d.ts +0 -1
- package/dist/src/hooks/use-focus-trap/tabbable.d.ts +0 -4
- package/dist/src/hooks/use-focus-trap/use-focus-trap.d.ts +0 -1
- package/dist/src/hooks/use-mutation/index.d.ts +0 -1
- package/dist/src/hooks/use-mutation/use-mutation.d.ts +0 -22
- package/dist/src/hooks/use-mutation/use-mutation.test.d.ts +0 -1
- package/src/hooks/use-async/index.ts +0 -1
- package/src/hooks/use-async/use-async.stories.tsx +0 -272
- package/src/hooks/use-async/use-async.test.ts +0 -397
- package/src/hooks/use-async/use-async.ts +0 -135
- package/src/hooks/use-focus-trap/index.ts +0 -1
- package/src/hooks/use-focus-trap/scope-tab.ts +0 -38
- package/src/hooks/use-focus-trap/tabbable.ts +0 -70
- package/src/hooks/use-focus-trap/use-focus-trap.stories.tsx +0 -37
- package/src/hooks/use-focus-trap/use-focus-trap.test.ts +0 -355
- package/src/hooks/use-focus-trap/use-focus-trap.ts +0 -78
- package/src/hooks/use-mutation/index.ts +0 -1
- package/src/hooks/use-pagination/use-pagination.tsx +0 -84
- /package/dist/src/hooks/{use-async/use-async.test.d.ts → use-action/use-action.test.d.ts} +0 -0
- /package/dist/src/hooks/{use-focus-trap/use-focus-trap.test.d.ts → use-session-storage/use-session-storage.test.d.ts} +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
4
4
|
import { format, isDate } from "date-fns";
|
|
5
5
|
import { CalendarIcon } from "lucide-react";
|
|
6
|
-
import { KeyboardEvent } from "react";
|
|
6
|
+
import { KeyboardEvent, useState } from "react";
|
|
7
7
|
import {
|
|
8
8
|
Calendar,
|
|
9
9
|
FieldControl,
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
} from "../../components";
|
|
16
16
|
import { cn } from "../../lib";
|
|
17
17
|
import type { DateInputProps } from "./date-picker.model";
|
|
18
|
+
import { parseInputToDate } from "./date-picker.utils";
|
|
18
19
|
import { useDateInput } from "./use-date-input";
|
|
19
20
|
import { useDateInputPopover } from "./use-date-input-popover";
|
|
20
21
|
import { useHiddenFieldValue } from "./use-hidden-field-value";
|
|
@@ -67,6 +68,20 @@ export const DateInput = (props: DateInputProps) => {
|
|
|
67
68
|
inputRef,
|
|
68
69
|
} = useDateInputPopover({ disabled });
|
|
69
70
|
|
|
71
|
+
// Month shown by the calendar. Controlled so opening the popover can jump to
|
|
72
|
+
// the date currently typed in the input — even before it's committed.
|
|
73
|
+
const [calendarMonth, setCalendarMonth] = useState<Date | undefined>(
|
|
74
|
+
undefined,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const handleOpenChange = (next: boolean) => {
|
|
78
|
+
if (next) {
|
|
79
|
+
const typed = parseInputToDate(inputValue);
|
|
80
|
+
setCalendarMonth(typed ?? selectedDate ?? undefined);
|
|
81
|
+
}
|
|
82
|
+
setOpen(next);
|
|
83
|
+
};
|
|
84
|
+
|
|
70
85
|
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
|
|
71
86
|
handleInputKeyDown(event);
|
|
72
87
|
handlePopoverKeyDown(event);
|
|
@@ -95,7 +110,11 @@ export const DateInput = (props: DateInputProps) => {
|
|
|
95
110
|
});
|
|
96
111
|
|
|
97
112
|
return (
|
|
98
|
-
<PopoverRoot
|
|
113
|
+
<PopoverRoot
|
|
114
|
+
data-slot="date-input"
|
|
115
|
+
open={open}
|
|
116
|
+
onOpenChange={handleOpenChange}
|
|
117
|
+
>
|
|
99
118
|
<div
|
|
100
119
|
className={cn(
|
|
101
120
|
inputBaseClasses,
|
|
@@ -173,6 +192,8 @@ export const DateInput = (props: DateInputProps) => {
|
|
|
173
192
|
<Calendar
|
|
174
193
|
className="border-none"
|
|
175
194
|
mode="single"
|
|
195
|
+
month={calendarMonth}
|
|
196
|
+
onMonthChange={setCalendarMonth}
|
|
176
197
|
selected={selectedDate ?? undefined}
|
|
177
198
|
onSelect={(next) => {
|
|
178
199
|
if (!next || !isDate(next)) {
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import { Form as FormPrimitive } from "@base-ui/react/form";
|
|
4
4
|
import type React from "react";
|
|
5
5
|
|
|
6
|
-
export type FormProps<
|
|
7
|
-
|
|
6
|
+
export type FormProps<
|
|
7
|
+
TValues extends Record<string, any> = Record<string, any>,
|
|
8
|
+
> = FormPrimitive.Props<TValues>;
|
|
8
9
|
|
|
9
10
|
export function Form<TValues extends Record<string, any> = Record<string, any>>(
|
|
10
11
|
props: FormProps<TValues>,
|
|
@@ -130,9 +130,9 @@ export function PaginationRoot({
|
|
|
130
130
|
const pagination = useRangePagination({
|
|
131
131
|
totalItems,
|
|
132
132
|
pageSize,
|
|
133
|
-
currentPage,
|
|
134
|
-
defaultCurrentPage,
|
|
135
|
-
onCurrentPageChange,
|
|
133
|
+
page: currentPage,
|
|
134
|
+
defaultPage: defaultCurrentPage,
|
|
135
|
+
onPageChange: onCurrentPageChange,
|
|
136
136
|
siblingCount,
|
|
137
137
|
});
|
|
138
138
|
|
|
@@ -144,6 +144,8 @@ export function PaginationRoot({
|
|
|
144
144
|
const value = useMemo<PaginationContextValue>(
|
|
145
145
|
() => ({
|
|
146
146
|
...pagination,
|
|
147
|
+
currentPage: pagination.page,
|
|
148
|
+
maxPage: pagination.pageCount,
|
|
147
149
|
pageSize,
|
|
148
150
|
totalItems,
|
|
149
151
|
setPageSize,
|
|
@@ -16,12 +16,12 @@ export function ScrollArea({
|
|
|
16
16
|
}): React.ReactElement {
|
|
17
17
|
return (
|
|
18
18
|
<ScrollAreaPrimitive.Root
|
|
19
|
-
className={cn("
|
|
19
|
+
className={cn("flex min-h-0 size-full flex-col", className)}
|
|
20
20
|
{...props}
|
|
21
21
|
>
|
|
22
22
|
<ScrollAreaPrimitive.Viewport
|
|
23
23
|
className={cn(
|
|
24
|
-
"h-
|
|
24
|
+
"min-h-0 flex-1 rounded-[inherit] outline-none transition-shadows focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background data-has-overflow-y:overscroll-y-contain data-has-overflow-x:overscroll-x-contain",
|
|
25
25
|
scrollFade &&
|
|
26
26
|
"mask-t-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-start)))] mask-b-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-y-end)))] mask-l-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-start)))] mask-r-from-[calc(100%-min(var(--fade-size),var(--scroll-area-overflow-x-end)))] [--fade-size:1.5rem]",
|
|
27
27
|
scrollbarGutter &&
|
|
@@ -38,8 +38,12 @@ export function SelectRoot<
|
|
|
38
38
|
export function SelectTrigger({
|
|
39
39
|
className,
|
|
40
40
|
children,
|
|
41
|
+
icon,
|
|
41
42
|
...props
|
|
42
|
-
}: SelectPrimitive.Trigger.Props
|
|
43
|
+
}: SelectPrimitive.Trigger.Props & {
|
|
44
|
+
/** Icon rendered inside the trigger. Defaults to a chevrons-up-down glyph. */
|
|
45
|
+
icon?: React.ReactNode;
|
|
46
|
+
}): React.ReactElement {
|
|
43
47
|
return (
|
|
44
48
|
<SelectPrimitive.Trigger
|
|
45
49
|
className={cn(selectTriggerClasses, className)}
|
|
@@ -51,7 +55,7 @@ export function SelectTrigger({
|
|
|
51
55
|
data-slot="select-icon"
|
|
52
56
|
className="text-muted-foreground hover:text-foreground transition-colors"
|
|
53
57
|
>
|
|
54
|
-
<ChevronsUpDownIcon className={selectTriggerIconClassName} />
|
|
58
|
+
{icon ?? <ChevronsUpDownIcon className={selectTriggerIconClassName} />}
|
|
55
59
|
</SelectPrimitive.Icon>
|
|
56
60
|
</SelectPrimitive.Trigger>
|
|
57
61
|
);
|
|
@@ -59,12 +63,15 @@ export function SelectTrigger({
|
|
|
59
63
|
|
|
60
64
|
export type SelectButtonProps = useRender.ComponentProps<"button"> & {
|
|
61
65
|
ref?: React.Ref<HTMLButtonElement>;
|
|
66
|
+
/** Icon rendered inside the button. Defaults to a chevrons-up-down glyph. */
|
|
67
|
+
icon?: React.ReactNode;
|
|
62
68
|
};
|
|
63
69
|
|
|
64
70
|
export function SelectButton({
|
|
65
71
|
className,
|
|
66
72
|
render,
|
|
67
73
|
children,
|
|
74
|
+
icon,
|
|
68
75
|
...props
|
|
69
76
|
}: SelectButtonProps): React.ReactElement {
|
|
70
77
|
const typeValue: React.ButtonHTMLAttributes<HTMLButtonElement>["type"] =
|
|
@@ -76,7 +83,7 @@ export function SelectButton({
|
|
|
76
83
|
<span className="flex-1 truncate in-data-placeholder:text-muted-foreground/72">
|
|
77
84
|
{children}
|
|
78
85
|
</span>
|
|
79
|
-
<ChevronsUpDownIcon className={selectTriggerIconClassName} />
|
|
86
|
+
{icon ?? <ChevronsUpDownIcon className={selectTriggerIconClassName} />}
|
|
80
87
|
</>
|
|
81
88
|
),
|
|
82
89
|
className: cn(selectTriggerClasses, "min-w-0", className),
|
|
@@ -302,6 +309,8 @@ type SelectBaseProps<TItem = unknown> = Omit<
|
|
|
302
309
|
renderItem?: (item: TItem) => React.ReactNode;
|
|
303
310
|
placeholder?: string;
|
|
304
311
|
className?: string;
|
|
312
|
+
/** Icon rendered inside the trigger. Defaults to a chevrons-up-down glyph. */
|
|
313
|
+
icon?: React.ReactNode;
|
|
305
314
|
/** Styles applied to each internal slot. */
|
|
306
315
|
classNames?: {
|
|
307
316
|
/** Popup panel containing the item list. */
|
|
@@ -353,6 +362,7 @@ export function Select<TItem = unknown>(
|
|
|
353
362
|
multiple,
|
|
354
363
|
className,
|
|
355
364
|
classNames,
|
|
365
|
+
icon,
|
|
356
366
|
"aria-invalid": ariaInvalid,
|
|
357
367
|
...rest
|
|
358
368
|
} = allProps as SelectBaseProps<TItem> & {
|
|
@@ -414,6 +424,7 @@ export function Select<TItem = unknown>(
|
|
|
414
424
|
>
|
|
415
425
|
<SelectTrigger
|
|
416
426
|
className={className}
|
|
427
|
+
icon={icon}
|
|
417
428
|
// Only forward `aria-invalid` when explicitly set. Base UI's Field
|
|
418
429
|
// integration already sets it from validity state; passing `undefined`
|
|
419
430
|
// here would clobber that value during prop merge and drop the error border.
|
package/src/hooks/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { useControllableState } from "@radix-ui/react-use-controllable-state";
|
|
2
|
+
export * from "./use-action";
|
|
2
3
|
export * from "./use-array";
|
|
3
|
-
export * from "./use-async";
|
|
4
4
|
export * from "./use-boolean";
|
|
5
5
|
export * from "./use-click-outside";
|
|
6
6
|
export * from "./use-clipboard";
|
|
@@ -9,14 +9,12 @@ export * from "./use-debounce-value";
|
|
|
9
9
|
export * from "./use-disclosure";
|
|
10
10
|
export * from "./use-document-title";
|
|
11
11
|
export * from "./use-event-listener";
|
|
12
|
-
export * from "./use-focus-trap";
|
|
13
12
|
export * from "./use-hotkey";
|
|
14
13
|
export * from "./use-hover";
|
|
15
14
|
export * from "./use-is-visible";
|
|
16
15
|
export * from "./use-local-storage";
|
|
17
16
|
export * from "./use-media-query";
|
|
18
17
|
export * from "./use-memoized-fn";
|
|
19
|
-
export * from "./use-mutation";
|
|
20
18
|
export * from "./use-object";
|
|
21
19
|
export * from "./use-on-mount";
|
|
22
20
|
export * from "./use-pagination";
|
|
@@ -24,4 +22,5 @@ export * from "./use-portal";
|
|
|
24
22
|
export * from "./use-prevent-page-close";
|
|
25
23
|
export * from "./use-range-pagination";
|
|
26
24
|
export * from "./use-selection";
|
|
25
|
+
export * from "./use-session-storage";
|
|
27
26
|
export * from "./use-step";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { isAppleDevice } from "./is-apple-device";
|
|
2
2
|
export { isBrowser } from "./is-browser";
|
|
3
|
+
export type { Serializer } from "./serializer";
|
|
3
4
|
export { useIsomorphicLayoutEffect } from "./use-isomorphic-layout-effect";
|
|
4
5
|
export { useLatestRef } from "./use-latest-ref";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./use-action";
|
package/src/hooks/{use-mutation/use-mutation.stories.tsx → use-action/use-action.stories.tsx}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { Button } from "../../components";
|
|
4
|
-
import { type
|
|
4
|
+
import { type UseActionOptions, useAction } from "./use-action";
|
|
5
5
|
|
|
6
6
|
// ─── Demo component ──────────────────────────────────────────────────────────
|
|
7
7
|
|
|
@@ -14,21 +14,21 @@ interface DemoData {
|
|
|
14
14
|
timestamp: number;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
type
|
|
17
|
+
type ActionDemoProps = UseActionOptions<DemoVariables, DemoData> & {
|
|
18
18
|
/** Simulated async delay in ms */
|
|
19
19
|
delay?: number;
|
|
20
20
|
/** When true the mutation will reject with an error */
|
|
21
21
|
shouldFail?: boolean;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
const
|
|
24
|
+
const ActionDemo = ({
|
|
25
25
|
delay = 800,
|
|
26
26
|
shouldFail = false,
|
|
27
|
-
|
|
27
|
+
onExecute,
|
|
28
28
|
onSuccess,
|
|
29
29
|
onError,
|
|
30
30
|
onSettled,
|
|
31
|
-
}:
|
|
31
|
+
}: ActionDemoProps) => {
|
|
32
32
|
const [log, setLog] = useState<string[]>([]);
|
|
33
33
|
|
|
34
34
|
const addLog = (msg: string) =>
|
|
@@ -37,7 +37,7 @@ const MutationDemo = ({
|
|
|
37
37
|
...prev,
|
|
38
38
|
]);
|
|
39
39
|
|
|
40
|
-
const
|
|
40
|
+
const action = useAction<DemoVariables, DemoData>({
|
|
41
41
|
fn: async (vars) => {
|
|
42
42
|
await new Promise((res, rej) =>
|
|
43
43
|
setTimeout(
|
|
@@ -48,9 +48,9 @@ const MutationDemo = ({
|
|
|
48
48
|
);
|
|
49
49
|
return { message: `Created: ${vars.value}`, timestamp: Date.now() };
|
|
50
50
|
},
|
|
51
|
-
|
|
52
|
-
addLog(`
|
|
53
|
-
|
|
51
|
+
onExecute: (vars) => {
|
|
52
|
+
addLog(`onExecute — vars: ${JSON.stringify(vars)}`);
|
|
53
|
+
onExecute?.(vars);
|
|
54
54
|
},
|
|
55
55
|
onSuccess: (data, vars) => {
|
|
56
56
|
addLog(`onSuccess — data: ${data.message}`);
|
|
@@ -72,15 +72,15 @@ const MutationDemo = ({
|
|
|
72
72
|
<div className="flex flex-col gap-4 max-w-sm">
|
|
73
73
|
<div className="flex gap-2">
|
|
74
74
|
<Button
|
|
75
|
-
onClick={() =>
|
|
76
|
-
disabled={
|
|
75
|
+
onClick={() => action.execute({ value: "item-1" })}
|
|
76
|
+
disabled={action.isPending}
|
|
77
77
|
>
|
|
78
|
-
{
|
|
78
|
+
{action.isPending ? "Running…" : "Execute"}
|
|
79
79
|
</Button>
|
|
80
80
|
<Button
|
|
81
81
|
variant="outline"
|
|
82
|
-
onClick={() =>
|
|
83
|
-
disabled={
|
|
82
|
+
onClick={() => action.reset()}
|
|
83
|
+
disabled={action.isPending}
|
|
84
84
|
>
|
|
85
85
|
Reset
|
|
86
86
|
</Button>
|
|
@@ -89,9 +89,9 @@ const MutationDemo = ({
|
|
|
89
89
|
<pre className="rounded-md bg-slate-950 p-3 text-xs text-white leading-relaxed">
|
|
90
90
|
{JSON.stringify(
|
|
91
91
|
{
|
|
92
|
-
status:
|
|
93
|
-
data:
|
|
94
|
-
error:
|
|
92
|
+
status: action.status,
|
|
93
|
+
data: action.data,
|
|
94
|
+
error: action.error?.message ?? null,
|
|
95
95
|
},
|
|
96
96
|
null,
|
|
97
97
|
2,
|
|
@@ -111,31 +111,31 @@ const MutationDemo = ({
|
|
|
111
111
|
|
|
112
112
|
// ─── Meta ─────────────────────────────────────────────────────────────────────
|
|
113
113
|
|
|
114
|
-
const meta: Meta<typeof
|
|
115
|
-
title: "hooks/
|
|
116
|
-
component:
|
|
114
|
+
const meta: Meta<typeof ActionDemo> = {
|
|
115
|
+
title: "hooks/useAction",
|
|
116
|
+
component: ActionDemo,
|
|
117
117
|
parameters: {
|
|
118
118
|
docs: {
|
|
119
119
|
description: {
|
|
120
120
|
component: `
|
|
121
|
-
\`
|
|
121
|
+
\`useAction\` manages async mutation lifecycle with stable callback identity.
|
|
122
122
|
|
|
123
123
|
**API summary**
|
|
124
124
|
|
|
125
125
|
\`\`\`ts
|
|
126
|
-
const {
|
|
127
|
-
|
|
126
|
+
const { execute, executeAsync, reset, status, data, error, isPending, isSuccess, isError, isIdle } =
|
|
127
|
+
useAction({ fn, onExecute?, onSuccess?, onError?, onSettled? });
|
|
128
128
|
\`\`\`
|
|
129
129
|
|
|
130
|
-
**Lifecycle order (success):** \`
|
|
130
|
+
**Lifecycle order (success):** \`onExecute\` → \`fn\` → \`onSuccess\` → \`onSettled\`
|
|
131
131
|
|
|
132
|
-
**Lifecycle order (error):** \`
|
|
132
|
+
**Lifecycle order (error):** \`onExecute\` → \`fn\` throws → \`onError\` → \`onSettled\`
|
|
133
133
|
|
|
134
134
|
**Key guarantees**
|
|
135
|
-
- \`
|
|
136
|
-
- \`
|
|
135
|
+
- \`execute\` is fire-and-forget: returns \`void\`, never throws.
|
|
136
|
+
- \`executeAsync\` returns a \`Promise\` that re-throws on failure.
|
|
137
137
|
- \`onSettled\` is always called — even if \`onError\` throws.
|
|
138
|
-
- All callbacks read through \`useLatestRef\`, so inline functions never recreate \`
|
|
138
|
+
- All callbacks read through \`useLatestRef\`, so inline functions never recreate \`execute\`.
|
|
139
139
|
`.trim(),
|
|
140
140
|
},
|
|
141
141
|
},
|
|
@@ -153,8 +153,8 @@ const { mutate, mutateAsync, reset, status, data, error, isPending, isSuccess, i
|
|
|
153
153
|
"When true the demo's fn rejects — simulates an API error. Demo control only, not a hook option.",
|
|
154
154
|
table: { category: "Demo controls" },
|
|
155
155
|
},
|
|
156
|
-
|
|
157
|
-
action: "
|
|
156
|
+
onExecute: {
|
|
157
|
+
action: "onExecute",
|
|
158
158
|
description:
|
|
159
159
|
"Called synchronously with `variables` before `fn` is awaited. Use for optimistic updates.",
|
|
160
160
|
table: {
|
|
@@ -192,11 +192,11 @@ const { mutate, mutateAsync, reset, status, data, error, isPending, isSuccess, i
|
|
|
192
192
|
},
|
|
193
193
|
},
|
|
194
194
|
},
|
|
195
|
-
render: (args) => <
|
|
195
|
+
render: (args) => <ActionDemo {...args} />,
|
|
196
196
|
};
|
|
197
197
|
|
|
198
198
|
export default meta;
|
|
199
|
-
type Story = StoryObj<typeof
|
|
199
|
+
type Story = StoryObj<typeof ActionDemo>;
|
|
200
200
|
|
|
201
201
|
// ─── Stories ──────────────────────────────────────────────────────────────────
|
|
202
202
|
|
|
@@ -224,7 +224,7 @@ export const Pending: Story = {
|
|
|
224
224
|
docs: {
|
|
225
225
|
description: {
|
|
226
226
|
story:
|
|
227
|
-
"Click **
|
|
227
|
+
"Click **Execute** to see `isPending=true` while the operation runs. The button disables until the promise settles.",
|
|
228
228
|
},
|
|
229
229
|
},
|
|
230
230
|
},
|
|
@@ -255,7 +255,7 @@ export const ErrorState: Story = {
|
|
|
255
255
|
docs: {
|
|
256
256
|
description: {
|
|
257
257
|
story:
|
|
258
|
-
"Toggle **shouldFail** in controls to simulate a failing API call. `status` becomes `'error'`, `error` is set, and `data` stays null. `
|
|
258
|
+
"Toggle **shouldFail** in controls to simulate a failing API call. `status` becomes `'error'`, `error` is set, and `data` stays null. `execute` swallows the rejection silently — use `executeAsync` if you need to catch it.",
|
|
259
259
|
},
|
|
260
260
|
},
|
|
261
261
|
},
|