aural-ui 4.0.1 → 4.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/README.md +8 -1
- package/dist/components/aspect-ratio/AspectRatio.stories.tsx +290 -1228
- package/dist/components/avatar/Avatar.stories.tsx +219 -235
- package/dist/components/badge/Badge.stories.tsx +379 -116
- package/dist/components/banner/Banner.stories.tsx +445 -391
- package/dist/components/breadcrumb/Breadcrumb.stories.tsx +453 -199
- package/dist/components/button/Button.stories.tsx +585 -230
- package/dist/components/card/Card.stories.tsx +619 -301
- package/dist/components/char-count/CharCount.stories.tsx +350 -248
- package/dist/components/checkbox/Checkbox.stories.tsx +309 -167
- package/dist/components/chip/Chip.stories.tsx +362 -168
- package/dist/components/circular-loader/CircularLoader.stories.tsx +221 -636
- package/dist/components/clamp-lines/ClampLines.stories.tsx +246 -117
- package/dist/components/collapsible/Collapsible.stories.tsx +391 -252
- package/dist/components/command/Command.stories.tsx +530 -867
- package/dist/components/dialog/Dialog.stories.tsx +501 -950
- package/dist/components/divider/Divider.stories.tsx +264 -527
- package/dist/components/dot-loader/DotLoader.stories.tsx +256 -257
- package/dist/components/drawer/Drawer.stories.tsx +659 -1023
- package/dist/components/dropdown/Dropdown.stories.tsx +643 -1028
- package/dist/components/form/Form.stories.tsx +560 -274
- package/dist/components/helper-text/HelperText.stories.tsx +199 -200
- package/dist/components/hover-card/HoverCard.stories.tsx +318 -1254
- package/dist/components/icon-button/IconButton.stories.tsx +837 -194
- package/dist/components/if-else/if-else.stories.tsx +370 -83
- package/dist/components/input/Input.stories.tsx +436 -368
- package/dist/components/label/Label.stories.tsx +156 -154
- package/dist/components/list/List.stories.tsx +484 -835
- package/dist/components/marquee/Marquee.stories.tsx +356 -712
- package/dist/components/otp-inputs/OtpInputs.stories.tsx +352 -422
- package/dist/components/overlay/Overlay.stories.tsx +452 -824
- package/dist/components/pagination/Pagination.stories.tsx +721 -210
- package/dist/components/popover/Popover.stories.tsx +481 -896
- package/dist/components/radio/Radio.stories.tsx +432 -124
- package/dist/components/resizable/Resizable.stories.tsx +495 -799
- package/dist/components/scroll-area/ScrollArea.stories.tsx +383 -1059
- package/dist/components/search/Search.stories.tsx +312 -595
- package/dist/components/select/Select.stories.tsx +684 -789
- package/dist/components/sheet/Sheet.stories.tsx +671 -950
- package/dist/components/skelton/Skelton.stories.tsx +230 -764
- package/dist/components/slider/Slider.stories.tsx +383 -760
- package/dist/components/stepper/Stepper.stories.tsx +371 -514
- package/dist/components/switch/Switch.stories.tsx +461 -208
- package/dist/components/switch-case/SwitchCase.stories.tsx +367 -188
- package/dist/components/table/Table.stories.tsx +770 -916
- package/dist/components/tabs/Tabs.stories.tsx +458 -1455
- package/dist/components/tag/Tag.stories.tsx +714 -542
- package/dist/components/textarea/TextArea.stories.tsx +621 -562
- package/dist/components/thumbnail-tags/ThumbnailTags.stories.tsx +228 -154
- package/dist/components/toast/Toast.stories.tsx +452 -1339
- package/dist/components/toggle/Toggle.stories.tsx +488 -931
- package/dist/components/tooltip/Tooltip.stories.tsx +344 -1388
- package/dist/components/typography/Typography.stories.tsx +406 -89
- package/dist/hooks/use-change-state/UseChangeState.stories.tsx +309 -606
- package/dist/hooks/use-previous/UsePrevious.stories.tsx +367 -917
- package/dist/hooks/use-standalone-pagination/UseStandalonePagination.stories.tsx +639 -867
- package/dist/icons/Icons.stories.tsx +0 -12
- package/dist/icons/ai-avatar-icon/AiAvatarIcon.stories.tsx +223 -1060
- package/dist/icons/alert-icon/AlertIcon.stories.tsx +106 -968
- package/dist/icons/all-icons.tsx +37 -16
- package/dist/icons/angle-down-icon/AngleDownIcon.stories.tsx +137 -1010
- package/dist/icons/apple-logo-icon/AppleLogoIcon.stories.tsx +145 -935
- package/dist/icons/arrow-box-left-icon/ArrowBoxLeftIcon.stories.tsx +132 -1046
- package/dist/icons/arrow-corner-up-left-icon/ArrowCornerUpLeftIcon.stories.tsx +134 -986
- package/dist/icons/arrow-corner-up-right-icon/ArrowCornerUpRightIcon.stories.tsx +135 -1028
- package/dist/icons/arrow-left-icon/ArrowLeftIcon.stories.tsx +133 -971
- package/dist/icons/arrow-right-icon/ArrowRightIcon.stories.tsx +145 -1123
- package/dist/icons/arrow-right-up-icon/ArrowRightUpIcon.stories.tsx +143 -1252
- package/dist/icons/art-board-icon/ArtBoardIcon.stories.tsx +123 -632
- package/dist/icons/audio-bar-icon/AudioBarIcon.stories.tsx +141 -1223
- package/dist/icons/backward-ten-seconds-icon/BackwardTenSecondsIcon.stories.tsx +164 -1018
- package/dist/icons/bubble-check-icon/BubbleCheckIcon.stories.tsx +121 -1236
- package/dist/icons/bubble-crossed-icon/BubbleCrossedIcon.stories.tsx +121 -1213
- package/dist/icons/bubble-sparkle-icon/BubbleSparkleIcon.stories.tsx +116 -893
- package/dist/icons/camera-icon/CameraIcon.stories.tsx +109 -1254
- package/dist/icons/capital-a-letter-icon/CapitalALetterIcon.stories.tsx +114 -975
- package/dist/icons/chevron-double-left-icon/ChevronDoubleLeftIcon.stories.tsx +157 -994
- package/dist/icons/chevron-double-right-icon/ChevronDoubleRightIcon.stories.tsx +160 -992
- package/dist/icons/chevron-down-icon/ChevronDownIcon.stories.tsx +140 -970
- package/dist/icons/chevron-left-icon/ChevronLeftIcon.stories.tsx +126 -993
- package/dist/icons/chevron-right-icon/ChevronRightIcon.stories.tsx +144 -987
- package/dist/icons/chevron-up-icon/ChevronUpIcon.stories.tsx +141 -1007
- package/dist/icons/circle-tick-icon/CircleTickIcon.stories.tsx +147 -1187
- package/dist/icons/circular-play-icon/CircularPlayIcon.stories.tsx +110 -476
- package/dist/icons/coin-icon/CoinIcon.stories.tsx +120 -1364
- package/dist/icons/coin-toons-icon/CoinToonsIcon.stories.tsx +113 -1360
- package/dist/icons/column-wide-add-icon/ColumnWideAddIcon.stories.tsx +111 -942
- package/dist/icons/command-icon/CommandIcon.stories.tsx +124 -1087
- package/dist/icons/copy-icon/CopyIcon.stories.tsx +119 -996
- package/dist/icons/cross-circle-icon/CrossCircleIcon.stories.tsx +144 -1046
- package/dist/icons/cross-icon/CrossIcon.stories.tsx +136 -999
- package/dist/icons/download-icon/DownloadIcon.stories.tsx +123 -857
- package/dist/icons/edit-big-icon/EditBigIcon.stories.tsx +121 -1080
- package/dist/icons/email-icon/EmailIcon.stories.tsx +112 -979
- package/dist/icons/expand-icon/ExpandIcon.stories.tsx +109 -1146
- package/dist/icons/eye-close-icon/EyeCloseIcon.stories.tsx +141 -1068
- package/dist/icons/eye-open-icon/EyeOpenIcon.stories.tsx +140 -1081
- package/dist/icons/feature-shine-icon/FeatureShineIcon.stories.tsx +124 -1050
- package/dist/icons/file-chart-icon/FileChartIcon.stories.tsx +123 -1091
- package/dist/icons/file-text-icon/FileTextIcon.stories.tsx +122 -633
- package/dist/icons/filter-bar-row-icon/FilterBarRowIcon.stories.tsx +116 -1087
- package/dist/icons/forward-ten-seconds-icon/ForwardTenSecondsIcon.stories.tsx +166 -1020
- package/dist/icons/git-branch-icon/GitBranchIcon.stories.tsx +112 -1182
- package/dist/icons/git-fork-icon/GitForkIcon.stories.tsx +112 -1155
- package/dist/icons/globe-icon/GlobeIcon.stories.tsx +127 -325
- package/dist/icons/google-logo-icon/GoogleLogoIcon.stories.tsx +142 -985
- package/dist/icons/grip-vertical-icon/GripVerticalIcon.stories.tsx +116 -1217
- package/dist/icons/head-icon/HeadIcon.stories.tsx +108 -953
- package/dist/icons/heart-icon/HeartIcon.stories.tsx +117 -1060
- package/dist/icons/image-avatar-sparkle-icon/ImageAvatarSparkleIcon.stories.tsx +116 -716
- package/dist/icons/image-icon/ImageIcon.stories.tsx +102 -1164
- package/dist/icons/import-folder-icon/ImportFolderIcon.stories.tsx +108 -1233
- package/dist/icons/import-left-arrow-folder-icon/ImportLeftArrowFolderIcon.stories.tsx +133 -1289
- package/dist/icons/indian-flag-icon/IndianFlagIcon.stories.tsx +155 -1012
- package/dist/icons/instagram-icon/InstagramIcon.stories.tsx +158 -1438
- package/dist/icons/layout-column-icon/LayoutColumnIcon.stories.tsx +121 -1011
- package/dist/icons/layout-left-icon/LayoutLeftIcon.stories.tsx +116 -981
- package/dist/icons/layout-right-icon/LayoutRightIcon.stories.tsx +116 -979
- package/dist/icons/light-bulb-simple-icon/LightBulbSimpleIcon.stories.tsx +105 -1252
- package/dist/icons/linked-in-icon/LinkedInIcon.stories.tsx +151 -1554
- package/dist/icons/magic-book-icon/MagicBookIcon.stories.tsx +107 -1227
- package/dist/icons/magic-edit-icon/MagicEditIcon.stories.tsx +116 -707
- package/dist/icons/maintenance-icon/MaintenanceIcon.stories.tsx +119 -1226
- package/dist/icons/message-icon/MessageIcon.stories.tsx +111 -557
- package/dist/icons/minimize-icon/MinimizeIcon.stories.tsx +112 -1198
- package/dist/icons/moon-icon/MoonIcon.stories.tsx +117 -557
- package/dist/icons/move-horizontal-icon/MoveHorizontalIcon.stories.tsx +106 -1235
- package/dist/icons/move-vertical-icon/MoveVerticalIcon.stories.tsx +112 -1185
- package/dist/icons/musical-note-icon/MusicalNoteIcon.stories.tsx +116 -1012
- package/dist/icons/notepad-icon/NotepadIcon.stories.tsx +108 -1137
- package/dist/icons/notes-icon/NotesIcon.stories.tsx +116 -1138
- package/dist/icons/page-search-icon/PageSearchIcon.stories.tsx +106 -1146
- package/dist/icons/page-text-icon/PageTextIcon.stories.tsx +119 -719
- package/dist/icons/paint-roll-icon/PaintRollIcon.stories.tsx +110 -999
- package/dist/icons/paper-plane-icon/PaperPlaneIcon.stories.tsx +109 -912
- package/dist/icons/pause-icon/PauseIcon.stories.tsx +110 -1041
- package/dist/icons/pencil-icon/PencilIcon.stories.tsx +112 -1109
- package/dist/icons/phone-icon/PhoneIcon.stories.tsx +112 -1023
- package/dist/icons/plus-icon/PlusIcon.stories.tsx +103 -1132
- package/dist/icons/pocket-studio-icon/PocketStudioIcon.stories.tsx +104 -870
- package/dist/icons/scroll-down-icon/ScrollDownIcon.stories.tsx +99 -476
- package/dist/icons/search-icon/SearchIcon.stories.tsx +108 -1161
- package/dist/icons/setting-icon/SettingIcon.stories.tsx +104 -1009
- package/dist/icons/share-icon/ShareIcon.stories.tsx +117 -1064
- package/dist/icons/shield-icon/ShieldIcon.stories.tsx +114 -974
- package/dist/icons/site-logo-icon/SiteLogoIcon.stories.tsx +134 -1160
- package/dist/icons/skip-backward-icon/SkipBackwardIcon.stories.tsx +169 -1017
- package/dist/icons/skip-forward-icon/SkipForwardIcon.stories.tsx +161 -1016
- package/dist/icons/sparkles-soft-icon/SparklesSoftIcon.stories.tsx +102 -1001
- package/dist/icons/spinner-gradient-icon/SpinnerGradientIcon.stories.tsx +155 -593
- package/dist/icons/spinner-solid-icon/SpinnerSolidIcon.stories.tsx +155 -608
- package/dist/icons/spinner-solid-neutral-icon/SpinnerSolidINeutralcon.stories.tsx +142 -712
- package/dist/icons/star-icon/StarIcon.stories.tsx +120 -946
- package/dist/icons/store-coin-icon/StoreCoinIcon.stories.tsx +109 -1013
- package/dist/icons/suggestion-icon/SuggestionIcon.stories.tsx +113 -891
- package/dist/icons/sun-icon/SunIcon.stories.tsx +117 -864
- package/dist/icons/text-color-icon/TextColorIcon.stories.tsx +113 -989
- package/dist/icons/text-indicator-icon/TextIndicatorIcon.stories.tsx +120 -1027
- package/dist/icons/threads-icon/ThreadsIcon.stories.tsx +153 -1476
- package/dist/icons/tick-circle-icon/TickCircleIcon.stories.tsx +143 -1187
- package/dist/icons/tick-icon/TickIcon.stories.tsx +142 -1322
- package/dist/icons/trash-icon/TrashIcon.stories.tsx +105 -970
- package/dist/icons/twitter-x-icon/TwitterXIcon.stories.tsx +154 -1457
- package/dist/icons/upload-icon/UploadIcon.stories.tsx +112 -930
- package/dist/icons/vertical-menu-icon/VerticalMenuIcon.stories.tsx +115 -1019
- package/dist/icons/video-play-list-icon/VideoPlaylistIcon.stories.tsx +122 -1092
- package/dist/icons/voice-playing-icon/VoicePlayingIcon.stories.tsx +120 -1401
- package/dist/icons/volume-full-icon/VolumeFullIcon.stories.tsx +107 -1212
- package/dist/icons/volume-half-icon/VolumeHalfIcon.stories.tsx +109 -1122
- package/dist/icons/volume-off-icon/VolumeOffIcon.stories.tsx +112 -1124
- package/dist/icons/warning-icon/WarningIcon.stories.tsx +119 -1083
- package/dist/icons/youtube-icon/YoutubeIcon.stories.tsx +158 -983
- package/dist/index.cjs +90 -90
- package/dist/index.js +90 -90
- package/package.json +8 -3
|
@@ -1,997 +1,447 @@
|
|
|
1
1
|
import React, { useCallback, useState } from "react"
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react-vite"
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
import {
|
|
5
|
+
CircleTickIcon,
|
|
6
|
+
EyeOpenIcon,
|
|
7
|
+
FeatureShineIcon,
|
|
8
|
+
FileTextIcon,
|
|
9
|
+
LayoutColumnIcon,
|
|
10
|
+
MoveHorizontalIcon,
|
|
11
|
+
NotepadIcon,
|
|
12
|
+
} from "src/ui/icons"
|
|
13
|
+
import {
|
|
14
|
+
HookCodeBlock,
|
|
15
|
+
HookControlButton,
|
|
16
|
+
HookPanel,
|
|
17
|
+
HookPlaygroundCanvas,
|
|
18
|
+
HookStateCard,
|
|
19
|
+
HookStateGrid,
|
|
20
|
+
HookUsageCanvas,
|
|
21
|
+
HookUsageSection,
|
|
22
|
+
} from "src/ui/story-spec/hooks/hook-story-canvas"
|
|
23
|
+
import { AuralHookDocsPage } from "src/ui/story-spec/hooks/hook-story-docs-page"
|
|
24
|
+
|
|
5
25
|
import { usePrevious } from "."
|
|
6
26
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
type PlaygroundDemoProps = {
|
|
28
|
+
label?: string
|
|
29
|
+
value?: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const NUMBER_PRESETS = [0, 5, 12, 24, 48]
|
|
33
|
+
const STATUS_LABELS = ["idle", "draft", "queued", "published"]
|
|
34
|
+
|
|
35
|
+
const formatPreviousValue = (value: unknown) => {
|
|
36
|
+
if (value === undefined) return "undefined"
|
|
37
|
+
if (typeof value === "string") return `"${value}"`
|
|
38
|
+
if (typeof value === "object") return JSON.stringify(value)
|
|
39
|
+
return String(value)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const PreviousPlaygroundDemo: React.FC<PlaygroundDemoProps> = ({
|
|
43
|
+
label = "Tracked value",
|
|
44
|
+
value = 12,
|
|
45
|
+
}) => {
|
|
19
46
|
const previousValue = usePrevious(value)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<HookPlaygroundCanvas>
|
|
50
|
+
<div className="mx-auto w-full max-w-sm space-y-6 p-8">
|
|
51
|
+
<HookStateGrid>
|
|
52
|
+
<HookStateCard label={label} value={String(value)} />
|
|
53
|
+
<HookStateCard
|
|
54
|
+
label="Previous render"
|
|
55
|
+
value={formatPreviousValue(previousValue)}
|
|
56
|
+
/>
|
|
57
|
+
</HookStateGrid>
|
|
58
|
+
|
|
59
|
+
<HookPanel title="How it works">
|
|
60
|
+
<p className="text-fm-secondary font-fm-text text-fm-sm leading-fm-sm">
|
|
61
|
+
Update the{" "}
|
|
62
|
+
<span className="text-fm-primary font-medium">value</span> control
|
|
63
|
+
in the Storybook sidebar to compare the current input with the
|
|
64
|
+
previous render snapshot.
|
|
65
|
+
</p>
|
|
66
|
+
</HookPanel>
|
|
67
|
+
</div>
|
|
68
|
+
</HookPlaygroundCanvas>
|
|
36
69
|
)
|
|
70
|
+
}
|
|
37
71
|
|
|
38
|
-
|
|
72
|
+
const PreviousInteractiveDemo: React.FC = () => {
|
|
73
|
+
const [count, setCount] = useState(12)
|
|
74
|
+
const [status, setStatus] = useState("idle")
|
|
75
|
+
const [enabled, setEnabled] = useState(false)
|
|
76
|
+
const [selection, setSelection] = useState({
|
|
77
|
+
id: "mix-01",
|
|
78
|
+
label: "Morning Mix",
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
const previousCount = usePrevious(count)
|
|
82
|
+
const previousStatus = usePrevious(status)
|
|
83
|
+
const previousEnabled = usePrevious(enabled)
|
|
84
|
+
const previousSelection = usePrevious(selection)
|
|
39
85
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
count: prev.count + 1,
|
|
43
|
-
name: `test-${prev.count + 1}`,
|
|
44
|
-
}))
|
|
86
|
+
const incrementCount = useCallback(() => {
|
|
87
|
+
setCount((current) => current + 1)
|
|
45
88
|
}, [])
|
|
46
89
|
|
|
47
|
-
const
|
|
48
|
-
|
|
90
|
+
const cycleCount = useCallback(() => {
|
|
91
|
+
setCount((current) => {
|
|
92
|
+
const currentIndex = NUMBER_PRESETS.indexOf(current)
|
|
93
|
+
const nextIndex = (currentIndex + 1) % NUMBER_PRESETS.length
|
|
94
|
+
return NUMBER_PRESETS[nextIndex] ?? NUMBER_PRESETS[0]
|
|
95
|
+
})
|
|
49
96
|
}, [])
|
|
50
97
|
|
|
51
|
-
const
|
|
52
|
-
|
|
98
|
+
const cycleStatus = useCallback(() => {
|
|
99
|
+
setStatus((current) => {
|
|
100
|
+
const currentIndex = STATUS_LABELS.indexOf(current)
|
|
101
|
+
const nextIndex = (currentIndex + 1) % STATUS_LABELS.length
|
|
102
|
+
return STATUS_LABELS[nextIndex] ?? STATUS_LABELS[0]
|
|
103
|
+
})
|
|
53
104
|
}, [])
|
|
54
105
|
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
106
|
+
const toggleEnabled = useCallback(() => {
|
|
107
|
+
setEnabled((current) => !current)
|
|
108
|
+
}, [])
|
|
109
|
+
|
|
110
|
+
const swapSelection = useCallback(() => {
|
|
111
|
+
setSelection((current) =>
|
|
112
|
+
current.id === "mix-01"
|
|
113
|
+
? { id: "mix-02", label: "Focus Session" }
|
|
114
|
+
: { id: "mix-01", label: "Morning Mix" }
|
|
115
|
+
)
|
|
60
116
|
}, [])
|
|
61
117
|
|
|
62
118
|
return (
|
|
63
|
-
<
|
|
64
|
-
<div className="
|
|
65
|
-
<
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
</
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
</span>
|
|
122
|
-
</div>
|
|
123
|
-
<div className="flex items-center justify-between rounded-lg bg-black/20 !p-3">
|
|
124
|
-
<span className="!text-sm !text-white/70">Previous:</span>
|
|
125
|
-
<span className="font-mono !text-green-300">
|
|
126
|
-
{previousStringValue !== undefined
|
|
127
|
-
? `"${previousStringValue}"`
|
|
128
|
-
: "undefined"}
|
|
129
|
-
</span>
|
|
130
|
-
</div>
|
|
131
|
-
</div>
|
|
132
|
-
<div className="!space-y-2">
|
|
133
|
-
<button
|
|
134
|
-
onClick={updateString}
|
|
135
|
-
className="w-full rounded-lg border border-green-500/30 bg-green-500/20 !px-3 !py-2 !text-sm !text-green-300 transition-colors hover:bg-green-500/30"
|
|
136
|
-
>
|
|
137
|
-
Random String
|
|
138
|
-
</button>
|
|
139
|
-
<input
|
|
140
|
-
type="text"
|
|
141
|
-
value={stringValue}
|
|
142
|
-
onChange={(e) => setStringValue(e.target.value)}
|
|
143
|
-
className="w-full rounded-lg border border-white/20 bg-white/5 !px-3 !py-2 !text-sm !text-white placeholder-white/50"
|
|
144
|
-
placeholder="Type to update..."
|
|
145
|
-
/>
|
|
146
|
-
</div>
|
|
119
|
+
<HookPlaygroundCanvas>
|
|
120
|
+
<div className="mx-auto w-full max-w-4xl space-y-6 p-8">
|
|
121
|
+
<HookStateGrid>
|
|
122
|
+
<HookStateCard label="Current number" value={String(count)} />
|
|
123
|
+
<HookStateCard
|
|
124
|
+
label="Previous number"
|
|
125
|
+
value={formatPreviousValue(previousCount)}
|
|
126
|
+
/>
|
|
127
|
+
<HookStateCard label="Current status" value={status} />
|
|
128
|
+
<HookStateCard
|
|
129
|
+
label="Previous status"
|
|
130
|
+
value={formatPreviousValue(previousStatus)}
|
|
131
|
+
/>
|
|
132
|
+
<HookStateCard label="Current boolean" value={String(enabled)} />
|
|
133
|
+
<HookStateCard
|
|
134
|
+
label="Previous boolean"
|
|
135
|
+
value={formatPreviousValue(previousEnabled)}
|
|
136
|
+
/>
|
|
137
|
+
<HookStateCard label="Current object" value={selection.label} />
|
|
138
|
+
<HookStateCard
|
|
139
|
+
label="Previous object"
|
|
140
|
+
value={previousSelection ? previousSelection.label : "undefined"}
|
|
141
|
+
/>
|
|
142
|
+
</HookStateGrid>
|
|
143
|
+
|
|
144
|
+
<HookPanel title="Controls">
|
|
145
|
+
<div className="flex flex-wrap gap-3">
|
|
146
|
+
<HookControlButton
|
|
147
|
+
onClick={incrementCount}
|
|
148
|
+
variant="positive"
|
|
149
|
+
className="flex-1"
|
|
150
|
+
>
|
|
151
|
+
Increment number
|
|
152
|
+
</HookControlButton>
|
|
153
|
+
<HookControlButton
|
|
154
|
+
onClick={cycleCount}
|
|
155
|
+
variant="info"
|
|
156
|
+
className="flex-1"
|
|
157
|
+
>
|
|
158
|
+
Cycle preset number
|
|
159
|
+
</HookControlButton>
|
|
160
|
+
<HookControlButton onClick={cycleStatus} className="flex-1">
|
|
161
|
+
Cycle string state
|
|
162
|
+
</HookControlButton>
|
|
163
|
+
<HookControlButton
|
|
164
|
+
onClick={toggleEnabled}
|
|
165
|
+
variant={enabled ? "negative" : "positive"}
|
|
166
|
+
className="flex-1"
|
|
167
|
+
>
|
|
168
|
+
Toggle boolean
|
|
169
|
+
</HookControlButton>
|
|
170
|
+
<HookControlButton
|
|
171
|
+
onClick={swapSelection}
|
|
172
|
+
variant="warning"
|
|
173
|
+
className="flex-1"
|
|
174
|
+
>
|
|
175
|
+
Swap object value
|
|
176
|
+
</HookControlButton>
|
|
147
177
|
</div>
|
|
148
|
-
</
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
{String(boolValue)}
|
|
163
|
-
</span>
|
|
164
|
-
</div>
|
|
165
|
-
<div className="flex items-center justify-between rounded-lg bg-black/20 !p-3">
|
|
166
|
-
<span className="!text-sm !text-white/70">Previous:</span>
|
|
167
|
-
<span className="font-mono !text-purple-300">
|
|
168
|
-
{previousBoolValue !== undefined
|
|
169
|
-
? String(previousBoolValue)
|
|
170
|
-
: "undefined"}
|
|
171
|
-
</span>
|
|
172
|
-
</div>
|
|
173
|
-
</div>
|
|
174
|
-
<div>
|
|
175
|
-
<button
|
|
176
|
-
onClick={toggleBool}
|
|
177
|
-
className={`w-full rounded-lg border !px-4 !py-3 !text-sm font-medium transition-colors ${
|
|
178
|
-
boolValue
|
|
179
|
-
? "border-green-500/30 bg-green-500/20 !text-green-300 hover:bg-green-500/30"
|
|
180
|
-
: "border-red-500/30 bg-red-500/20 !text-red-300 hover:bg-red-500/30"
|
|
181
|
-
}`}
|
|
182
|
-
>
|
|
183
|
-
Toggle: {boolValue ? "ON" : "OFF"}
|
|
184
|
-
</button>
|
|
185
|
-
</div>
|
|
178
|
+
</HookPanel>
|
|
179
|
+
|
|
180
|
+
<HookPanel title="Tracked object snapshot">
|
|
181
|
+
<div className="grid gap-3 md:grid-cols-2">
|
|
182
|
+
<HookCodeBlock
|
|
183
|
+
code={`currentSelection = ${JSON.stringify(selection, null, 2)}`}
|
|
184
|
+
/>
|
|
185
|
+
<HookCodeBlock
|
|
186
|
+
code={`previousSelection = ${
|
|
187
|
+
previousSelection
|
|
188
|
+
? JSON.stringify(previousSelection, null, 2)
|
|
189
|
+
: "undefined"
|
|
190
|
+
}`}
|
|
191
|
+
/>
|
|
186
192
|
</div>
|
|
187
|
-
</
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
<div className="rounded-lg bg-black/20 !p-3">
|
|
197
|
-
<div className="!mb-1 !text-sm !text-white/70">Current:</div>
|
|
198
|
-
<pre className="overflow-x-auto !text-xs !text-orange-100">
|
|
199
|
-
{JSON.stringify(objectValue, null, 2)}
|
|
200
|
-
</pre>
|
|
201
|
-
</div>
|
|
202
|
-
<div className="rounded-lg bg-black/20 !p-3">
|
|
203
|
-
<div className="!mb-1 !text-sm !text-white/70">Previous:</div>
|
|
204
|
-
<pre className="overflow-x-auto !text-xs !text-orange-300">
|
|
205
|
-
{previousObjectValue !== undefined
|
|
206
|
-
? JSON.stringify(previousObjectValue, null, 2)
|
|
207
|
-
: "undefined"}
|
|
208
|
-
</pre>
|
|
209
|
-
</div>
|
|
210
|
-
</div>
|
|
211
|
-
<div>
|
|
212
|
-
<button
|
|
213
|
-
onClick={updateObject}
|
|
214
|
-
className="w-full rounded-lg border border-orange-500/30 bg-orange-500/20 !px-3 !py-2 !text-sm !text-orange-300 transition-colors hover:bg-orange-500/30"
|
|
215
|
-
>
|
|
216
|
-
Update Object
|
|
217
|
-
</button>
|
|
218
|
-
</div>
|
|
219
|
-
</div>
|
|
220
|
-
</div>
|
|
221
|
-
|
|
222
|
-
{/* Array Value Demo */}
|
|
223
|
-
<div className="rounded-lg border border-cyan-500/20 bg-cyan-500/10 !p-4">
|
|
224
|
-
<h4 className="!mb-3 !text-lg font-semibold !text-cyan-300">
|
|
225
|
-
Array Value
|
|
226
|
-
</h4>
|
|
227
|
-
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
|
|
228
|
-
<div className="!space-y-2">
|
|
229
|
-
<div className="rounded-lg bg-black/20 !p-3">
|
|
230
|
-
<div className="!mb-1 !text-sm !text-white/70">Current:</div>
|
|
231
|
-
<div className="font-mono !text-cyan-100">
|
|
232
|
-
[{arrayValue.join(", ")}]
|
|
233
|
-
</div>
|
|
234
|
-
<div className="!mt-1 !text-xs !text-white/50">
|
|
235
|
-
Length: {arrayValue.length}
|
|
236
|
-
</div>
|
|
237
|
-
</div>
|
|
238
|
-
<div className="rounded-lg bg-black/20 !p-3">
|
|
239
|
-
<div className="!mb-1 !text-sm !text-white/70">Previous:</div>
|
|
240
|
-
<div className="font-mono !text-cyan-300">
|
|
241
|
-
{previousArrayValue !== undefined
|
|
242
|
-
? `[${previousArrayValue.join(", ")}]`
|
|
243
|
-
: "undefined"}
|
|
244
|
-
</div>
|
|
245
|
-
<div className="!mt-1 !text-xs !text-white/50">
|
|
246
|
-
Length:{" "}
|
|
247
|
-
{previousArrayValue ? previousArrayValue.length : "N/A"}
|
|
248
|
-
</div>
|
|
249
|
-
</div>
|
|
250
|
-
</div>
|
|
251
|
-
<div className="!space-y-2">
|
|
252
|
-
<button
|
|
253
|
-
onClick={addToArray}
|
|
254
|
-
className="w-full rounded-lg border border-cyan-500/30 bg-cyan-500/20 !px-3 !py-2 !text-sm !text-cyan-300 transition-colors hover:bg-cyan-500/30"
|
|
255
|
-
>
|
|
256
|
-
Add Item
|
|
257
|
-
</button>
|
|
258
|
-
<button
|
|
259
|
-
onClick={removeFromArray}
|
|
260
|
-
disabled={arrayValue.length === 0}
|
|
261
|
-
className="w-full rounded-lg border border-red-500/30 bg-red-500/20 !px-3 !py-2 !text-sm !text-red-300 transition-colors hover:bg-red-500/30 disabled:cursor-not-allowed disabled:opacity-50"
|
|
262
|
-
>
|
|
263
|
-
Remove Item
|
|
264
|
-
</button>
|
|
265
|
-
</div>
|
|
266
|
-
</div>
|
|
267
|
-
</div>
|
|
268
|
-
|
|
269
|
-
{/* Usage Information */}
|
|
270
|
-
<div className="rounded-lg border border-white/10 bg-black/20 !p-4">
|
|
271
|
-
<h4 className="!mb-3 !text-lg font-semibold !text-white">
|
|
272
|
-
Hook Usage
|
|
273
|
-
</h4>
|
|
274
|
-
<div className="!space-y-2 !text-sm">
|
|
275
|
-
<div className="!text-white/70">
|
|
276
|
-
<span className="font-medium !text-white">Pattern:</span> const
|
|
277
|
-
previousValue = usePrevious(currentValue)
|
|
278
|
-
</div>
|
|
279
|
-
<div className="!text-white/70">
|
|
280
|
-
<span className="font-medium !text-white">Returns:</span> The
|
|
281
|
-
previous value from the last render, or undefined on first render
|
|
282
|
-
</div>
|
|
283
|
-
<div className="!text-white/70">
|
|
284
|
-
<span className="font-medium !text-white">Use cases:</span>{" "}
|
|
285
|
-
Comparing values, animations, tracking changes, debugging
|
|
286
|
-
</div>
|
|
287
|
-
</div>
|
|
288
|
-
</div>
|
|
193
|
+
</HookPanel>
|
|
194
|
+
|
|
195
|
+
<HookCodeBlock
|
|
196
|
+
code={`const previous = usePrevious(value)
|
|
197
|
+
|
|
198
|
+
if (previous !== value) {
|
|
199
|
+
// react to the transition between renders
|
|
200
|
+
}`}
|
|
201
|
+
/>
|
|
289
202
|
</div>
|
|
290
|
-
</
|
|
203
|
+
</HookPlaygroundCanvas>
|
|
291
204
|
)
|
|
292
205
|
}
|
|
293
206
|
|
|
294
|
-
const meta: Meta<typeof
|
|
207
|
+
const meta: Meta<typeof PreviousPlaygroundDemo> = {
|
|
295
208
|
title: "Hooks/usePrevious",
|
|
296
|
-
component:
|
|
209
|
+
component: PreviousPlaygroundDemo,
|
|
297
210
|
parameters: {
|
|
298
211
|
layout: "fullscreen",
|
|
299
212
|
backgrounds: {
|
|
300
213
|
default: "dark",
|
|
301
214
|
values: [
|
|
302
|
-
{ name: "dark", value: "
|
|
303
|
-
{ name: "darker", value: "
|
|
304
|
-
{ name: "light", value: "
|
|
215
|
+
{ name: "dark", value: "var(--color-fm-surface-primary)" },
|
|
216
|
+
{ name: "darker", value: "var(--color-fm-neutral-0)" },
|
|
217
|
+
{ name: "light", value: "var(--color-fm-neutral-1100)" },
|
|
305
218
|
],
|
|
306
219
|
},
|
|
307
220
|
docs: {
|
|
308
221
|
page: () => (
|
|
309
|
-
|
|
310
|
-
{
|
|
311
|
-
|
|
312
|
-
{
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
margin: 0 !important;
|
|
322
|
-
background: transparent !important;
|
|
323
|
-
}
|
|
324
|
-
.docs-story {
|
|
325
|
-
background: transparent !important;
|
|
326
|
-
}
|
|
327
|
-
.sbdocs {
|
|
328
|
-
background: transparent !important;
|
|
329
|
-
}
|
|
330
|
-
body {
|
|
331
|
-
background: #0a0a0a !important;
|
|
332
|
-
}
|
|
333
|
-
#storybook-docs {
|
|
334
|
-
background: #0a0a0a !important;
|
|
335
|
-
}
|
|
336
|
-
.sbdocs-preview {
|
|
337
|
-
background: transparent !important;
|
|
338
|
-
border: none !important;
|
|
339
|
-
}
|
|
340
|
-
.sbdocs-h1, .sbdocs-h2, .sbdocs-h3, .sbdocs-h4, .sbdocs-h5, .sbdocs-h6 {
|
|
341
|
-
color: white !important;
|
|
342
|
-
}
|
|
343
|
-
.sbdocs-p, .sbdocs-li {
|
|
344
|
-
color: rgba(255, 255, 255, 0.7) !important;
|
|
345
|
-
}
|
|
346
|
-
.sbdocs-code {
|
|
347
|
-
background: rgba(255, 255, 255, 0.1) !important;
|
|
348
|
-
color: rgba(168, 85, 247, 1) !important;
|
|
349
|
-
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
350
|
-
}
|
|
351
|
-
.sbdocs-pre {
|
|
352
|
-
background: rgba(0, 0, 0, 0.4) !important;
|
|
353
|
-
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
354
|
-
}
|
|
355
|
-
.sbdocs-table {
|
|
356
|
-
background: rgba(255, 255, 255, 0.05) !important;
|
|
357
|
-
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
358
|
-
}
|
|
359
|
-
.sbdocs-table th {
|
|
360
|
-
background: rgba(255, 255, 255, 0.05) !important;
|
|
361
|
-
color: white !important;
|
|
362
|
-
border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
363
|
-
}
|
|
364
|
-
.sbdocs-table td {
|
|
365
|
-
color: rgba(255, 255, 255, 0.7) !important;
|
|
366
|
-
border-bottom: 1px solid rgba(255, 255, 255, 0.05) !important;
|
|
367
|
-
}
|
|
368
|
-
`}
|
|
369
|
-
</style>
|
|
370
|
-
|
|
371
|
-
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-purple-900/20 to-gray-900">
|
|
372
|
-
{/* Header */}
|
|
373
|
-
<div className="relative overflow-hidden border-b border-white/10 bg-black/20 backdrop-blur-xl">
|
|
374
|
-
<div className="absolute inset-0 bg-gradient-to-r from-purple-500/10 via-transparent to-indigo-500/10" />
|
|
375
|
-
<div className="relative !mx-auto max-w-7xl !px-6 !py-16">
|
|
376
|
-
<div className="!space-y-6 text-center">
|
|
377
|
-
<div className="!mx-auto flex !h-24 !w-24 items-center justify-center rounded-2xl border border-purple-500/30 bg-gradient-to-br from-purple-500/20 to-indigo-500/20">
|
|
378
|
-
<span className="!text-4xl">⏮️</span>
|
|
379
|
-
</div>
|
|
380
|
-
<h1 className="!text-fm-primary !text-5xl font-bold">
|
|
381
|
-
usePrevious
|
|
382
|
-
</h1>
|
|
383
|
-
<p className="!mx-auto max-w-3xl !text-xl leading-relaxed !text-white/70">
|
|
384
|
-
A simple yet powerful React hook that captures and returns
|
|
385
|
-
the previous value of any variable. Essential for tracking
|
|
386
|
-
changes, implementing smooth transitions, and debugging
|
|
387
|
-
state updates.
|
|
388
|
-
</p>
|
|
389
|
-
|
|
390
|
-
{/* Stats */}
|
|
391
|
-
<div className="flex items-center justify-center gap-8 !pt-8">
|
|
392
|
-
<div className="text-center">
|
|
393
|
-
<div className="!text-3xl font-bold !text-purple-300">
|
|
394
|
-
Simple
|
|
395
|
-
</div>
|
|
396
|
-
<div className="!text-sm !text-white/60">
|
|
397
|
-
Easy to use API
|
|
398
|
-
</div>
|
|
399
|
-
</div>
|
|
400
|
-
<div className="!h-8 !w-px bg-white/20" />
|
|
401
|
-
<div className="text-center">
|
|
402
|
-
<div className="!text-3xl font-bold !text-indigo-300">
|
|
403
|
-
Universal
|
|
404
|
-
</div>
|
|
405
|
-
<div className="!text-sm !text-white/60">
|
|
406
|
-
Works with any type
|
|
407
|
-
</div>
|
|
408
|
-
</div>
|
|
409
|
-
<div className="!h-8 !w-px bg-white/20" />
|
|
410
|
-
<div className="text-center">
|
|
411
|
-
<div className="!text-3xl font-bold !text-cyan-300">
|
|
412
|
-
Lightweight
|
|
413
|
-
</div>
|
|
414
|
-
<div className="!text-sm !text-white/60">
|
|
415
|
-
Minimal overhead
|
|
416
|
-
</div>
|
|
417
|
-
</div>
|
|
418
|
-
</div>
|
|
419
|
-
</div>
|
|
420
|
-
</div>
|
|
421
|
-
</div>
|
|
422
|
-
|
|
423
|
-
{/* Content */}
|
|
424
|
-
<div className="!mx-auto max-w-7xl !space-y-16 !px-6 !py-12">
|
|
425
|
-
{/* Quick Usage */}
|
|
426
|
-
<div className="!space-y-8">
|
|
427
|
-
<h2 className="text-center !text-3xl font-bold !text-white">
|
|
428
|
-
Quick Start
|
|
429
|
-
</h2>
|
|
430
|
-
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
|
431
|
-
<div className="!space-y-4">
|
|
432
|
-
<h3 className="!text-xl font-semibold !text-purple-300">
|
|
433
|
-
Basic Usage
|
|
434
|
-
</h3>
|
|
435
|
-
<div className="rounded-lg bg-black/40 !p-4">
|
|
436
|
-
<pre className="overflow-x-auto !text-sm !text-green-300">
|
|
437
|
-
{`import { usePrevious } from "@hooks/use-previous"
|
|
438
|
-
|
|
439
|
-
function MyComponent() {
|
|
440
|
-
const [count, setCount] = useState(0)
|
|
441
|
-
const [name, setName] = useState("John")
|
|
442
|
-
|
|
443
|
-
// Get previous values
|
|
444
|
-
const previousCount = usePrevious(count)
|
|
445
|
-
const previousName = usePrevious(name)
|
|
222
|
+
<AuralHookDocsPage
|
|
223
|
+
icon={MoveHorizontalIcon}
|
|
224
|
+
features={[
|
|
225
|
+
{ title: "Reliable", description: "Previous render snapshot" },
|
|
226
|
+
{ title: "Generic", description: "Tracks any value type" },
|
|
227
|
+
{ title: "Minimal", description: "Ref based implementation" },
|
|
228
|
+
]}
|
|
229
|
+
quickStart={{
|
|
230
|
+
codeExample: `import { usePrevious } from "src/ui/hooks/use-previous"
|
|
231
|
+
|
|
232
|
+
function PriceSummary({ amount }: { amount: number }) {
|
|
233
|
+
const previousAmount = usePrevious(amount)
|
|
446
234
|
|
|
447
235
|
return (
|
|
448
236
|
<div>
|
|
449
|
-
<
|
|
450
|
-
|
|
451
|
-
</div>
|
|
452
|
-
<div>
|
|
453
|
-
Name: {name} (was: {previousName ?? "N/A"})
|
|
454
|
-
</div>
|
|
455
|
-
|
|
456
|
-
<button onClick={() => setCount(c => c + 1)}>
|
|
457
|
-
Increment
|
|
458
|
-
</button>
|
|
459
|
-
<button onClick={() => setName("Jane")}>
|
|
460
|
-
Change Name
|
|
461
|
-
</button>
|
|
237
|
+
<p>Current: {amount}</p>
|
|
238
|
+
<p>Previous: {previousAmount ?? "undefined"}</p>
|
|
462
239
|
</div>
|
|
463
240
|
)
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
<table className="!my-0 w-full">
|
|
533
|
-
<thead className="bg-white/5">
|
|
534
|
-
<tr className="border-b border-white/10">
|
|
535
|
-
<th className="!px-6 !py-4 text-left !text-sm font-semibold !text-white">
|
|
536
|
-
Parameter
|
|
537
|
-
</th>
|
|
538
|
-
<th className="!px-6 !py-4 text-left !text-sm font-semibold !text-white">
|
|
539
|
-
Type
|
|
540
|
-
</th>
|
|
541
|
-
<th className="!px-6 !py-4 text-left !text-sm font-semibold !text-white">
|
|
542
|
-
Description
|
|
543
|
-
</th>
|
|
544
|
-
</tr>
|
|
545
|
-
</thead>
|
|
546
|
-
<tbody>
|
|
547
|
-
<tr className="border-b border-white/5">
|
|
548
|
-
<td className="!px-6 !py-4 font-mono !text-sm !text-purple-300">
|
|
549
|
-
value
|
|
550
|
-
</td>
|
|
551
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
552
|
-
T (generic)
|
|
553
|
-
</td>
|
|
554
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
555
|
-
The current value to track
|
|
556
|
-
</td>
|
|
557
|
-
</tr>
|
|
558
|
-
<tr className="!bg-black/10">
|
|
559
|
-
<td className="!px-6 !py-4 font-mono !text-sm !text-purple-300">
|
|
560
|
-
returns
|
|
561
|
-
</td>
|
|
562
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
563
|
-
T | undefined
|
|
564
|
-
</td>
|
|
565
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
566
|
-
Previous value from last render, undefined on first
|
|
567
|
-
render
|
|
568
|
-
</td>
|
|
569
|
-
</tr>
|
|
570
|
-
</tbody>
|
|
571
|
-
</table>
|
|
572
|
-
</div>
|
|
573
|
-
</div>
|
|
574
|
-
|
|
575
|
-
{/* Use Cases */}
|
|
576
|
-
<div className="!space-y-8">
|
|
577
|
-
<h2 className="text-center !text-3xl font-bold !text-white">
|
|
578
|
-
Common Use Cases
|
|
579
|
-
</h2>
|
|
580
|
-
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
581
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
582
|
-
<div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-blue-500/20">
|
|
583
|
-
<span className="!text-2xl">📊</span>
|
|
584
|
-
</div>
|
|
585
|
-
<h3 className="!mb-2 !text-lg font-semibold !text-white">
|
|
586
|
-
Value Comparison
|
|
587
|
-
</h3>
|
|
588
|
-
<p className="!text-sm !text-white/70">
|
|
589
|
-
Compare current and previous values to detect changes and
|
|
590
|
-
trigger side effects.
|
|
591
|
-
</p>
|
|
592
|
-
</div>
|
|
593
|
-
|
|
594
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
595
|
-
<div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-green-500/20">
|
|
596
|
-
<span className="!text-2xl">🎬</span>
|
|
597
|
-
</div>
|
|
598
|
-
<h3 className="!mb-2 !text-lg font-semibold !text-white">
|
|
599
|
-
Animations
|
|
600
|
-
</h3>
|
|
601
|
-
<p className="!text-sm !text-white/70">
|
|
602
|
-
Create smooth transitions by animating between previous
|
|
603
|
-
and current values.
|
|
604
|
-
</p>
|
|
605
|
-
</div>
|
|
606
|
-
|
|
607
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
608
|
-
<div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-purple-500/20">
|
|
609
|
-
<span className="!text-2xl">🐛</span>
|
|
610
|
-
</div>
|
|
611
|
-
<h3 className="!mb-2 !text-lg font-semibold !text-white">
|
|
612
|
-
Debugging
|
|
613
|
-
</h3>
|
|
614
|
-
<p className="!text-sm !text-white/70">
|
|
615
|
-
Track state changes during development to understand
|
|
616
|
-
component behavior.
|
|
617
|
-
</p>
|
|
618
|
-
</div>
|
|
619
|
-
|
|
620
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
621
|
-
<div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-orange-500/20">
|
|
622
|
-
<span className="!text-2xl">🔄</span>
|
|
623
|
-
</div>
|
|
624
|
-
<h3 className="!mb-2 !text-lg font-semibold !text-white">
|
|
625
|
-
Undo/Redo
|
|
626
|
-
</h3>
|
|
627
|
-
<p className="!text-sm !text-white/70">
|
|
628
|
-
Implement simple undo functionality by maintaining
|
|
629
|
-
previous state values.
|
|
630
|
-
</p>
|
|
631
|
-
</div>
|
|
632
|
-
|
|
633
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
634
|
-
<div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-cyan-500/20">
|
|
635
|
-
<span className="!text-2xl">📈</span>
|
|
636
|
-
</div>
|
|
637
|
-
<h3 className="!mb-2 !text-lg font-semibold !text-white">
|
|
638
|
-
Change Tracking
|
|
639
|
-
</h3>
|
|
640
|
-
<p className="!text-sm !text-white/70">
|
|
641
|
-
Monitor value changes for analytics, logging, or
|
|
642
|
-
validation purposes.
|
|
643
|
-
</p>
|
|
644
|
-
</div>
|
|
645
|
-
|
|
646
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
647
|
-
<div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-pink-500/20">
|
|
648
|
-
<span className="!text-2xl">⚡</span>
|
|
649
|
-
</div>
|
|
650
|
-
<h3 className="!mb-2 !text-lg font-semibold !text-white">
|
|
651
|
-
Performance
|
|
652
|
-
</h3>
|
|
653
|
-
<p className="!text-sm !text-white/70">
|
|
654
|
-
Optimize renders by comparing previous values before
|
|
655
|
-
expensive operations.
|
|
656
|
-
</p>
|
|
657
|
-
</div>
|
|
658
|
-
</div>
|
|
659
|
-
</div>
|
|
660
|
-
|
|
661
|
-
{/* Usage Patterns */}
|
|
662
|
-
<div className="!space-y-8">
|
|
663
|
-
<h2 className="text-center !text-3xl font-bold !text-white">
|
|
664
|
-
Usage Patterns
|
|
665
|
-
</h2>
|
|
666
|
-
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
|
667
|
-
<div className="!space-y-4">
|
|
668
|
-
<h3 className="!text-xl font-semibold !text-purple-300">
|
|
669
|
-
Conditional Effects
|
|
670
|
-
</h3>
|
|
671
|
-
<div className="rounded-lg bg-black/40 !p-4">
|
|
672
|
-
<pre className="overflow-x-auto !text-sm !text-green-300">
|
|
673
|
-
{`function UserProfile({ userId }) {
|
|
674
|
-
const [user, setUser] = useState(null)
|
|
675
|
-
const previousUserId = usePrevious(userId)
|
|
676
|
-
|
|
677
|
-
useEffect(() => {
|
|
678
|
-
// Only fetch if userId actually changed
|
|
679
|
-
if (userId !== previousUserId) {
|
|
680
|
-
fetchUser(userId).then(setUser)
|
|
681
|
-
}
|
|
682
|
-
}, [userId, previousUserId])
|
|
683
|
-
|
|
684
|
-
return <div>{user?.name}</div>
|
|
685
|
-
}`}
|
|
686
|
-
</pre>
|
|
687
|
-
</div>
|
|
688
|
-
</div>
|
|
689
|
-
|
|
690
|
-
<div className="!space-y-4">
|
|
691
|
-
<h3 className="!text-xl font-semibold !text-purple-300">
|
|
692
|
-
Animation Transitions
|
|
693
|
-
</h3>
|
|
694
|
-
<div className="rounded-lg bg-black/40 !p-4">
|
|
695
|
-
<pre className="overflow-x-auto !text-sm !text-green-300">
|
|
696
|
-
{`function AnimatedCounter({ value }) {
|
|
697
|
-
const previousValue = usePrevious(value)
|
|
698
|
-
const [displayValue, setDisplayValue] = useState(value)
|
|
699
|
-
|
|
700
|
-
useEffect(() => {
|
|
701
|
-
if (previousValue !== undefined) {
|
|
702
|
-
// Animate from previous to current
|
|
703
|
-
animateValue(previousValue, value, setDisplayValue)
|
|
704
|
-
}
|
|
705
|
-
}, [value, previousValue])
|
|
706
|
-
|
|
707
|
-
return <span>{displayValue}</span>
|
|
708
|
-
}`}
|
|
709
|
-
</pre>
|
|
710
|
-
</div>
|
|
711
|
-
</div>
|
|
712
|
-
|
|
713
|
-
<div className="!space-y-4">
|
|
714
|
-
<h3 className="!text-xl font-semibold !text-purple-300">
|
|
715
|
-
Change Detection
|
|
716
|
-
</h3>
|
|
717
|
-
<div className="rounded-lg bg-black/40 !p-4">
|
|
718
|
-
<pre className="overflow-x-auto !text-sm !text-green-300">
|
|
719
|
-
{`function FormField({ value, onChange }) {
|
|
720
|
-
const previousValue = usePrevious(value)
|
|
721
|
-
const [hasChanged, setHasChanged] = useState(false)
|
|
241
|
+
}`,
|
|
242
|
+
hookProperties: [
|
|
243
|
+
{ label: "Returns", value: "Previous value or undefined" },
|
|
244
|
+
{ label: "Input", value: "Any tracked value" },
|
|
245
|
+
{ label: "Storage", value: "Persistent ref" },
|
|
246
|
+
{ label: "Update timing", value: "After each render" },
|
|
247
|
+
],
|
|
248
|
+
}}
|
|
249
|
+
hookSignature={`function usePrevious<T>(value: T): T | undefined`}
|
|
250
|
+
parameters={[
|
|
251
|
+
{
|
|
252
|
+
name: "value",
|
|
253
|
+
type: "T",
|
|
254
|
+
required: true,
|
|
255
|
+
description: "The current value to track across renders.",
|
|
256
|
+
},
|
|
257
|
+
]}
|
|
258
|
+
returns={[
|
|
259
|
+
{
|
|
260
|
+
name: "previousValue",
|
|
261
|
+
type: "T | undefined",
|
|
262
|
+
description:
|
|
263
|
+
"The value from the previous render, or undefined on the first render.",
|
|
264
|
+
},
|
|
265
|
+
]}
|
|
266
|
+
useCases={[
|
|
267
|
+
{
|
|
268
|
+
icon: NotepadIcon,
|
|
269
|
+
title: "Form Diffing",
|
|
270
|
+
description:
|
|
271
|
+
"Compare current and previous field values before autosaving or validating.",
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
icon: EyeOpenIcon,
|
|
275
|
+
title: "Visibility Changes",
|
|
276
|
+
description:
|
|
277
|
+
"Detect when UI state flips between hidden and visible across renders.",
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
icon: LayoutColumnIcon,
|
|
281
|
+
title: "Filter Panels",
|
|
282
|
+
description:
|
|
283
|
+
"Track previous selections to decide when data needs to be refetched.",
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
icon: FileTextIcon,
|
|
287
|
+
title: "Draft History",
|
|
288
|
+
description:
|
|
289
|
+
"Show editors the last saved title, body, or metadata alongside the current draft.",
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
icon: CircleTickIcon,
|
|
293
|
+
title: "Status Transitions",
|
|
294
|
+
description:
|
|
295
|
+
"React to changes like idle to loading or loading to success with clear context.",
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
icon: FeatureShineIcon,
|
|
299
|
+
title: "Analytics Events",
|
|
300
|
+
description:
|
|
301
|
+
"Emit events only when a tracked value actually transitions between meaningful states.",
|
|
302
|
+
},
|
|
303
|
+
]}
|
|
304
|
+
usagePatterns={[
|
|
305
|
+
{
|
|
306
|
+
title: "Detecting a Search Term Change",
|
|
307
|
+
code: `function SearchResults({ query }: { query: string }) {
|
|
308
|
+
const previousQuery = usePrevious(query)
|
|
722
309
|
|
|
723
310
|
useEffect(() => {
|
|
724
|
-
if (
|
|
725
|
-
|
|
726
|
-
setHasChanged(true)
|
|
727
|
-
// Log change for analytics
|
|
728
|
-
logFieldChange(previousValue, value)
|
|
311
|
+
if (previousQuery && previousQuery !== query) {
|
|
312
|
+
analytics.track("search_changed", { from: previousQuery, to: query })
|
|
729
313
|
}
|
|
730
|
-
}, [
|
|
314
|
+
}, [query, previousQuery])
|
|
315
|
+
|
|
316
|
+
return <ResultsList query={query} />
|
|
317
|
+
}`,
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
title: "Comparing Pagination State",
|
|
321
|
+
code: `function Pager({ page }: { page: number }) {
|
|
322
|
+
const previousPage = usePrevious(page)
|
|
323
|
+
const direction =
|
|
324
|
+
previousPage === undefined
|
|
325
|
+
? "start"
|
|
326
|
+
: page > previousPage
|
|
327
|
+
? "forward"
|
|
328
|
+
: "backward"
|
|
329
|
+
|
|
330
|
+
return <span>{direction}</span>
|
|
331
|
+
}`,
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
title: "Tracking Object Transitions",
|
|
335
|
+
code: `function PlaylistHeader({ playlist }: { playlist: Playlist }) {
|
|
336
|
+
const previousPlaylist = usePrevious(playlist)
|
|
337
|
+
|
|
338
|
+
const changedOwner =
|
|
339
|
+
previousPlaylist &&
|
|
340
|
+
previousPlaylist.ownerId !== playlist.ownerId
|
|
731
341
|
|
|
732
342
|
return (
|
|
733
|
-
<
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
/>
|
|
343
|
+
<header>
|
|
344
|
+
{changedOwner && <span>Owner changed</span>}
|
|
345
|
+
<h2>{playlist.name}</h2>
|
|
346
|
+
</header>
|
|
738
347
|
)
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
<div className="!space-y-4">
|
|
745
|
-
<h3 className="!text-xl font-semibold !text-purple-300">
|
|
746
|
-
State Validation
|
|
747
|
-
</h3>
|
|
748
|
-
<div className="rounded-lg bg-black/40 !p-4">
|
|
749
|
-
<pre className="overflow-x-auto !text-sm !text-green-300">
|
|
750
|
-
{`function DataTable({ sortColumn, sortDirection }) {
|
|
751
|
-
const [data, setData] = useState([])
|
|
752
|
-
const previousSort = usePrevious({
|
|
753
|
-
column: sortColumn,
|
|
754
|
-
direction: sortDirection
|
|
755
|
-
})
|
|
348
|
+
}`,
|
|
349
|
+
},
|
|
350
|
+
]}
|
|
351
|
+
implementation={{
|
|
352
|
+
code: `import { useEffect, useRef } from "react"
|
|
756
353
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
column: sortColumn,
|
|
760
|
-
direction: sortDirection
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
// Only re-sort if sort config changed
|
|
764
|
-
if (!isEqual(currentSort, previousSort)) {
|
|
765
|
-
const sorted = sortData(data, currentSort)
|
|
766
|
-
setData(sorted)
|
|
767
|
-
}
|
|
768
|
-
}, [sortColumn, sortDirection, previousSort])
|
|
354
|
+
export function usePrevious<T>(value: T): T | undefined {
|
|
355
|
+
const ref = useRef<T | undefined>(undefined)
|
|
769
356
|
|
|
770
|
-
|
|
771
|
-
}`}
|
|
772
|
-
</pre>
|
|
773
|
-
</div>
|
|
774
|
-
</div>
|
|
775
|
-
</div>
|
|
776
|
-
</div>
|
|
777
|
-
|
|
778
|
-
{/* Implementation Details */}
|
|
779
|
-
<div className="!space-y-8">
|
|
780
|
-
<h2 className="text-center !text-3xl font-bold !text-white">
|
|
781
|
-
Implementation
|
|
782
|
-
</h2>
|
|
783
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
784
|
-
<h3 className="!mb-4 !text-xl font-semibold !text-white">
|
|
785
|
-
Hook Implementation
|
|
786
|
-
</h3>
|
|
787
|
-
<div className="rounded-lg bg-black/40 !p-4">
|
|
788
|
-
<pre className="overflow-x-auto !text-sm !text-blue-300">
|
|
789
|
-
{`import { useRef, useEffect } from 'react'
|
|
790
|
-
|
|
791
|
-
function usePrevious<T>(value: T): T | undefined {
|
|
792
|
-
const ref = useRef<T>()
|
|
793
|
-
|
|
357
|
+
// Store current value in ref
|
|
794
358
|
useEffect(() => {
|
|
795
359
|
ref.current = value
|
|
796
|
-
})
|
|
797
|
-
|
|
360
|
+
}, [value]) // Only re-run if value changes
|
|
361
|
+
|
|
362
|
+
// Return previous value (happens before update in useEffect above)
|
|
798
363
|
return ref.current
|
|
799
364
|
}
|
|
800
365
|
|
|
801
|
-
export
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
<code className="!text-purple-300">useEffect</code> to
|
|
810
|
-
update it after each render.
|
|
811
|
-
</p>
|
|
812
|
-
<p className="!text-white">
|
|
813
|
-
Since <code className="!text-purple-300">useEffect</code>{" "}
|
|
814
|
-
runs after the render, the ref contains the value from the
|
|
815
|
-
previous render cycle.
|
|
816
|
-
</p>
|
|
817
|
-
</div>
|
|
818
|
-
</div>
|
|
819
|
-
</div>
|
|
820
|
-
</div>
|
|
821
|
-
|
|
822
|
-
{/* Footer */}
|
|
823
|
-
<div className="border-t border-white/10 bg-black/20 backdrop-blur-xl">
|
|
824
|
-
<div className="!mx-auto max-w-7xl !px-6 !py-8">
|
|
825
|
-
<div className="!space-y-4 text-center">
|
|
826
|
-
<p className="!text-white/60">
|
|
827
|
-
usePrevious is a fundamental hook that enables powerful
|
|
828
|
-
patterns for state management, animations, and performance
|
|
829
|
-
optimizations in React applications.
|
|
830
|
-
</p>
|
|
831
|
-
<p className="!text-sm !text-white/40">
|
|
832
|
-
Simple, reliable, and type-safe - perfect for tracking any
|
|
833
|
-
value changes across component re-renders.
|
|
834
|
-
</p>
|
|
835
|
-
</div>
|
|
836
|
-
</div>
|
|
837
|
-
</div>
|
|
838
|
-
</div>
|
|
839
|
-
</>
|
|
366
|
+
export default usePrevious`,
|
|
367
|
+
notes: [
|
|
368
|
+
"The ref persists across renders without triggering extra re-renders when it updates.",
|
|
369
|
+
"The effect writes the latest value after React commits, so the returned value is always the previous render snapshot.",
|
|
370
|
+
"The generic signature keeps the hook flexible for primitives, objects, arrays, and derived values.",
|
|
371
|
+
],
|
|
372
|
+
}}
|
|
373
|
+
/>
|
|
840
374
|
),
|
|
841
375
|
},
|
|
842
376
|
},
|
|
843
377
|
tags: ["autodocs"],
|
|
844
378
|
argTypes: {
|
|
845
|
-
|
|
379
|
+
label: {
|
|
846
380
|
control: "text",
|
|
847
|
-
description: "
|
|
381
|
+
description: "Label shown for the tracked current value.",
|
|
848
382
|
},
|
|
849
|
-
|
|
383
|
+
value: {
|
|
850
384
|
control: { type: "number" },
|
|
851
|
-
description: "
|
|
385
|
+
description: "Current value passed into usePrevious.",
|
|
852
386
|
},
|
|
853
387
|
},
|
|
854
388
|
}
|
|
855
389
|
|
|
856
390
|
export default meta
|
|
857
|
-
type Story = StoryObj<typeof
|
|
858
|
-
|
|
859
|
-
// Story parameters for consistent dark theme
|
|
860
|
-
const storyParameters = {
|
|
861
|
-
backgrounds: {
|
|
862
|
-
default: "dark",
|
|
863
|
-
values: [
|
|
864
|
-
{ name: "dark", value: "#0a0a0a" },
|
|
865
|
-
{ name: "darker", value: "#000000" },
|
|
866
|
-
],
|
|
867
|
-
},
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
export const Default: Story = {
|
|
871
|
-
args: {
|
|
872
|
-
title: "usePrevious Hook Demo",
|
|
873
|
-
initialValue: 0,
|
|
874
|
-
},
|
|
875
|
-
parameters: storyParameters,
|
|
876
|
-
render: (args) => (
|
|
877
|
-
<div className="min-h-dvh bg-gradient-to-br from-gray-900 to-gray-800 !p-8">
|
|
878
|
-
<PreviousValueDemo {...args} />
|
|
879
|
-
</div>
|
|
880
|
-
),
|
|
881
|
-
}
|
|
391
|
+
type Story = StoryObj<typeof meta>
|
|
882
392
|
|
|
883
|
-
export const
|
|
393
|
+
export const Playground: Story = {
|
|
884
394
|
args: {
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
},
|
|
888
|
-
parameters: {
|
|
889
|
-
...storyParameters,
|
|
890
|
-
docs: {
|
|
891
|
-
description: {
|
|
892
|
-
story:
|
|
893
|
-
"Demonstrates tracking numeric values with the usePrevious hook.",
|
|
894
|
-
},
|
|
895
|
-
},
|
|
395
|
+
label: "Tracked value",
|
|
396
|
+
value: 12,
|
|
896
397
|
},
|
|
897
|
-
render: (args) =>
|
|
898
|
-
<div className="min-h-dvh bg-gradient-to-br from-gray-900 to-gray-800 !p-8">
|
|
899
|
-
<PreviousValueDemo {...args} />
|
|
900
|
-
</div>
|
|
901
|
-
),
|
|
398
|
+
render: (args) => <PreviousPlaygroundDemo {...args} />,
|
|
902
399
|
}
|
|
903
400
|
|
|
904
|
-
export const
|
|
905
|
-
|
|
906
|
-
title: "Multiple Data Types",
|
|
907
|
-
initialValue: 100,
|
|
908
|
-
},
|
|
909
|
-
parameters: {
|
|
910
|
-
...storyParameters,
|
|
911
|
-
docs: {
|
|
912
|
-
description: {
|
|
913
|
-
story:
|
|
914
|
-
"Shows how usePrevious works with different data types: numbers, strings, booleans, objects, and arrays.",
|
|
915
|
-
},
|
|
916
|
-
},
|
|
917
|
-
},
|
|
918
|
-
render: (args) => (
|
|
919
|
-
<div className="min-h-dvh bg-gradient-to-br from-gray-900 to-gray-800 !p-8">
|
|
920
|
-
<PreviousValueDemo {...args} />
|
|
921
|
-
</div>
|
|
922
|
-
),
|
|
401
|
+
export const Interactive: Story = {
|
|
402
|
+
render: () => <PreviousInteractiveDemo />,
|
|
923
403
|
}
|
|
924
404
|
|
|
925
|
-
export const
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
docs: {
|
|
933
|
-
description: {
|
|
934
|
-
story:
|
|
935
|
-
"Full interactive demo showing the usePrevious hook with real-time value tracking across different data types.",
|
|
936
|
-
},
|
|
937
|
-
},
|
|
938
|
-
},
|
|
939
|
-
render: (args) => (
|
|
940
|
-
<div className="min-h-dvh bg-gradient-to-br from-gray-900 to-gray-800 !p-8">
|
|
941
|
-
<div className="!space-y-8">
|
|
942
|
-
<PreviousValueDemo {...args} />
|
|
943
|
-
|
|
944
|
-
{/* Additional Usage Example */}
|
|
945
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
946
|
-
<h3 className="!mb-4 !text-xl font-semibold !text-white">
|
|
947
|
-
Real-world Example
|
|
948
|
-
</h3>
|
|
949
|
-
<div className="rounded-lg bg-black/40 !p-4">
|
|
950
|
-
<pre className="overflow-x-auto !text-sm !text-blue-300">
|
|
951
|
-
{`// Compare with previous props to avoid unnecessary API calls
|
|
952
|
-
function UserDetails({ userId }) {
|
|
953
|
-
const [user, setUser] = useState(null)
|
|
954
|
-
const [loading, setLoading] = useState(false)
|
|
955
|
-
const previousUserId = usePrevious(userId)
|
|
405
|
+
export const UseCases: Story = {
|
|
406
|
+
render: () => (
|
|
407
|
+
<HookUsageCanvas>
|
|
408
|
+
<HookUsageSection title="Form field change awareness">
|
|
409
|
+
<HookCodeBlock
|
|
410
|
+
code={`function ShippingForm({ country }: { country: string }) {
|
|
411
|
+
const previousCountry = usePrevious(country)
|
|
956
412
|
|
|
957
413
|
useEffect(() => {
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
setLoading(true)
|
|
961
|
-
fetchUserById(userId)
|
|
962
|
-
.then(setUser)
|
|
963
|
-
.finally(() => setLoading(false))
|
|
414
|
+
if (previousCountry && previousCountry !== country) {
|
|
415
|
+
resetProvinceField()
|
|
964
416
|
}
|
|
965
|
-
}, [
|
|
417
|
+
}, [country, previousCountry])
|
|
418
|
+
}`}
|
|
419
|
+
/>
|
|
420
|
+
</HookUsageSection>
|
|
421
|
+
|
|
422
|
+
<HookUsageSection title="Navigation direction">
|
|
423
|
+
<HookCodeBlock
|
|
424
|
+
code={`function PageStepper({ page }: { page: number }) {
|
|
425
|
+
const previousPage = usePrevious(page)
|
|
426
|
+
const movedForward =
|
|
427
|
+
previousPage !== undefined && page > previousPage
|
|
966
428
|
|
|
967
|
-
|
|
968
|
-
return <div>{user?.name}</div>
|
|
429
|
+
return <span>{movedForward ? "Next" : "Back"}</span>
|
|
969
430
|
}`}
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
</div>
|
|
973
|
-
</div>
|
|
974
|
-
</div>
|
|
975
|
-
),
|
|
976
|
-
}
|
|
431
|
+
/>
|
|
432
|
+
</HookUsageSection>
|
|
977
433
|
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
initialValue: 50,
|
|
991
|
-
},
|
|
992
|
-
render: (args) => (
|
|
993
|
-
<div className="min-h-dvh bg-gradient-to-br from-gray-900 to-gray-800 !p-8">
|
|
994
|
-
<PreviousValueDemo {...args} />
|
|
995
|
-
</div>
|
|
434
|
+
<HookUsageSection title="Comparing API payloads">
|
|
435
|
+
<HookCodeBlock
|
|
436
|
+
code={`function SyncBadge({ payload }: { payload: Payload }) {
|
|
437
|
+
const previousPayload = usePrevious(payload)
|
|
438
|
+
const changedVersion =
|
|
439
|
+
previousPayload?.version !== payload.version
|
|
440
|
+
|
|
441
|
+
return changedVersion ? <Badge>Updated</Badge> : null
|
|
442
|
+
}`}
|
|
443
|
+
/>
|
|
444
|
+
</HookUsageSection>
|
|
445
|
+
</HookUsageCanvas>
|
|
996
446
|
),
|
|
997
447
|
}
|