@boxcustodia/library 2.0.0-alpha.19 → 2.0.0-alpha.20
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/button/button.cjs.js +1 -1
- package/dist/components/button/button.es.js +19 -18
- package/dist/components/button/components/base-button.cjs.js +1 -1
- package/dist/components/button/components/base-button.es.js +20 -20
- package/dist/components/calendar/calendar.cjs.js +1 -1
- package/dist/components/calendar/calendar.es.js +1 -0
- package/dist/components/date-picker/date-input.cjs.js +1 -1
- package/dist/components/date-picker/date-input.es.js +92 -75
- package/dist/components/date-picker/date-picker.cjs.js +1 -1
- package/dist/components/date-picker/date-picker.es.js +104 -95
- package/dist/components/date-picker/date-picker.utils.cjs.js +1 -1
- package/dist/components/date-picker/date-picker.utils.es.js +51 -43
- package/dist/components/date-picker/use-hidden-field-value.cjs.js +1 -0
- package/dist/components/date-picker/use-hidden-field-value.es.js +11 -0
- package/dist/components/menu/menu.es.js +1 -9
- package/dist/components/otp/otp.cjs.js +2 -0
- package/dist/components/otp/otp.es.js +93 -0
- package/dist/components/password/password.cjs.js +1 -1
- package/dist/components/password/password.es.js +2 -2
- package/dist/components/select/select.cjs.js +1 -1
- package/dist/components/select/select.es.js +68 -60
- package/dist/hooks/internal/is-apple-device.cjs.js +1 -0
- package/dist/hooks/internal/is-apple-device.es.js +9 -0
- package/dist/hooks/internal/use-latest-ref.cjs.js +1 -0
- package/dist/hooks/internal/use-latest-ref.es.js +11 -0
- package/dist/hooks/use-array/use-array.cjs.js +1 -1
- package/dist/hooks/use-array/use-array.es.js +54 -42
- package/dist/hooks/use-async/use-async.cjs.js +1 -1
- package/dist/hooks/use-async/use-async.es.js +53 -20
- package/dist/hooks/use-boolean/use-boolean.cjs.js +1 -0
- package/dist/hooks/use-boolean/use-boolean.es.js +25 -0
- package/dist/hooks/use-click-outside/use-click-outside.cjs.js +1 -1
- package/dist/hooks/use-click-outside/use-click-outside.es.js +26 -12
- package/dist/hooks/use-debounce-callback/use-debounced-callback.cjs.js +1 -1
- package/dist/hooks/use-debounce-callback/use-debounced-callback.es.js +27 -10
- package/dist/hooks/use-debounce-value/use-debounced-value.cjs.js +1 -1
- package/dist/hooks/use-debounce-value/use-debounced-value.es.js +7 -9
- package/dist/hooks/use-disclosure/use-disclosure.cjs.js +1 -1
- package/dist/hooks/use-disclosure/use-disclosure.es.js +21 -11
- package/dist/hooks/use-document-title/use-document-title.cjs.js +1 -1
- package/dist/hooks/use-document-title/use-document-title.es.js +14 -12
- package/dist/hooks/use-event-listener/use-event-listener.cjs.js +1 -1
- package/dist/hooks/use-event-listener/use-event-listener.es.js +17 -9
- package/dist/hooks/use-hotkey/use-hotkey.cjs.js +1 -1
- package/dist/hooks/use-hotkey/use-hotkey.es.js +30 -14
- package/dist/hooks/use-hotkey/utils/is-input-field.cjs.js +1 -1
- package/dist/hooks/use-hotkey/utils/is-input-field.es.js +4 -2
- package/dist/hooks/use-hotkey/utils/match-and-run.cjs.js +1 -0
- package/dist/hooks/use-hotkey/utils/match-and-run.es.js +12 -0
- package/dist/hooks/use-hotkey/utils/match-key-modifiers.cjs.js +1 -1
- package/dist/hooks/use-hotkey/utils/match-key-modifiers.es.js +13 -12
- package/dist/hooks/use-hover/use-hover.cjs.js +1 -1
- package/dist/hooks/use-hover/use-hover.es.js +32 -17
- package/dist/hooks/use-is-visible/use-is-visible.cjs.js +1 -1
- package/dist/hooks/use-is-visible/use-is-visible.es.js +31 -27
- package/dist/hooks/use-local-storage/use-local-storage.cjs.js +1 -1
- package/dist/hooks/use-local-storage/use-local-storage.es.js +52 -20
- package/dist/hooks/use-media-query/use-media-query.cjs.js +1 -1
- package/dist/hooks/use-media-query/use-media-query.es.js +21 -11
- package/dist/hooks/use-mutation/use-mutation.cjs.js +1 -1
- package/dist/hooks/use-mutation/use-mutation.es.js +36 -22
- package/dist/hooks/use-object/use-object.cjs.js +1 -1
- package/dist/hooks/use-object/use-object.es.js +26 -22
- package/dist/hooks/use-prevent-page-close/use-prevent-page-close.cjs.js +1 -0
- package/dist/hooks/use-prevent-page-close/use-prevent-page-close.es.js +14 -0
- package/dist/hooks/use-step/use-step.cjs.js +1 -1
- package/dist/hooks/use-step/use-step.es.js +25 -24
- package/dist/index.cjs.js +1 -1
- package/dist/index.es.js +308 -300
- package/dist/src/components/date-picker/date-picker.utils.d.ts +17 -0
- package/dist/src/components/date-picker/use-hidden-field-value.d.ts +12 -0
- package/dist/src/components/index.d.ts +1 -0
- package/dist/src/hooks/index.d.ts +2 -2
- package/dist/src/hooks/internal/index.d.ts +2 -0
- package/dist/src/hooks/internal/is-apple-device.d.ts +12 -0
- package/dist/src/hooks/internal/use-latest-ref.d.ts +12 -0
- package/dist/src/hooks/use-array/use-array.d.ts +24 -11
- package/dist/src/hooks/use-async/use-async.d.ts +16 -13
- package/dist/src/hooks/use-boolean/index.d.ts +1 -0
- package/dist/src/hooks/use-boolean/use-boolean.d.ts +15 -0
- package/dist/src/hooks/use-boolean/use-boolean.test.d.ts +1 -0
- package/dist/src/hooks/use-click-outside/use-click-outside.d.ts +23 -1
- package/dist/src/hooks/use-debounce-callback/use-debounced-callback.d.ts +19 -1
- package/dist/src/hooks/use-debounce-value/use-debounced-value.d.ts +10 -1
- package/dist/src/hooks/use-disclosure/use-disclosure.d.ts +17 -8
- package/dist/src/hooks/use-document-title/use-document-title.d.ts +11 -0
- package/dist/src/hooks/use-event-listener/use-event-listener.d.ts +18 -1
- package/dist/src/hooks/use-hotkey/index.d.ts +2 -1
- package/dist/src/hooks/use-hotkey/use-hotkey.d.ts +62 -5
- package/dist/src/hooks/use-hotkey/utils/index.d.ts +4 -3
- package/dist/src/hooks/use-hotkey/utils/is-input-field.d.ts +12 -2
- package/dist/src/hooks/use-hotkey/utils/is-input-field.test.d.ts +1 -0
- package/dist/src/hooks/use-hotkey/utils/match-and-run.d.ts +36 -0
- package/dist/src/hooks/use-hotkey/utils/match-and-run.test.d.ts +1 -0
- package/dist/src/hooks/use-hotkey/utils/match-key-modifiers.d.ts +20 -6
- package/dist/src/hooks/use-hotkey/utils/match-key-modifiers.test.d.ts +1 -0
- package/dist/src/hooks/use-hover/use-hover.d.ts +8 -4
- package/dist/src/hooks/use-is-visible/use-is-visible.d.ts +28 -4
- package/dist/src/hooks/use-local-storage/use-local-storage.d.ts +13 -2
- package/dist/src/hooks/use-media-query/use-media-query.d.ts +10 -1
- package/dist/src/hooks/use-media-query/use-media-query.test.d.ts +1 -0
- package/dist/src/hooks/use-mutation/use-mutation.d.ts +18 -11
- package/dist/src/hooks/use-object/use-object.d.ts +15 -6
- package/dist/src/hooks/use-prevent-page-close/index.d.ts +1 -0
- package/dist/src/hooks/use-prevent-page-close/use-prevent-page-close.d.ts +10 -0
- package/dist/src/hooks/use-prevent-page-close/use-prevent-page-close.test.d.ts +1 -0
- package/dist/src/hooks/use-step/use-step.d.ts +18 -11
- package/dist/src/utils/form.d.ts +10 -0
- package/package.json +1 -1
- package/src/components/alert-dialog/alert-dialog.test.tsx +13 -9
- package/src/components/auto-complete/auto-complete.test.tsx +4 -14
- package/src/components/avatar/avatar.test.tsx +7 -12
- package/src/components/button/button.test.tsx +10 -15
- package/src/components/button/button.tsx +14 -9
- package/src/components/button/components/base-button.tsx +2 -4
- package/src/components/calendar/calendar.test.tsx +12 -19
- package/src/components/calendar/calendar.tsx +4 -0
- package/src/components/card/card.test.tsx +4 -6
- package/src/components/checkbox/checkbox.test.tsx +12 -8
- package/src/components/checkbox-group/checkbox-group.test.tsx +7 -8
- package/src/components/combobox/combobox.test.tsx +24 -21
- package/src/components/date-picker/date-input-form.test.tsx +77 -0
- package/src/components/date-picker/date-input.stories.tsx +30 -18
- package/src/components/date-picker/date-input.tsx +77 -44
- package/src/components/date-picker/date-picker.stories.tsx +31 -1
- package/src/components/date-picker/date-picker.test.tsx +3 -13
- package/src/components/date-picker/date-picker.tsx +35 -16
- package/src/components/date-picker/date-picker.utils.test.ts +32 -14
- package/src/components/date-picker/date-picker.utils.ts +33 -0
- package/src/components/date-picker/use-date-input-popover.test.ts +3 -1
- package/src/components/date-picker/use-hidden-field-value.ts +23 -0
- package/src/components/dialog/dialog.test.tsx +10 -8
- package/src/components/dropzone/dropzone.test.tsx +11 -13
- package/src/components/empty/empty.test.tsx +4 -3
- package/src/components/field/field.test.tsx +12 -13
- package/src/components/form/form.stories.tsx +16 -1
- package/src/components/index.ts +1 -0
- package/src/components/label/label.test.tsx +3 -3
- package/src/components/menu/menu.tsx +1 -5
- package/src/components/number-input/number-input.test.tsx +6 -2
- package/src/components/password/password.test.tsx +20 -6
- package/src/components/password/password.tsx +2 -2
- package/src/components/popover/popover.test.tsx +4 -4
- package/src/components/progress/progress.test.tsx +7 -8
- package/src/components/radio-group/radio-group.test.tsx +17 -11
- package/src/components/select/select.test.tsx +10 -10
- package/src/components/select/select.tsx +9 -1
- package/src/components/stepper/stepper.stories.tsx +11 -15
- package/src/components/stepper/stepper.test.tsx +6 -4
- package/src/components/switch/switch.test.tsx +3 -3
- package/src/components/table/table.test.tsx +9 -3
- package/src/components/tabs/tabs.test.tsx +6 -2
- package/src/components/tag/tag.test.tsx +1 -3
- package/src/components/textarea/textarea.test.tsx +4 -1
- package/src/components/timeline/timeline.test.tsx +10 -5
- package/src/components/toast/toast.test.tsx +11 -14
- package/src/components/tooltip/tooltip.test.tsx +1 -5
- package/src/components/tree/tree.test.tsx +3 -1
- package/src/hooks/index.ts +2 -2
- package/src/hooks/internal/index.ts +2 -0
- package/src/hooks/internal/is-apple-device.test.ts +41 -0
- package/src/hooks/internal/is-apple-device.ts +33 -0
- package/src/hooks/internal/use-isomorphic-layout-effect.ts +3 -1
- package/src/hooks/internal/use-latest-ref.ts +21 -0
- package/src/hooks/use-array/use-array.stories.tsx +435 -64
- package/src/hooks/use-array/use-array.test.tsx +398 -15
- package/src/hooks/use-array/use-array.ts +105 -66
- package/src/hooks/use-async/use-async.stories.tsx +255 -131
- package/src/hooks/use-async/use-async.test.ts +397 -0
- package/src/hooks/use-async/use-async.ts +117 -39
- package/src/hooks/use-boolean/index.ts +1 -0
- package/src/hooks/use-boolean/use-boolean.stories.tsx +377 -0
- package/src/hooks/use-boolean/use-boolean.test.tsx +177 -0
- package/src/hooks/use-boolean/use-boolean.ts +50 -0
- package/src/hooks/use-click-outside/use-click-outside.stories.tsx +188 -18
- package/src/hooks/use-click-outside/use-click-outside.test.tsx +89 -10
- package/src/hooks/use-click-outside/use-click-outside.ts +62 -16
- package/src/hooks/use-debounce-callback/use-debounced-callback.stories.tsx +141 -41
- package/src/hooks/use-debounce-callback/use-debounced-callback.test.ts +217 -9
- package/src/hooks/use-debounce-callback/use-debounced-callback.ts +71 -11
- package/src/hooks/use-debounce-value/use-debounced-value.stories.tsx +247 -47
- package/src/hooks/use-debounce-value/use-debounced-value.test.ts +105 -10
- package/src/hooks/use-debounce-value/use-debounced-value.ts +19 -10
- package/src/hooks/use-disclosure/use-disclosure.stories.tsx +305 -14
- package/src/hooks/use-disclosure/use-disclosure.test.ts +198 -50
- package/src/hooks/use-disclosure/use-disclosure.ts +49 -29
- package/src/hooks/use-document-title/use-document-title.stories.tsx +54 -0
- package/src/hooks/use-document-title/use-document-title.test.tsx +26 -0
- package/src/hooks/use-document-title/{use-document-title.tsx → use-document-title.ts} +17 -3
- package/src/hooks/use-event-listener/use-event-listener.stories.tsx +105 -9
- package/src/hooks/use-event-listener/use-event-listener.test.tsx +77 -10
- package/src/hooks/use-event-listener/use-event-listener.ts +71 -11
- package/src/hooks/use-focus-trap/use-focus-trap.test.ts +31 -6
- package/src/hooks/use-focus-trap/use-focus-trap.ts +3 -2
- package/src/hooks/use-hotkey/index.ts +9 -1
- package/src/hooks/use-hotkey/use-hotkey.stories.tsx +279 -74
- package/src/hooks/use-hotkey/use-hotkey.test.tsx +286 -34
- package/src/hooks/use-hotkey/use-hotkey.ts +141 -17
- package/src/hooks/use-hotkey/utils/index.ts +8 -3
- package/src/hooks/use-hotkey/utils/is-input-field.test.ts +78 -0
- package/src/hooks/use-hotkey/utils/is-input-field.ts +31 -10
- package/src/hooks/use-hotkey/utils/match-and-run.test.ts +203 -0
- package/src/hooks/use-hotkey/utils/match-and-run.ts +62 -0
- package/src/hooks/use-hotkey/utils/match-key-modifiers.test.ts +65 -0
- package/src/hooks/use-hotkey/utils/match-key-modifiers.ts +39 -12
- package/src/hooks/use-hover/use-hover.stories.tsx +258 -80
- package/src/hooks/use-hover/use-hover.test.tsx +266 -26
- package/src/hooks/use-hover/use-hover.tsx +93 -28
- package/src/hooks/use-is-visible/use-is-visible.stories.tsx +193 -46
- package/src/hooks/use-is-visible/use-is-visible.test.tsx +235 -7
- package/src/hooks/use-is-visible/use-is-visible.ts +114 -0
- package/src/hooks/use-local-storage/use-local-storage.stories.tsx +129 -29
- package/src/hooks/use-local-storage/use-local-storage.test.ts +106 -41
- package/src/hooks/use-local-storage/use-local-storage.ts +100 -31
- package/src/hooks/use-media-query/use-media-query.stories.tsx +86 -26
- package/src/hooks/use-media-query/use-media-query.test.ts +132 -0
- package/src/hooks/use-media-query/use-media-query.ts +39 -14
- package/src/hooks/use-memoized-fn/use-memoized-fn.ts +0 -1
- package/src/hooks/use-mutation/use-mutation.stories.tsx +260 -94
- package/src/hooks/use-mutation/use-mutation.test.ts +359 -0
- package/src/hooks/use-mutation/use-mutation.ts +97 -0
- package/src/hooks/use-object/use-object.stories.tsx +310 -79
- package/src/hooks/use-object/use-object.test.tsx +235 -56
- package/src/hooks/use-object/use-object.ts +59 -0
- package/src/hooks/use-pagination/use-pagination.tsx +0 -1
- package/src/hooks/use-prevent-page-close/index.ts +1 -0
- package/src/hooks/use-prevent-page-close/use-prevent-page-close.stories.tsx +39 -0
- package/src/hooks/use-prevent-page-close/use-prevent-page-close.test.ts +89 -0
- package/src/hooks/use-prevent-page-close/use-prevent-page-close.ts +27 -0
- package/src/hooks/use-range-pagination/use-range-pagination.test.tsx +1 -1
- package/src/hooks/use-range-pagination/use-range-pagination.tsx +1 -1
- package/src/hooks/use-selection/use-selection.ts +0 -1
- package/src/hooks/use-step/use-step.stories.tsx +178 -65
- package/src/hooks/use-step/use-step.test.ts +178 -53
- package/src/hooks/use-step/use-step.ts +57 -49
- package/src/utils/form.test.tsx +13 -8
- package/src/utils/form.tsx +10 -0
- package/src/utils/functions/getFormData.test.ts +1 -1
- package/dist/hooks/use-hotkey/utils/create-hotkey-listener.cjs.js +0 -1
- package/dist/hooks/use-hotkey/utils/create-hotkey-listener.es.js +0 -10
- package/dist/hooks/use-prevent-close-window/use-prevent-close-window.cjs.js +0 -1
- package/dist/hooks/use-prevent-close-window/use-prevent-close-window.es.js +0 -15
- package/dist/hooks/use-toggle/use-toggle.cjs.js +0 -1
- package/dist/hooks/use-toggle/use-toggle.es.js +0 -10
- package/dist/src/hooks/use-hotkey/utils/create-hotkey-listener.d.ts +0 -1
- package/dist/src/hooks/use-prevent-close-window/index.d.ts +0 -1
- package/dist/src/hooks/use-prevent-close-window/use-prevent-close-window.d.ts +0 -13
- package/dist/src/hooks/use-toggle/index.d.ts +0 -1
- package/dist/src/hooks/use-toggle/use-toggle.d.ts +0 -3
- package/src/hooks/use-async/use-async.test.tsx +0 -68
- package/src/hooks/use-hotkey/utils/create-hotkey-listener.ts +0 -25
- package/src/hooks/use-is-visible/use-is-visible.tsx +0 -49
- package/src/hooks/use-mutation/use-mutation.test.tsx +0 -83
- package/src/hooks/use-mutation/use-mutation.tsx +0 -59
- package/src/hooks/use-object/use-object.tsx +0 -46
- package/src/hooks/use-prevent-close-window/index.ts +0 -1
- package/src/hooks/use-prevent-close-window/use-prevent-close-window.stories.tsx +0 -32
- package/src/hooks/use-prevent-close-window/use-prevent-close-window.test.ts +0 -79
- package/src/hooks/use-prevent-close-window/use-prevent-close-window.ts +0 -33
- package/src/hooks/use-toggle/index.ts +0 -1
- package/src/hooks/use-toggle/use-toggle.stories.tsx +0 -25
- package/src/hooks/use-toggle/use-toggle.test.tsx +0 -64
- package/src/hooks/use-toggle/use-toggle.ts +0 -14
- /package/dist/src/{hooks/use-prevent-close-window/use-prevent-close-window.test.d.ts → components/date-picker/date-input-form.test.d.ts} +0 -0
- /package/dist/src/hooks/{use-toggle/use-toggle.test.d.ts → internal/is-apple-device.test.d.ts} +0 -0
|
@@ -1,100 +1,471 @@
|
|
|
1
|
-
import { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
-
import {
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react-vite";
|
|
2
|
+
import { useState } from "react";
|
|
3
3
|
import { Button } from "../../components";
|
|
4
|
-
import { useArray } from "
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
4
|
+
import { useArray } from "./use-array";
|
|
5
|
+
|
|
6
|
+
// ─── Types ─────────────────────────────────────────────────────────────────────
|
|
7
|
+
|
|
8
|
+
interface ActiveItem {
|
|
9
|
+
id: number;
|
|
10
|
+
name: string;
|
|
11
|
+
active: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// ─── Shared UI helpers ─────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
const JsonPanel = ({ value }: { value: unknown }) => (
|
|
17
|
+
<pre className="rounded-md bg-slate-950 p-3 text-xs text-white leading-relaxed overflow-auto max-h-48">
|
|
18
|
+
{JSON.stringify(value, null, 2)}
|
|
19
|
+
</pre>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const LogPanel = ({ entries }: { entries: string[] }) =>
|
|
23
|
+
entries.length > 0 ? (
|
|
24
|
+
<div className="rounded-md border bg-muted p-3 text-xs font-mono space-y-0.5">
|
|
25
|
+
{entries.map((entry, i) => (
|
|
26
|
+
<div key={i}>{entry}</div>
|
|
27
|
+
))}
|
|
28
|
+
</div>
|
|
29
|
+
) : (
|
|
30
|
+
<div className="rounded-md border bg-muted p-3 text-xs font-mono text-muted-foreground">
|
|
31
|
+
No events yet — log is empty until the first mutation (proves onChange
|
|
32
|
+
does not fire on mount).
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
const timestamp = () => new Date().toISOString().slice(11, 23);
|
|
37
|
+
|
|
38
|
+
// ─── Shared data ───────────────────────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
const INITIAL_ITEMS: ActiveItem[] = [
|
|
41
|
+
{ id: 1, name: "Alice", active: true },
|
|
42
|
+
{ id: 2, name: "Bob", active: false },
|
|
43
|
+
{ id: 3, name: "Charlie", active: true },
|
|
44
|
+
{ id: 4, name: "Diana", active: false },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
// ─── Meta ─────────────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
22
49
|
const meta: Meta = {
|
|
23
50
|
title: "hooks/useArray",
|
|
51
|
+
parameters: {
|
|
52
|
+
docs: {
|
|
53
|
+
description: {
|
|
54
|
+
component: `
|
|
55
|
+
\`useArray\` manages array state with a stable set of immutable actions.
|
|
56
|
+
|
|
57
|
+
**Key behaviors**
|
|
58
|
+
- All 17 action references are **stable across renders** — built once in \`useMemo([setList])\`. Safe to pass as props or include in dependency arrays without causing extra renders.
|
|
59
|
+
- Every mutating action returns a **new array reference** — React change detection always works correctly.
|
|
60
|
+
- \`sort\` and \`reverse\` spread the previous state before calling the native method, preventing in-place mutation.
|
|
61
|
+
- \`replace\` returns the previous ref unchanged when the predicate matches nothing — no phantom re-render, no spurious \`onChange\`.
|
|
62
|
+
- \`reset()\` restores the **mount-time value** regardless of prop changes after mount (frozen ref pattern).
|
|
63
|
+
- \`onChange\` fires after every state change but **not on initial mount**.
|
|
64
|
+
|
|
65
|
+
\`\`\`ts
|
|
66
|
+
const [list, actions] = useArray(initialValue, { onChange? });
|
|
67
|
+
\`\`\`
|
|
68
|
+
`.trim(),
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
argTypes: {
|
|
73
|
+
initialValue: {
|
|
74
|
+
description:
|
|
75
|
+
"The initial array value. Captured once on mount — `reset()` always restores this value regardless of later prop changes.",
|
|
76
|
+
table: {
|
|
77
|
+
category: "Hook options",
|
|
78
|
+
type: { summary: "T[]" },
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
},
|
|
24
82
|
};
|
|
25
83
|
|
|
26
84
|
export default meta;
|
|
85
|
+
type Story = StoryObj;
|
|
27
86
|
|
|
28
|
-
|
|
87
|
+
// ─── Story 1 — Default (Core CRUD) ────────────────────────────────────────────
|
|
29
88
|
|
|
30
89
|
export const Default: Story = {
|
|
31
90
|
render: () => {
|
|
32
|
-
const
|
|
33
|
-
|
|
91
|
+
const INITIAL = ["First", "Second", "Third"];
|
|
92
|
+
const [inputValue, setInputValue] = useState("");
|
|
93
|
+
const [list, actions] = useArray<string>(INITIAL);
|
|
94
|
+
|
|
95
|
+
const handlePush = () => {
|
|
96
|
+
const val = inputValue.trim();
|
|
97
|
+
if (!val) return;
|
|
98
|
+
actions.push(val);
|
|
99
|
+
setInputValue("");
|
|
100
|
+
};
|
|
34
101
|
|
|
35
102
|
return (
|
|
36
|
-
<div className="
|
|
103
|
+
<div className="flex flex-col gap-4 max-w-sm">
|
|
104
|
+
<div className="flex gap-2">
|
|
105
|
+
<input
|
|
106
|
+
className="flex-1 rounded-md border px-3 py-2 text-sm"
|
|
107
|
+
placeholder="Type an item..."
|
|
108
|
+
value={inputValue}
|
|
109
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
110
|
+
onKeyDown={(e) => e.key === "Enter" && handlePush()}
|
|
111
|
+
/>
|
|
112
|
+
<Button variant="outline" onClick={handlePush}>
|
|
113
|
+
Push
|
|
114
|
+
</Button>
|
|
115
|
+
</div>
|
|
116
|
+
|
|
37
117
|
<div className="flex gap-2 flex-wrap">
|
|
38
|
-
<Button
|
|
39
|
-
|
|
118
|
+
<Button
|
|
119
|
+
variant="outline"
|
|
120
|
+
onClick={() => actions.insertAt(0, "Prepended")}
|
|
121
|
+
>
|
|
122
|
+
Prepend
|
|
40
123
|
</Button>
|
|
41
|
-
<Button
|
|
42
|
-
|
|
124
|
+
<Button
|
|
125
|
+
variant="outline"
|
|
126
|
+
disabled={list.length === 0}
|
|
127
|
+
onClick={() => actions.pop()}
|
|
128
|
+
>
|
|
129
|
+
Pop
|
|
43
130
|
</Button>
|
|
44
|
-
<Button
|
|
45
|
-
|
|
131
|
+
<Button variant="outline" onClick={() => actions.reset()}>
|
|
132
|
+
Reset
|
|
133
|
+
</Button>
|
|
134
|
+
<Button variant="outline" onClick={() => actions.clear()}>
|
|
135
|
+
Clear
|
|
136
|
+
</Button>
|
|
137
|
+
</div>
|
|
138
|
+
|
|
139
|
+
{list.length > 0 && (
|
|
140
|
+
<ul className="space-y-1">
|
|
141
|
+
{list.map((item, index) => (
|
|
142
|
+
<li
|
|
143
|
+
key={index}
|
|
144
|
+
className="flex items-center justify-between rounded-md border px-3 py-1.5 text-sm"
|
|
145
|
+
>
|
|
146
|
+
<span>{item}</span>
|
|
147
|
+
<Button
|
|
148
|
+
variant="ghost"
|
|
149
|
+
size="sm"
|
|
150
|
+
onClick={() => actions.removeAt(index)}
|
|
151
|
+
>
|
|
152
|
+
Remove
|
|
153
|
+
</Button>
|
|
154
|
+
</li>
|
|
155
|
+
))}
|
|
156
|
+
</ul>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
<JsonPanel value={list} />
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
},
|
|
163
|
+
parameters: {
|
|
164
|
+
docs: {
|
|
165
|
+
description: {
|
|
166
|
+
story:
|
|
167
|
+
"Core CRUD: push, pop, prepend via `insertAt(0)`, removeAt per row, reset (restores mount-time value), and clear. Type in the input and press Enter or click **Push** to add items.",
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
// ─── Story 2 — SearchAndTransform ─────────────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
export const SearchAndTransform: Story = {
|
|
176
|
+
render: () => {
|
|
177
|
+
const [list, actions] = useArray<ActiveItem>(INITIAL_ITEMS);
|
|
178
|
+
const [findResult, setFindResult] = useState<ActiveItem | undefined>(
|
|
179
|
+
undefined,
|
|
180
|
+
);
|
|
181
|
+
const [searched, setSearched] = useState(false);
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<div className="flex flex-col gap-4 max-w-sm">
|
|
185
|
+
<div className="flex gap-2 flex-wrap">
|
|
186
|
+
<Button
|
|
187
|
+
variant="outline"
|
|
188
|
+
onClick={() => actions.filter((item) => item.active)}
|
|
189
|
+
>
|
|
190
|
+
Filter active only
|
|
191
|
+
</Button>
|
|
192
|
+
<Button
|
|
193
|
+
variant="outline"
|
|
194
|
+
onClick={() => {
|
|
195
|
+
const found = actions.find((item) => !item.active);
|
|
196
|
+
setFindResult(found);
|
|
197
|
+
setSearched(true);
|
|
198
|
+
}}
|
|
199
|
+
>
|
|
200
|
+
Find first inactive
|
|
201
|
+
</Button>
|
|
202
|
+
<Button
|
|
203
|
+
variant="outline"
|
|
204
|
+
onClick={() =>
|
|
205
|
+
actions.replace(
|
|
206
|
+
(item) => item.name.toLowerCase().includes("a"),
|
|
207
|
+
(item) => ({ ...item, active: true }),
|
|
208
|
+
)
|
|
209
|
+
}
|
|
210
|
+
>
|
|
211
|
+
Activate first with "a"
|
|
46
212
|
</Button>
|
|
47
213
|
<Button
|
|
48
|
-
|
|
214
|
+
variant="outline"
|
|
215
|
+
onClick={() => actions.removeWhere((item) => !item.active)}
|
|
49
216
|
>
|
|
50
|
-
|
|
217
|
+
Remove inactive
|
|
218
|
+
</Button>
|
|
219
|
+
<Button variant="outline" onClick={() => actions.set(INITIAL_ITEMS)}>
|
|
220
|
+
Reset to initial
|
|
51
221
|
</Button>
|
|
52
|
-
<Button onClick={reset}>Resetear</Button>
|
|
53
|
-
<Button onClick={clear}>Limpiar</Button>
|
|
54
222
|
</div>
|
|
55
223
|
|
|
56
|
-
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
224
|
+
{searched && (
|
|
225
|
+
<div className="rounded-md border bg-muted p-3 text-xs font-mono">
|
|
226
|
+
<span className="text-muted-foreground">find result: </span>
|
|
227
|
+
{findResult ? JSON.stringify(findResult) : "undefined"}
|
|
228
|
+
</div>
|
|
229
|
+
)}
|
|
230
|
+
|
|
231
|
+
<JsonPanel value={list} />
|
|
64
232
|
</div>
|
|
65
233
|
);
|
|
66
234
|
},
|
|
235
|
+
parameters: {
|
|
236
|
+
docs: {
|
|
237
|
+
description: {
|
|
238
|
+
story:
|
|
239
|
+
"Predicate-based operations: `filter` retains matching items, `find` returns the first match without mutating state, `replace` transforms the first match (updater fn variant), and `removeWhere` removes all matching items.",
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
},
|
|
67
243
|
};
|
|
68
244
|
|
|
69
|
-
|
|
70
|
-
render: () => {
|
|
71
|
-
const [list, updateList] = useArray<string>([]);
|
|
245
|
+
// ─── Story 3 — Reordering ─────────────────────────────────────────────────────
|
|
72
246
|
|
|
73
|
-
|
|
247
|
+
export const Reordering: Story = {
|
|
248
|
+
render: () => {
|
|
249
|
+
const [list, actions] = useArray(["Apple", "Banana", "Cherry", "Date"]);
|
|
250
|
+
const [fromIdx, setFromIdx] = useState(0);
|
|
251
|
+
const [toIdx, setToIdx] = useState(2);
|
|
252
|
+
const [swapA, setSwapA] = useState(0);
|
|
253
|
+
const [swapB, setSwapB] = useState(3);
|
|
74
254
|
|
|
75
255
|
return (
|
|
76
|
-
<div className="
|
|
256
|
+
<div className="flex flex-col gap-4 max-w-sm">
|
|
77
257
|
<div className="flex items-center gap-2">
|
|
78
|
-
<
|
|
79
|
-
<
|
|
80
|
-
|
|
258
|
+
<label className="text-sm shrink-0">Move from</label>
|
|
259
|
+
<input
|
|
260
|
+
type="number"
|
|
261
|
+
min={0}
|
|
262
|
+
max={list.length - 1}
|
|
263
|
+
value={fromIdx}
|
|
264
|
+
onChange={(e) => setFromIdx(Number(e.target.value))}
|
|
265
|
+
className="w-16 rounded-md border px-2 py-1.5 text-sm"
|
|
266
|
+
/>
|
|
267
|
+
<label className="text-sm shrink-0">to</label>
|
|
268
|
+
<input
|
|
269
|
+
type="number"
|
|
270
|
+
min={0}
|
|
271
|
+
max={list.length - 1}
|
|
272
|
+
value={toIdx}
|
|
273
|
+
onChange={(e) => setToIdx(Number(e.target.value))}
|
|
274
|
+
className="w-16 rounded-md border px-2 py-1.5 text-sm"
|
|
275
|
+
/>
|
|
276
|
+
<Button
|
|
277
|
+
variant="outline"
|
|
278
|
+
onClick={() => actions.move(fromIdx, toIdx)}
|
|
279
|
+
>
|
|
280
|
+
Move
|
|
81
281
|
</Button>
|
|
82
282
|
</div>
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
283
|
+
|
|
284
|
+
<div className="flex items-center gap-2">
|
|
285
|
+
<label className="text-sm shrink-0">Swap</label>
|
|
286
|
+
<input
|
|
287
|
+
type="number"
|
|
288
|
+
min={0}
|
|
289
|
+
max={list.length - 1}
|
|
290
|
+
value={swapA}
|
|
291
|
+
onChange={(e) => setSwapA(Number(e.target.value))}
|
|
292
|
+
className="w-16 rounded-md border px-2 py-1.5 text-sm"
|
|
293
|
+
/>
|
|
294
|
+
<label className="text-sm shrink-0">and</label>
|
|
295
|
+
<input
|
|
296
|
+
type="number"
|
|
297
|
+
min={0}
|
|
298
|
+
max={list.length - 1}
|
|
299
|
+
value={swapB}
|
|
300
|
+
onChange={(e) => setSwapB(Number(e.target.value))}
|
|
301
|
+
className="w-16 rounded-md border px-2 py-1.5 text-sm"
|
|
302
|
+
/>
|
|
303
|
+
<Button variant="outline" onClick={() => actions.swap(swapA, swapB)}>
|
|
304
|
+
Swap
|
|
305
|
+
</Button>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
<div className="flex gap-2 flex-wrap">
|
|
309
|
+
<Button
|
|
310
|
+
variant="outline"
|
|
311
|
+
onClick={() => actions.sort((a, b) => a.localeCompare(b))}
|
|
312
|
+
>
|
|
313
|
+
Sort A→Z
|
|
314
|
+
</Button>
|
|
315
|
+
<Button
|
|
316
|
+
variant="outline"
|
|
317
|
+
onClick={() => actions.sort((a, b) => b.localeCompare(a))}
|
|
318
|
+
>
|
|
319
|
+
Sort Z→A
|
|
320
|
+
</Button>
|
|
321
|
+
<Button variant="outline" onClick={() => actions.reverse()}>
|
|
322
|
+
Reverse
|
|
323
|
+
</Button>
|
|
324
|
+
<Button
|
|
325
|
+
variant="outline"
|
|
326
|
+
onClick={() => actions.set(["Apple", "Banana", "Cherry", "Date"])}
|
|
327
|
+
>
|
|
328
|
+
Reset
|
|
329
|
+
</Button>
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
<JsonPanel value={list} />
|
|
333
|
+
</div>
|
|
334
|
+
);
|
|
335
|
+
},
|
|
336
|
+
parameters: {
|
|
337
|
+
docs: {
|
|
338
|
+
description: {
|
|
339
|
+
story:
|
|
340
|
+
"Reorder actions: `move(from, to)` repositions an element, `swap(a, b)` exchanges two, `sort` with a compareFn sorts without mutating prior state, and `reverse` reverses into a new array. Same-index `move` or `swap` returns the previous ref unchanged.",
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
// ─── Story 4 — BulkOperations ─────────────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
export const BulkOperations: Story = {
|
|
349
|
+
render: () => {
|
|
350
|
+
const [list, actions] = useArray<string>([]);
|
|
351
|
+
const [csvInput, setCsvInput] = useState("foo, bar, baz");
|
|
352
|
+
|
|
353
|
+
const handlePushMany = () => {
|
|
354
|
+
const items = csvInput
|
|
355
|
+
.split(",")
|
|
356
|
+
.map((s) => s.trim())
|
|
357
|
+
.filter(Boolean);
|
|
358
|
+
actions.pushMany(items);
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
return (
|
|
362
|
+
<div className="flex flex-col gap-4 max-w-sm">
|
|
363
|
+
<div className="flex gap-2">
|
|
364
|
+
<input
|
|
365
|
+
className="flex-1 rounded-md border px-3 py-2 text-sm"
|
|
366
|
+
placeholder="comma-separated values"
|
|
367
|
+
value={csvInput}
|
|
368
|
+
onChange={(e) => setCsvInput(e.target.value)}
|
|
369
|
+
/>
|
|
370
|
+
<Button variant="outline" onClick={handlePushMany}>
|
|
371
|
+
Push Many
|
|
372
|
+
</Button>
|
|
373
|
+
</div>
|
|
374
|
+
|
|
375
|
+
<div className="flex gap-2 flex-wrap">
|
|
376
|
+
<Button
|
|
377
|
+
variant="outline"
|
|
378
|
+
onClick={() => actions.set(["🍎", "🍌", "🍒", "🍇"])}
|
|
379
|
+
>
|
|
380
|
+
Set fruits
|
|
381
|
+
</Button>
|
|
382
|
+
<Button
|
|
383
|
+
variant="outline"
|
|
384
|
+
onClick={() => actions.set(["red", "green", "blue"])}
|
|
385
|
+
>
|
|
386
|
+
Set colors
|
|
387
|
+
</Button>
|
|
388
|
+
<Button
|
|
389
|
+
variant="outline"
|
|
390
|
+
onClick={() => actions.set([1, 2, 3, 4, 5] as unknown as string[])}
|
|
391
|
+
>
|
|
392
|
+
Set numbers
|
|
393
|
+
</Button>
|
|
394
|
+
<Button variant="outline" onClick={() => actions.clear()}>
|
|
395
|
+
Clear
|
|
396
|
+
</Button>
|
|
397
|
+
</div>
|
|
398
|
+
|
|
399
|
+
<JsonPanel value={list} />
|
|
97
400
|
</div>
|
|
98
401
|
);
|
|
99
402
|
},
|
|
403
|
+
parameters: {
|
|
404
|
+
docs: {
|
|
405
|
+
description: {
|
|
406
|
+
story:
|
|
407
|
+
"`pushMany` appends all items in one state update — more efficient than multiple `push` calls. `set` replaces the entire array at once. Calling `pushMany([])` is a no-op (no state change, no `onChange` fire).",
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// ─── Story 5 — WithOnChange ────────────────────────────────────────────────────
|
|
414
|
+
|
|
415
|
+
export const WithOnChange: Story = {
|
|
416
|
+
render: () => {
|
|
417
|
+
const [log, setLog] = useState<string[]>([]);
|
|
418
|
+
|
|
419
|
+
const addLog = (action: string, list: string[]) =>
|
|
420
|
+
setLog((prev) => [
|
|
421
|
+
`[${timestamp()}] ${action} → ${JSON.stringify(list)}`,
|
|
422
|
+
...prev,
|
|
423
|
+
]);
|
|
424
|
+
|
|
425
|
+
const [list, actions] = useArray<string>(["a", "b", "c"], {
|
|
426
|
+
onChange: (updated) => addLog("onChange", updated),
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
return (
|
|
430
|
+
<div className="flex flex-col gap-4 max-w-sm">
|
|
431
|
+
<p className="text-xs text-muted-foreground">
|
|
432
|
+
The log panel below starts empty — proving <code>onChange</code> does
|
|
433
|
+
not fire on initial mount. Every button triggers a real mutation.
|
|
434
|
+
</p>
|
|
435
|
+
|
|
436
|
+
<div className="flex gap-2 flex-wrap">
|
|
437
|
+
<Button variant="outline" onClick={() => actions.push("x")}>
|
|
438
|
+
Push "x"
|
|
439
|
+
</Button>
|
|
440
|
+
<Button
|
|
441
|
+
variant="outline"
|
|
442
|
+
disabled={list.length === 0}
|
|
443
|
+
onClick={() => actions.pop()}
|
|
444
|
+
>
|
|
445
|
+
Pop
|
|
446
|
+
</Button>
|
|
447
|
+
<Button variant="outline" onClick={() => actions.clear()}>
|
|
448
|
+
Clear
|
|
449
|
+
</Button>
|
|
450
|
+
<Button variant="outline" onClick={() => actions.reset()}>
|
|
451
|
+
Reset
|
|
452
|
+
</Button>
|
|
453
|
+
<Button variant="outline" onClick={() => setLog([])}>
|
|
454
|
+
Clear log
|
|
455
|
+
</Button>
|
|
456
|
+
</div>
|
|
457
|
+
|
|
458
|
+
<JsonPanel value={list} />
|
|
459
|
+
<LogPanel entries={log} />
|
|
460
|
+
</div>
|
|
461
|
+
);
|
|
462
|
+
},
|
|
463
|
+
parameters: {
|
|
464
|
+
docs: {
|
|
465
|
+
description: {
|
|
466
|
+
story:
|
|
467
|
+
"Wires `onChange` to a visible log panel. The log is empty on mount (no initial fire). Each mutation appends a timestamped entry with the updated list. Passing a new inline `onChange` reference between renders does not cause double-fires because the callback is read through `useLatestRef`.",
|
|
468
|
+
},
|
|
469
|
+
},
|
|
470
|
+
},
|
|
100
471
|
};
|