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,12 +1,93 @@
|
|
|
1
|
-
import React from "react"
|
|
1
|
+
import React, { useMemo, useState } from "react"
|
|
2
2
|
import type { Meta, StoryObj } from "@storybook/react-vite"
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
FeatureShineIcon,
|
|
6
|
+
FileTextIcon,
|
|
7
|
+
LayoutColumnIcon,
|
|
8
|
+
NotepadIcon,
|
|
9
|
+
PageTextIcon,
|
|
10
|
+
SearchIcon,
|
|
11
|
+
SettingIcon,
|
|
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
|
+
|
|
25
|
+
import { useStandalonePagination, type UseStandalonePaginationProps } from "."
|
|
26
|
+
|
|
27
|
+
type PlaygroundDemoProps = UseStandalonePaginationProps & {
|
|
28
|
+
label?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const PAGE_SIZE_OPTIONS = [5, 10, 20, 50]
|
|
32
|
+
|
|
33
|
+
const formatVisiblePages = (visiblePages: number[]) =>
|
|
34
|
+
visiblePages.map((page) => (page > 0 ? String(page) : "...")).join(" ")
|
|
35
|
+
|
|
36
|
+
const getRangeLabel = (
|
|
37
|
+
currentPage: number,
|
|
38
|
+
pageSize: number,
|
|
39
|
+
totalItems: number
|
|
40
|
+
) => {
|
|
41
|
+
const start = totalItems === 0 ? 0 : (currentPage - 1) * pageSize + 1
|
|
42
|
+
const end = Math.min(currentPage * pageSize, totalItems)
|
|
43
|
+
return `${start}-${end} of ${totalItems}`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const PaginationPlaygroundDemo: React.FC<PlaygroundDemoProps> = ({
|
|
47
|
+
label = "Dataset",
|
|
48
|
+
...props
|
|
49
|
+
}) => {
|
|
50
|
+
const {
|
|
51
|
+
currentPage,
|
|
52
|
+
totalPages,
|
|
53
|
+
pageSize,
|
|
54
|
+
totalItems,
|
|
55
|
+
isFirstPage,
|
|
56
|
+
isLastPage,
|
|
57
|
+
visiblePages,
|
|
58
|
+
} = useStandalonePagination(props)
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<HookPlaygroundCanvas>
|
|
62
|
+
<div className="mx-auto w-full max-w-3xl space-y-6 p-8">
|
|
63
|
+
<HookStateGrid>
|
|
64
|
+
<HookStateCard label="Current page" value={String(currentPage)} />
|
|
65
|
+
<HookStateCard label="Total pages" value={String(totalPages)} />
|
|
66
|
+
<HookStateCard label="Page size" value={String(pageSize)} />
|
|
67
|
+
<HookStateCard
|
|
68
|
+
label={label}
|
|
69
|
+
value={getRangeLabel(currentPage, pageSize, totalItems)}
|
|
70
|
+
/>
|
|
71
|
+
</HookStateGrid>
|
|
72
|
+
|
|
73
|
+
<HookPanel title="Derived state">
|
|
74
|
+
<div className="grid gap-3 md:grid-cols-2">
|
|
75
|
+
<HookCodeBlock
|
|
76
|
+
code={`visiblePages = [${formatVisiblePages(visiblePages)}]`}
|
|
77
|
+
/>
|
|
78
|
+
<HookCodeBlock
|
|
79
|
+
code={`isFirstPage = ${String(isFirstPage)}
|
|
80
|
+
isLastPage = ${String(isLastPage)}`}
|
|
81
|
+
/>
|
|
82
|
+
</div>
|
|
83
|
+
</HookPanel>
|
|
84
|
+
</div>
|
|
85
|
+
</HookPlaygroundCanvas>
|
|
86
|
+
)
|
|
87
|
+
}
|
|
5
88
|
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
UseStandalonePaginationProps & { title?: string }
|
|
9
|
-
> = ({ title = "Pagination Demo", ...props }) => {
|
|
89
|
+
const PaginationInteractiveDemo: React.FC = () => {
|
|
90
|
+
const [goToPageValue, setGoToPageValue] = useState("1")
|
|
10
91
|
const {
|
|
11
92
|
currentPage,
|
|
12
93
|
totalPages,
|
|
@@ -21,963 +102,654 @@ const PaginationDemo: React.FC<
|
|
|
21
102
|
isFirstPage,
|
|
22
103
|
isLastPage,
|
|
23
104
|
visiblePages,
|
|
24
|
-
} = useStandalonePagination(
|
|
105
|
+
} = useStandalonePagination({
|
|
106
|
+
totalItems: 126,
|
|
107
|
+
initialPage: 4,
|
|
108
|
+
initialPageSize: 10,
|
|
109
|
+
siblingCount: 1,
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
const pageRows = useMemo(
|
|
113
|
+
() =>
|
|
114
|
+
Array.from({ length: pageSize }, (_, index) => {
|
|
115
|
+
const itemNumber = (currentPage - 1) * pageSize + index + 1
|
|
116
|
+
if (itemNumber > totalItems) return null
|
|
117
|
+
return `Item ${itemNumber}`
|
|
118
|
+
}).filter(Boolean) as string[],
|
|
119
|
+
[currentPage, pageSize, totalItems]
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
const commitPageInput = () => {
|
|
123
|
+
const parsedPage = Number.parseInt(goToPageValue, 10)
|
|
124
|
+
if (Number.isNaN(parsedPage)) return
|
|
125
|
+
setPage(parsedPage)
|
|
126
|
+
setGoToPageValue(String(parsedPage))
|
|
127
|
+
}
|
|
25
128
|
|
|
26
129
|
return (
|
|
27
|
-
<
|
|
28
|
-
<div className="
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
<
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
</div>
|
|
50
|
-
</div>
|
|
51
|
-
<div className="rounded-lg border border-orange-500/20 bg-orange-500/10 !p-3">
|
|
52
|
-
<div className="!text-sm !text-orange-300">Total Items</div>
|
|
53
|
-
<div className="!text-2xl font-bold !text-orange-100">
|
|
54
|
-
{totalItems}
|
|
55
|
-
</div>
|
|
56
|
-
</div>
|
|
57
|
-
</div>
|
|
58
|
-
|
|
59
|
-
{/* Page Size Controls */}
|
|
60
|
-
<div className="!space-y-2">
|
|
61
|
-
<label className="!text-sm font-medium !text-white">Page Size</label>
|
|
62
|
-
<div className="flex gap-2">
|
|
63
|
-
{[5, 10, 20, 50].map((size) => (
|
|
64
|
-
<button
|
|
65
|
-
key={size}
|
|
66
|
-
onClick={() => setPageSize(size)}
|
|
67
|
-
className={`rounded-lg !px-3 !py-1 !text-sm transition-colors ${
|
|
68
|
-
pageSize === size
|
|
69
|
-
? "bg-blue-500 !text-white"
|
|
70
|
-
: "border border-white/20 bg-white/5 !text-white/70 hover:bg-white/10"
|
|
71
|
-
}`}
|
|
72
|
-
>
|
|
73
|
-
{size}
|
|
74
|
-
</button>
|
|
75
|
-
))}
|
|
76
|
-
</div>
|
|
77
|
-
</div>
|
|
78
|
-
|
|
79
|
-
{/* Navigation Controls */}
|
|
80
|
-
<div className="!space-y-2">
|
|
81
|
-
<label className="!text-sm font-medium !text-white">
|
|
82
|
-
Navigation Controls
|
|
83
|
-
</label>
|
|
84
|
-
<div className="flex gap-2">
|
|
85
|
-
<button
|
|
130
|
+
<HookPlaygroundCanvas>
|
|
131
|
+
<div className="mx-auto w-full max-w-4xl space-y-6 p-8">
|
|
132
|
+
<HookStateGrid>
|
|
133
|
+
<HookStateCard label="Current page" value={String(currentPage)} />
|
|
134
|
+
<HookStateCard label="Total pages" value={String(totalPages)} />
|
|
135
|
+
<HookStateCard label="Page size" value={String(pageSize)} />
|
|
136
|
+
<HookStateCard
|
|
137
|
+
label="Showing"
|
|
138
|
+
value={getRangeLabel(currentPage, pageSize, totalItems)}
|
|
139
|
+
/>
|
|
140
|
+
<HookStateCard label="First page" value={String(isFirstPage)} />
|
|
141
|
+
<HookStateCard label="Last page" value={String(isLastPage)} />
|
|
142
|
+
<HookStateCard
|
|
143
|
+
label="Visible slots"
|
|
144
|
+
value={String(visiblePages.length)}
|
|
145
|
+
/>
|
|
146
|
+
<HookStateCard label="Sibling count" value="1" />
|
|
147
|
+
</HookStateGrid>
|
|
148
|
+
|
|
149
|
+
<HookPanel title="Navigation controls">
|
|
150
|
+
<div className="flex flex-wrap gap-3">
|
|
151
|
+
<HookControlButton
|
|
86
152
|
onClick={firstPage}
|
|
87
153
|
disabled={isFirstPage}
|
|
88
|
-
className="
|
|
154
|
+
className="flex-1"
|
|
89
155
|
>
|
|
90
156
|
First
|
|
91
|
-
</
|
|
92
|
-
<
|
|
157
|
+
</HookControlButton>
|
|
158
|
+
<HookControlButton
|
|
93
159
|
onClick={prevPage}
|
|
94
160
|
disabled={isFirstPage}
|
|
95
|
-
|
|
161
|
+
variant="negative"
|
|
162
|
+
className="flex-1"
|
|
96
163
|
>
|
|
97
164
|
Previous
|
|
98
|
-
</
|
|
99
|
-
<
|
|
165
|
+
</HookControlButton>
|
|
166
|
+
<HookControlButton
|
|
100
167
|
onClick={nextPage}
|
|
101
168
|
disabled={isLastPage}
|
|
102
|
-
|
|
169
|
+
variant="positive"
|
|
170
|
+
className="flex-1"
|
|
103
171
|
>
|
|
104
172
|
Next
|
|
105
|
-
</
|
|
106
|
-
<
|
|
173
|
+
</HookControlButton>
|
|
174
|
+
<HookControlButton
|
|
107
175
|
onClick={lastPage}
|
|
108
176
|
disabled={isLastPage}
|
|
109
|
-
className="
|
|
177
|
+
className="flex-1"
|
|
110
178
|
>
|
|
111
179
|
Last
|
|
112
|
-
</
|
|
180
|
+
</HookControlButton>
|
|
113
181
|
</div>
|
|
114
|
-
</
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
<button
|
|
124
|
-
key={index}
|
|
125
|
-
onClick={() => page > 0 && setPage(page)}
|
|
126
|
-
disabled={page < 0}
|
|
127
|
-
className={`!h-10 !w-10 rounded-lg !text-sm transition-colors ${
|
|
128
|
-
page === currentPage
|
|
129
|
-
? "bg-blue-500 !text-white"
|
|
130
|
-
: page > 0
|
|
131
|
-
? "border border-white/20 bg-white/5 !text-white/70 hover:bg-white/10"
|
|
132
|
-
: "cursor-default !text-white/40"
|
|
133
|
-
}`}
|
|
182
|
+
</HookPanel>
|
|
183
|
+
|
|
184
|
+
<HookPanel title="Page size">
|
|
185
|
+
<div className="flex flex-wrap gap-3">
|
|
186
|
+
{PAGE_SIZE_OPTIONS.map((size) => (
|
|
187
|
+
<HookControlButton
|
|
188
|
+
key={size}
|
|
189
|
+
onClick={() => setPageSize(size)}
|
|
190
|
+
variant={pageSize === size ? "info" : "default"}
|
|
134
191
|
>
|
|
135
|
-
{
|
|
136
|
-
</
|
|
192
|
+
{size} per page
|
|
193
|
+
</HookControlButton>
|
|
137
194
|
))}
|
|
138
195
|
</div>
|
|
139
|
-
</
|
|
196
|
+
</HookPanel>
|
|
197
|
+
|
|
198
|
+
<HookPanel title="Visible pages">
|
|
199
|
+
<div className="flex flex-wrap gap-3">
|
|
200
|
+
{visiblePages.map((page, index) =>
|
|
201
|
+
page > 0 ? (
|
|
202
|
+
<HookControlButton
|
|
203
|
+
key={`${page}-${index}`}
|
|
204
|
+
onClick={() => {
|
|
205
|
+
setPage(page)
|
|
206
|
+
setGoToPageValue(String(page))
|
|
207
|
+
}}
|
|
208
|
+
variant={page === currentPage ? "info" : "default"}
|
|
209
|
+
>
|
|
210
|
+
{page}
|
|
211
|
+
</HookControlButton>
|
|
212
|
+
) : (
|
|
213
|
+
<div
|
|
214
|
+
key={`ellipsis-${index}`}
|
|
215
|
+
className="border-fm-divider-secondary bg-fm-surface-secondary text-fm-tertiary flex min-w-11 items-center justify-center rounded-lg border px-4 py-2 text-sm"
|
|
216
|
+
>
|
|
217
|
+
...
|
|
218
|
+
</div>
|
|
219
|
+
)
|
|
220
|
+
)}
|
|
221
|
+
</div>
|
|
222
|
+
</HookPanel>
|
|
140
223
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
<label className="!text-sm font-medium !text-white">Go to Page</label>
|
|
144
|
-
<div className="flex gap-2">
|
|
224
|
+
<HookPanel title="Jump to page">
|
|
225
|
+
<div className="flex flex-wrap gap-3">
|
|
145
226
|
<input
|
|
146
227
|
type="number"
|
|
147
228
|
min="1"
|
|
148
229
|
max={totalPages}
|
|
149
|
-
value={
|
|
150
|
-
onChange={(
|
|
151
|
-
|
|
152
|
-
if (!isNaN(page)) setPage(page)
|
|
153
|
-
}}
|
|
154
|
-
className="w-20 rounded-lg border border-white/20 bg-white/5 !px-3 !py-2 !text-sm !text-white placeholder-white/50"
|
|
230
|
+
value={goToPageValue}
|
|
231
|
+
onChange={(event) => setGoToPageValue(event.target.value)}
|
|
232
|
+
className="border-fm-divider-secondary bg-fm-surface-secondary text-fm-primary placeholder:text-fm-tertiary w-28 rounded-lg border px-4 py-2 text-sm"
|
|
155
233
|
/>
|
|
156
|
-
<
|
|
157
|
-
|
|
158
|
-
</
|
|
234
|
+
<HookControlButton onClick={commitPageInput} variant="warning">
|
|
235
|
+
Apply page
|
|
236
|
+
</HookControlButton>
|
|
237
|
+
<p className="text-fm-secondary font-fm-text text-fm-sm self-center">
|
|
238
|
+
Enter any number from 1 to {totalPages}.
|
|
239
|
+
</p>
|
|
159
240
|
</div>
|
|
160
|
-
</
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
</div>
|
|
175
|
-
<div className="!text-white/70">
|
|
176
|
-
<span className="font-medium !text-white">Pages:</span>{" "}
|
|
177
|
-
{currentPage} of {totalPages}
|
|
178
|
-
</div>
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
241
|
+
</HookPanel>
|
|
242
|
+
|
|
243
|
+
<HookPanel title="Current slice">
|
|
244
|
+
<HookCodeBlock code={pageRows.join("\n")} />
|
|
245
|
+
</HookPanel>
|
|
246
|
+
|
|
247
|
+
<HookCodeBlock
|
|
248
|
+
code={`const pagination = useStandalonePagination({
|
|
249
|
+
totalItems: 126,
|
|
250
|
+
initialPage: 4,
|
|
251
|
+
initialPageSize: 10,
|
|
252
|
+
siblingCount: 1,
|
|
253
|
+
})`}
|
|
254
|
+
/>
|
|
181
255
|
</div>
|
|
182
|
-
</
|
|
256
|
+
</HookPlaygroundCanvas>
|
|
183
257
|
)
|
|
184
258
|
}
|
|
185
259
|
|
|
186
|
-
const meta: Meta<typeof
|
|
260
|
+
const meta: Meta<typeof PaginationPlaygroundDemo> = {
|
|
187
261
|
title: "Hooks/useStandalonePagination",
|
|
188
|
-
component:
|
|
262
|
+
component: PaginationPlaygroundDemo,
|
|
189
263
|
parameters: {
|
|
190
264
|
layout: "fullscreen",
|
|
191
265
|
backgrounds: {
|
|
192
266
|
default: "dark",
|
|
193
267
|
values: [
|
|
194
|
-
{ name: "dark", value: "
|
|
195
|
-
{ name: "darker", value: "
|
|
196
|
-
{ name: "light", value: "
|
|
268
|
+
{ name: "dark", value: "var(--color-fm-surface-primary)" },
|
|
269
|
+
{ name: "darker", value: "var(--color-fm-neutral-0)" },
|
|
270
|
+
{ name: "light", value: "var(--color-fm-neutral-1100)" },
|
|
197
271
|
],
|
|
198
272
|
},
|
|
199
273
|
docs: {
|
|
200
274
|
page: () => (
|
|
201
|
-
|
|
202
|
-
{
|
|
203
|
-
|
|
204
|
-
{
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
padding: 0 !important;
|
|
213
|
-
margin: 0 !important;
|
|
214
|
-
background: transparent !important;
|
|
215
|
-
}
|
|
216
|
-
.docs-story {
|
|
217
|
-
background: transparent !important;
|
|
218
|
-
}
|
|
219
|
-
.sbdocs {
|
|
220
|
-
background: transparent !important;
|
|
221
|
-
}
|
|
222
|
-
body {
|
|
223
|
-
background: #0a0a0a !important;
|
|
224
|
-
}
|
|
225
|
-
#storybook-docs {
|
|
226
|
-
background: #0a0a0a !important;
|
|
227
|
-
}
|
|
228
|
-
.sbdocs-preview {
|
|
229
|
-
background: transparent !important;
|
|
230
|
-
border: none !important;
|
|
231
|
-
}
|
|
232
|
-
.sbdocs-h1, .sbdocs-h2, .sbdocs-h3, .sbdocs-h4, .sbdocs-h5, .sbdocs-h6 {
|
|
233
|
-
color: white !important;
|
|
234
|
-
}
|
|
235
|
-
.sbdocs-p, .sbdocs-li {
|
|
236
|
-
color: rgba(255, 255, 255, 0.7) !important;
|
|
237
|
-
}
|
|
238
|
-
.sbdocs-code {
|
|
239
|
-
background: rgba(255, 255, 255, 0.1) !important;
|
|
240
|
-
color: rgba(168, 85, 247, 1) !important;
|
|
241
|
-
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
242
|
-
}
|
|
243
|
-
.sbdocs-pre {
|
|
244
|
-
background: rgba(0, 0, 0, 0.4) !important;
|
|
245
|
-
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
246
|
-
}
|
|
247
|
-
.sbdocs-table {
|
|
248
|
-
background: rgba(255, 255, 255, 0.05) !important;
|
|
249
|
-
border: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
250
|
-
}
|
|
251
|
-
.sbdocs-table th {
|
|
252
|
-
background: rgba(255, 255, 255, 0.05) !important;
|
|
253
|
-
color: white !important;
|
|
254
|
-
border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important;
|
|
255
|
-
}
|
|
256
|
-
.sbdocs-table td {
|
|
257
|
-
color: rgba(255, 255, 255, 0.7) !important;
|
|
258
|
-
border-bottom: 1px solid rgba(255, 255, 255, 0.05) !important;
|
|
259
|
-
}
|
|
260
|
-
`}
|
|
261
|
-
</style>
|
|
262
|
-
|
|
263
|
-
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-indigo-900/20 to-gray-900">
|
|
264
|
-
{/* Header */}
|
|
265
|
-
<div className="relative overflow-hidden border-b border-white/10 bg-black/20 backdrop-blur-xl">
|
|
266
|
-
<div className="absolute inset-0 bg-gradient-to-r from-indigo-500/10 via-transparent to-purple-500/10" />
|
|
267
|
-
<div className="relative !mx-auto max-w-7xl !px-6 !py-16">
|
|
268
|
-
<div className="!space-y-6 text-center">
|
|
269
|
-
<div className="!mx-auto flex !h-24 !w-24 items-center justify-center rounded-2xl border border-indigo-500/30 bg-gradient-to-br from-indigo-500/20 to-purple-500/20">
|
|
270
|
-
<span className="!text-4xl">📄</span>
|
|
271
|
-
</div>
|
|
272
|
-
<h1 className="!text-fm-primary !text-5xl font-bold">
|
|
273
|
-
useStandalonePagination
|
|
274
|
-
</h1>
|
|
275
|
-
<p className="!mx-auto max-w-3xl !text-xl leading-relaxed !text-white/70">
|
|
276
|
-
A comprehensive React hook for managing pagination state
|
|
277
|
-
without external data dependencies. Perfect for client-side
|
|
278
|
-
pagination, API integration, and complex data navigation
|
|
279
|
-
scenarios.
|
|
280
|
-
</p>
|
|
281
|
-
|
|
282
|
-
{/* Stats */}
|
|
283
|
-
<div className="flex items-center justify-center gap-8 !pt-8">
|
|
284
|
-
<div className="text-center">
|
|
285
|
-
<div className="!text-3xl font-bold !text-indigo-300">
|
|
286
|
-
Stateful
|
|
287
|
-
</div>
|
|
288
|
-
<div className="!text-sm !text-white/60">
|
|
289
|
-
Complete state management
|
|
290
|
-
</div>
|
|
291
|
-
</div>
|
|
292
|
-
<div className="!h-8 !w-px bg-white/20" />
|
|
293
|
-
<div className="text-center">
|
|
294
|
-
<div className="!text-3xl font-bold !text-purple-300">
|
|
295
|
-
Flexible
|
|
296
|
-
</div>
|
|
297
|
-
<div className="!text-sm !text-white/60">
|
|
298
|
-
Configurable page sizes
|
|
299
|
-
</div>
|
|
300
|
-
</div>
|
|
301
|
-
<div className="!h-8 !w-px bg-white/20" />
|
|
302
|
-
<div className="text-center">
|
|
303
|
-
<div className="!text-3xl font-bold !text-cyan-300">
|
|
304
|
-
Smart
|
|
305
|
-
</div>
|
|
306
|
-
<div className="!text-sm !text-white/60">
|
|
307
|
-
Intelligent page calculation
|
|
308
|
-
</div>
|
|
309
|
-
</div>
|
|
310
|
-
</div>
|
|
311
|
-
</div>
|
|
312
|
-
</div>
|
|
313
|
-
</div>
|
|
314
|
-
|
|
315
|
-
{/* Content */}
|
|
316
|
-
<div className="!mx-auto max-w-7xl !space-y-16 !px-6 !py-12">
|
|
317
|
-
{/* Quick Usage */}
|
|
318
|
-
<div className="!space-y-8">
|
|
319
|
-
<h2 className="text-center !text-3xl font-bold !text-white">
|
|
320
|
-
Quick Start
|
|
321
|
-
</h2>
|
|
322
|
-
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
|
323
|
-
<div className="!space-y-4">
|
|
324
|
-
<h3 className="!text-xl font-semibold !text-indigo-300">
|
|
325
|
-
Basic Usage
|
|
326
|
-
</h3>
|
|
327
|
-
<div className="rounded-lg bg-black/40 !p-4">
|
|
328
|
-
<pre className="overflow-x-auto !text-sm !text-green-300">
|
|
329
|
-
{`import { useStandalonePagination } from "@hooks/use-standalone-pagination"
|
|
330
|
-
|
|
331
|
-
function MyComponent() {
|
|
275
|
+
<AuralHookDocsPage
|
|
276
|
+
icon={LayoutColumnIcon}
|
|
277
|
+
features={[
|
|
278
|
+
{ title: "Stateful", description: "Complete pagination state" },
|
|
279
|
+
{ title: "Adaptive", description: "Keeps visible items stable" },
|
|
280
|
+
{ title: "Smart", description: "Builds ellipsis ranges" },
|
|
281
|
+
]}
|
|
282
|
+
quickStart={{
|
|
283
|
+
codeExample: `import { useStandalonePagination } from "src/ui/hooks/use-standalone-pagination"
|
|
284
|
+
|
|
285
|
+
function ResultsList({ totalItems }: { totalItems: number }) {
|
|
332
286
|
const {
|
|
333
287
|
currentPage,
|
|
334
288
|
totalPages,
|
|
335
|
-
setPage,
|
|
336
289
|
nextPage,
|
|
337
290
|
prevPage,
|
|
338
291
|
visiblePages,
|
|
339
|
-
isFirstPage,
|
|
340
|
-
isLastPage
|
|
341
292
|
} = useStandalonePagination({
|
|
342
|
-
totalItems
|
|
293
|
+
totalItems,
|
|
343
294
|
initialPageSize: 10,
|
|
344
|
-
siblingCount: 1
|
|
295
|
+
siblingCount: 1,
|
|
345
296
|
})
|
|
346
297
|
|
|
347
298
|
return (
|
|
348
|
-
<
|
|
349
|
-
<
|
|
350
|
-
<
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
onClick={() => page > 0 && setPage(page)}
|
|
355
|
-
disabled={page < 0}
|
|
356
|
-
>
|
|
357
|
-
{page > 0 ? page : "..."}
|
|
358
|
-
</button>
|
|
359
|
-
))}
|
|
360
|
-
</div>
|
|
361
|
-
</div>
|
|
299
|
+
<section>
|
|
300
|
+
<p>{currentPage} / {totalPages}</p>
|
|
301
|
+
<button onClick={prevPage}>Prev</button>
|
|
302
|
+
<button onClick={nextPage}>Next</button>
|
|
303
|
+
<div>{visiblePages.join(", ")}</div>
|
|
304
|
+
</section>
|
|
362
305
|
)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
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
|
-
<th className="!px-6 !py-4 text-left !text-sm font-semibold !text-white">
|
|
516
|
-
Description
|
|
517
|
-
</th>
|
|
518
|
-
</tr>
|
|
519
|
-
</thead>
|
|
520
|
-
<tbody>
|
|
521
|
-
<tr className="border-b border-white/5">
|
|
522
|
-
<td className="!px-6 !py-4 font-mono !text-sm !text-indigo-300">
|
|
523
|
-
currentPage
|
|
524
|
-
</td>
|
|
525
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
526
|
-
number
|
|
527
|
-
</td>
|
|
528
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
529
|
-
Current active page number
|
|
530
|
-
</td>
|
|
531
|
-
</tr>
|
|
532
|
-
<tr className="border-b border-white/5 !bg-black/10">
|
|
533
|
-
<td className="!px-6 !py-4 font-mono !text-sm !text-indigo-300">
|
|
534
|
-
totalPages
|
|
535
|
-
</td>
|
|
536
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
537
|
-
number
|
|
538
|
-
</td>
|
|
539
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
540
|
-
Total number of pages
|
|
541
|
-
</td>
|
|
542
|
-
</tr>
|
|
543
|
-
<tr className="border-b border-white/5">
|
|
544
|
-
<td className="!px-6 !py-4 font-mono !text-sm !text-indigo-300">
|
|
545
|
-
setPage
|
|
546
|
-
</td>
|
|
547
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">{`(page: number) => void`}</td>
|
|
548
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
549
|
-
Navigate to specific page
|
|
550
|
-
</td>
|
|
551
|
-
</tr>
|
|
552
|
-
<tr className="border-b border-white/5 !bg-black/10">
|
|
553
|
-
<td className="!px-6 !py-4 font-mono !text-sm !text-indigo-300">
|
|
554
|
-
nextPage
|
|
555
|
-
</td>
|
|
556
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">{`() => void`}</td>
|
|
557
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
558
|
-
Navigate to next page
|
|
559
|
-
</td>
|
|
560
|
-
</tr>
|
|
561
|
-
<tr className="border-b border-white/5">
|
|
562
|
-
<td className="!px-6 !py-4 font-mono !text-sm !text-indigo-300">
|
|
563
|
-
prevPage
|
|
564
|
-
</td>
|
|
565
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">{`() => void`}</td>
|
|
566
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
567
|
-
Navigate to previous page
|
|
568
|
-
</td>
|
|
569
|
-
</tr>
|
|
570
|
-
<tr className="border-b border-white/5 !bg-black/10">
|
|
571
|
-
<td className="!px-6 !py-4 font-mono !text-sm !text-indigo-300">
|
|
572
|
-
visiblePages
|
|
573
|
-
</td>
|
|
574
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
575
|
-
number[]
|
|
576
|
-
</td>
|
|
577
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
578
|
-
Array of page numbers to display (negative values
|
|
579
|
-
represent dots)
|
|
580
|
-
</td>
|
|
581
|
-
</tr>
|
|
582
|
-
<tr className="border-b border-white/5">
|
|
583
|
-
<td className="!px-6 !py-4 font-mono !text-sm !text-indigo-300">
|
|
584
|
-
isFirstPage
|
|
585
|
-
</td>
|
|
586
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
587
|
-
boolean
|
|
588
|
-
</td>
|
|
589
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
590
|
-
Whether currently on first page
|
|
591
|
-
</td>
|
|
592
|
-
</tr>
|
|
593
|
-
<tr className="!bg-black/10">
|
|
594
|
-
<td className="!px-6 !py-4 font-mono !text-sm !text-indigo-300">
|
|
595
|
-
isLastPage
|
|
596
|
-
</td>
|
|
597
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
598
|
-
boolean
|
|
599
|
-
</td>
|
|
600
|
-
<td className="!px-6 !py-4 !text-sm !text-white/70">
|
|
601
|
-
Whether currently on last page
|
|
602
|
-
</td>
|
|
603
|
-
</tr>
|
|
604
|
-
</tbody>
|
|
605
|
-
</table>
|
|
606
|
-
</div>
|
|
607
|
-
</div>
|
|
608
|
-
|
|
609
|
-
{/* Features */}
|
|
610
|
-
<div className="!space-y-8">
|
|
611
|
-
<h2 className="text-center !text-3xl font-bold !text-white">
|
|
612
|
-
Key Features
|
|
613
|
-
</h2>
|
|
614
|
-
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
|
615
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
616
|
-
<div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-blue-500/20">
|
|
617
|
-
<span className="!text-2xl">🔄</span>
|
|
618
|
-
</div>
|
|
619
|
-
<h3 className="!mb-2 !text-lg font-semibold !text-white">
|
|
620
|
-
State Management
|
|
621
|
-
</h3>
|
|
622
|
-
<p className="!text-sm !text-white/70">
|
|
623
|
-
Complete pagination state with automatic page validation
|
|
624
|
-
and boundary checks.
|
|
625
|
-
</p>
|
|
626
|
-
</div>
|
|
627
|
-
|
|
628
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
629
|
-
<div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-green-500/20">
|
|
630
|
-
<span className="!text-2xl">📊</span>
|
|
631
|
-
</div>
|
|
632
|
-
<h3 className="!mb-2 !text-lg font-semibold !text-white">
|
|
633
|
-
Smart Pagination
|
|
634
|
-
</h3>
|
|
635
|
-
<p className="!text-sm !text-white/70">
|
|
636
|
-
Intelligent page number calculation with customizable
|
|
637
|
-
sibling count and ellipsis.
|
|
638
|
-
</p>
|
|
639
|
-
</div>
|
|
640
|
-
|
|
641
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
642
|
-
<div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-purple-500/20">
|
|
643
|
-
<span className="!text-2xl">⚙️</span>
|
|
644
|
-
</div>
|
|
645
|
-
<h3 className="!mb-2 !text-lg font-semibold !text-white">
|
|
646
|
-
Dynamic Page Size
|
|
647
|
-
</h3>
|
|
648
|
-
<p className="!text-sm !text-white/70">
|
|
649
|
-
Change page size on the fly with automatic page adjustment
|
|
650
|
-
to maintain context.
|
|
651
|
-
</p>
|
|
652
|
-
</div>
|
|
653
|
-
|
|
654
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
655
|
-
<div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-orange-500/20">
|
|
656
|
-
<span className="!text-2xl">🎯</span>
|
|
657
|
-
</div>
|
|
658
|
-
<h3 className="!mb-2 !text-lg font-semibold !text-white">
|
|
659
|
-
Navigation Methods
|
|
660
|
-
</h3>
|
|
661
|
-
<p className="!text-sm !text-white/70">
|
|
662
|
-
Multiple navigation options: next, previous, first, last,
|
|
663
|
-
and direct page selection.
|
|
664
|
-
</p>
|
|
665
|
-
</div>
|
|
666
|
-
|
|
667
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
668
|
-
<div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-cyan-500/20">
|
|
669
|
-
<span className="!text-2xl">🔍</span>
|
|
670
|
-
</div>
|
|
671
|
-
<h3 className="!mb-2 !text-lg font-semibold !text-white">
|
|
672
|
-
State Indicators
|
|
673
|
-
</h3>
|
|
674
|
-
<p className="!text-sm !text-white/70">
|
|
675
|
-
Built-in boolean flags for first/last page states and
|
|
676
|
-
comprehensive pagination info.
|
|
677
|
-
</p>
|
|
678
|
-
</div>
|
|
679
|
-
|
|
680
|
-
<div className="rounded-lg border border-white/10 bg-white/5 !p-6">
|
|
681
|
-
<div className="!mb-4 flex !h-12 !w-12 items-center justify-center rounded-lg bg-pink-500/20">
|
|
682
|
-
<span className="!text-2xl">⚡</span>
|
|
683
|
-
</div>
|
|
684
|
-
<h3 className="!mb-2 !text-lg font-semibold !text-white">
|
|
685
|
-
Performance Optimized
|
|
686
|
-
</h3>
|
|
687
|
-
<p className="!text-sm !text-white/70">
|
|
688
|
-
Memoized calculations and optimized re-renders for smooth
|
|
689
|
-
user experience.
|
|
690
|
-
</p>
|
|
691
|
-
</div>
|
|
692
|
-
</div>
|
|
693
|
-
</div>
|
|
694
|
-
|
|
695
|
-
{/* Usage Patterns */}
|
|
696
|
-
<div className="!space-y-8">
|
|
697
|
-
<h2 className="text-center !text-3xl font-bold !text-white">
|
|
698
|
-
Usage Patterns
|
|
699
|
-
</h2>
|
|
700
|
-
<div className="grid grid-cols-1 gap-8 lg:grid-cols-2">
|
|
701
|
-
<div className="!space-y-4">
|
|
702
|
-
<h3 className="!text-xl font-semibold !text-indigo-300">
|
|
703
|
-
Table Pagination
|
|
704
|
-
</h3>
|
|
705
|
-
<div className="rounded-lg bg-black/40 !p-4">
|
|
706
|
-
<pre className="overflow-x-auto !text-sm !text-green-300">
|
|
707
|
-
{`// Table with pagination
|
|
708
|
-
function DataTable({ data }) {
|
|
709
|
-
const {
|
|
710
|
-
currentPage,
|
|
711
|
-
pageSize,
|
|
712
|
-
setPage,
|
|
713
|
-
setPageSize,
|
|
714
|
-
visiblePages
|
|
715
|
-
} = useStandalonePagination({
|
|
716
|
-
totalItems: data.length,
|
|
717
|
-
initialPageSize: 10
|
|
306
|
+
}`,
|
|
307
|
+
hookProperties: [
|
|
308
|
+
{ label: "Returns", value: "Full pagination controller" },
|
|
309
|
+
{ label: "Range model", value: "Ellipsis aware pages" },
|
|
310
|
+
{ label: "Page sizing", value: "Keeps item position stable" },
|
|
311
|
+
{ label: "Best for", value: "Client side pagination UIs" },
|
|
312
|
+
],
|
|
313
|
+
}}
|
|
314
|
+
hookSignature={`function useStandalonePagination({
|
|
315
|
+
initialPage,
|
|
316
|
+
initialPageSize,
|
|
317
|
+
totalItems,
|
|
318
|
+
siblingCount,
|
|
319
|
+
}: UseStandalonePaginationProps): UseStandalonePaginationReturn`}
|
|
320
|
+
parameters={[
|
|
321
|
+
{
|
|
322
|
+
name: "initialPage",
|
|
323
|
+
type: "number",
|
|
324
|
+
description: "Starting page for the pagination state.",
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
name: "initialPageSize",
|
|
328
|
+
type: "number",
|
|
329
|
+
description: "Initial number of items shown per page.",
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: "totalItems",
|
|
333
|
+
type: "number",
|
|
334
|
+
required: true,
|
|
335
|
+
description: "Total item count used to derive total pages.",
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: "siblingCount",
|
|
339
|
+
type: "number",
|
|
340
|
+
description:
|
|
341
|
+
"How many pages to show on each side of the current page.",
|
|
342
|
+
},
|
|
343
|
+
]}
|
|
344
|
+
returns={[
|
|
345
|
+
{
|
|
346
|
+
name: "currentPage",
|
|
347
|
+
type: "number",
|
|
348
|
+
description: "Active page currently selected.",
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
name: "totalPages",
|
|
352
|
+
type: "number",
|
|
353
|
+
description: "Derived page count from totalItems and pageSize.",
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
name: "pageSize",
|
|
357
|
+
type: "number",
|
|
358
|
+
description: "Current items-per-page value.",
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: "totalItems",
|
|
362
|
+
type: "number",
|
|
363
|
+
description: "Echo of the provided total item count.",
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
name: "setPage",
|
|
367
|
+
type: "(page: number) => void",
|
|
368
|
+
description: "Moves to a clamped page within valid bounds.",
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
name: "nextPage",
|
|
372
|
+
type: "() => void",
|
|
373
|
+
description: "Advances one page unless already at the end.",
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: "prevPage",
|
|
377
|
+
type: "() => void",
|
|
378
|
+
description: "Moves back one page unless already at the start.",
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
name: "firstPage",
|
|
382
|
+
type: "() => void",
|
|
383
|
+
description: "Jumps directly to page one.",
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
name: "lastPage",
|
|
387
|
+
type: "() => void",
|
|
388
|
+
description: "Jumps directly to the final page.",
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
name: "setPageSize",
|
|
392
|
+
type: "(size: number) => void",
|
|
393
|
+
description:
|
|
394
|
+
"Updates page size and recalculates the best matching page.",
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
name: "isFirstPage",
|
|
398
|
+
type: "boolean",
|
|
399
|
+
description: "True when the current page is page one.",
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: "isLastPage",
|
|
403
|
+
type: "boolean",
|
|
404
|
+
description: "True when the current page is the last page.",
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
name: "visiblePages",
|
|
408
|
+
type: "number[]",
|
|
409
|
+
description:
|
|
410
|
+
"Page model used to render pagination controls, including negative sentinel values for ellipsis slots.",
|
|
411
|
+
},
|
|
412
|
+
]}
|
|
413
|
+
useCases={[
|
|
414
|
+
{
|
|
415
|
+
icon: SearchIcon,
|
|
416
|
+
title: "Search Results",
|
|
417
|
+
description:
|
|
418
|
+
"Power a client-side results grid with next, previous, and jump controls.",
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
icon: FileTextIcon,
|
|
422
|
+
title: "Article Lists",
|
|
423
|
+
description:
|
|
424
|
+
"Paginate long editorial archives while keeping visible ranges readable.",
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
icon: NotepadIcon,
|
|
428
|
+
title: "Admin Tables",
|
|
429
|
+
description:
|
|
430
|
+
"Handle back-office lists where users often change page size and jump far ahead.",
|
|
431
|
+
},
|
|
432
|
+
{
|
|
433
|
+
icon: PageTextIcon,
|
|
434
|
+
title: "Document Browsing",
|
|
435
|
+
description:
|
|
436
|
+
"Show page context for readers moving across long sets of content or records.",
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
icon: SettingIcon,
|
|
440
|
+
title: "Configurable Views",
|
|
441
|
+
description:
|
|
442
|
+
"Expose page-size controls when operators need dense or compact list views.",
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
icon: FeatureShineIcon,
|
|
446
|
+
title: "Hybrid APIs",
|
|
447
|
+
description:
|
|
448
|
+
"Reuse the same state machine to drive local slicing or remote fetch parameters.",
|
|
449
|
+
},
|
|
450
|
+
]}
|
|
451
|
+
usagePatterns={[
|
|
452
|
+
{
|
|
453
|
+
title: "Paginating a Local Array",
|
|
454
|
+
code: `function TracksTable({ tracks }: { tracks: Track[] }) {
|
|
455
|
+
const pagination = useStandalonePagination({
|
|
456
|
+
totalItems: tracks.length,
|
|
457
|
+
initialPageSize: 20,
|
|
718
458
|
})
|
|
719
459
|
|
|
720
|
-
const
|
|
721
|
-
const
|
|
722
|
-
|
|
723
|
-
startIndex + pageSize
|
|
724
|
-
)
|
|
460
|
+
const start = (pagination.currentPage - 1) * pagination.pageSize
|
|
461
|
+
const end = start + pagination.pageSize
|
|
462
|
+
const rows = tracks.slice(start, end)
|
|
725
463
|
|
|
726
|
-
return
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
</tr>
|
|
733
|
-
))}
|
|
734
|
-
</table>
|
|
735
|
-
|
|
736
|
-
<PaginationControls
|
|
737
|
-
visiblePages={visiblePages}
|
|
738
|
-
onPageChange={setPage}
|
|
739
|
-
/>
|
|
740
|
-
</div>
|
|
741
|
-
)
|
|
742
|
-
}`}
|
|
743
|
-
</pre>
|
|
744
|
-
</div>
|
|
745
|
-
</div>
|
|
746
|
-
|
|
747
|
-
<div className="!space-y-4">
|
|
748
|
-
<h3 className="!text-xl font-semibold !text-indigo-300">
|
|
749
|
-
API Integration
|
|
750
|
-
</h3>
|
|
751
|
-
<div className="rounded-lg bg-black/40 !p-4">
|
|
752
|
-
<pre className="overflow-x-auto !text-sm !text-green-300">
|
|
753
|
-
{`// API with pagination
|
|
754
|
-
function ApiDataList() {
|
|
755
|
-
const [data, setData] = useState([])
|
|
756
|
-
const [totalItems, setTotalItems] = useState(0)
|
|
757
|
-
|
|
464
|
+
return <Table rows={rows} />
|
|
465
|
+
}`,
|
|
466
|
+
},
|
|
467
|
+
{
|
|
468
|
+
title: "Driving a Remote Query",
|
|
469
|
+
code: `function AuditLog() {
|
|
758
470
|
const pagination = useStandalonePagination({
|
|
759
|
-
totalItems,
|
|
760
|
-
initialPageSize:
|
|
471
|
+
totalItems: 500,
|
|
472
|
+
initialPageSize: 25,
|
|
473
|
+
siblingCount: 2,
|
|
761
474
|
})
|
|
762
475
|
|
|
763
476
|
useEffect(() => {
|
|
764
|
-
|
|
477
|
+
fetchLogs({
|
|
765
478
|
page: pagination.currentPage,
|
|
766
|
-
limit: pagination.pageSize
|
|
767
|
-
}).then(response => {
|
|
768
|
-
setData(response.data)
|
|
769
|
-
setTotalItems(response.total)
|
|
479
|
+
limit: pagination.pageSize,
|
|
770
480
|
})
|
|
771
481
|
}, [pagination.currentPage, pagination.pageSize])
|
|
772
482
|
|
|
773
|
-
return
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
483
|
+
return <LogView pagination={pagination} />
|
|
484
|
+
}`,
|
|
485
|
+
},
|
|
486
|
+
{
|
|
487
|
+
title: "Persisting Page Size",
|
|
488
|
+
code: `function SavedPreferenceList({ totalItems }: Props) {
|
|
489
|
+
const initialPageSize = Number(localStorage.getItem("pageSize") ?? 10)
|
|
490
|
+
const { pageSize, setPageSize, ...pagination } = useStandalonePagination({
|
|
491
|
+
totalItems,
|
|
492
|
+
initialPageSize,
|
|
493
|
+
})
|
|
494
|
+
|
|
495
|
+
useEffect(() => {
|
|
496
|
+
localStorage.setItem("pageSize", String(pageSize))
|
|
497
|
+
}, [pageSize])
|
|
498
|
+
|
|
499
|
+
return <ListControls pageSize={pageSize} setPageSize={setPageSize} />
|
|
500
|
+
}`,
|
|
501
|
+
},
|
|
502
|
+
]}
|
|
503
|
+
implementation={{
|
|
504
|
+
code: `import { useCallback, useEffect, useMemo, useState } from "react"
|
|
505
|
+
|
|
506
|
+
export interface UseStandalonePaginationProps {
|
|
507
|
+
initialPage?: number
|
|
508
|
+
initialPageSize?: number
|
|
509
|
+
totalItems: number
|
|
510
|
+
siblingCount?: number
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export interface UseStandalonePaginationReturn {
|
|
514
|
+
currentPage: number
|
|
515
|
+
totalPages: number
|
|
516
|
+
pageSize: number
|
|
517
|
+
totalItems: number
|
|
518
|
+
setPage: (page: number) => void
|
|
519
|
+
nextPage: () => void
|
|
520
|
+
prevPage: () => void
|
|
521
|
+
firstPage: () => void
|
|
522
|
+
lastPage: () => void
|
|
523
|
+
setPageSize: (size: number) => void
|
|
524
|
+
isFirstPage: boolean
|
|
525
|
+
isLastPage: boolean
|
|
526
|
+
visiblePages: number[]
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export const useStandalonePagination = ({
|
|
530
|
+
initialPage = 1,
|
|
531
|
+
initialPageSize = 10,
|
|
532
|
+
totalItems,
|
|
533
|
+
siblingCount = 1,
|
|
534
|
+
}: UseStandalonePaginationProps): UseStandalonePaginationReturn => {
|
|
535
|
+
const [currentPage, setCurrentPage] = useState<number>(initialPage)
|
|
536
|
+
const [pageSize, setPageSize] = useState<number>(initialPageSize)
|
|
537
|
+
|
|
538
|
+
const totalPages = useMemo(
|
|
539
|
+
() => Math.max(1, Math.ceil(totalItems / pageSize)),
|
|
540
|
+
[totalItems, pageSize]
|
|
778
541
|
)
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
542
|
+
|
|
543
|
+
useEffect(() => {
|
|
544
|
+
if (currentPage > totalPages) {
|
|
545
|
+
setCurrentPage(totalPages)
|
|
546
|
+
}
|
|
547
|
+
}, [totalPages, currentPage])
|
|
548
|
+
|
|
549
|
+
const isFirstPage = currentPage === 1
|
|
550
|
+
const isLastPage = currentPage === totalPages
|
|
551
|
+
|
|
552
|
+
const setPage = useCallback(
|
|
553
|
+
(page: number) => {
|
|
554
|
+
const validPage = Math.min(Math.max(1, page), totalPages)
|
|
555
|
+
setCurrentPage(validPage)
|
|
556
|
+
},
|
|
557
|
+
[totalPages]
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
const nextPage = useCallback(() => {
|
|
561
|
+
if (!isLastPage) {
|
|
562
|
+
setCurrentPage((prev) => prev + 1)
|
|
563
|
+
}
|
|
564
|
+
}, [isLastPage])
|
|
565
|
+
|
|
566
|
+
const prevPage = useCallback(() => {
|
|
567
|
+
if (!isFirstPage) {
|
|
568
|
+
setCurrentPage((prev) => prev - 1)
|
|
569
|
+
}
|
|
570
|
+
}, [isFirstPage])
|
|
571
|
+
|
|
572
|
+
const firstPage = useCallback(() => {
|
|
573
|
+
setCurrentPage(1)
|
|
574
|
+
}, [])
|
|
575
|
+
|
|
576
|
+
const lastPage = useCallback(() => {
|
|
577
|
+
setCurrentPage(totalPages)
|
|
578
|
+
}, [totalPages])
|
|
579
|
+
|
|
580
|
+
const updatePageSize = useCallback(
|
|
581
|
+
(size: number) => {
|
|
582
|
+
setPageSize(size)
|
|
583
|
+
const firstItemIndex = (currentPage - 1) * pageSize
|
|
584
|
+
const newPage = Math.floor(firstItemIndex / size) + 1
|
|
585
|
+
setCurrentPage(Math.min(newPage, Math.ceil(totalItems / size)))
|
|
586
|
+
},
|
|
587
|
+
[currentPage, pageSize, totalItems]
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
const visiblePages = useMemo(() => {
|
|
591
|
+
const range = (start: number, end: number): number[] => {
|
|
592
|
+
return Array.from({ length: end - start + 1 }, (_, i) => start + i)
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const totalPageNumbers = siblingCount * 2 + 3
|
|
596
|
+
|
|
597
|
+
if (totalPages <= totalPageNumbers) {
|
|
598
|
+
return range(1, totalPages)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const leftSiblingIndex = Math.max(currentPage - siblingCount, 1)
|
|
602
|
+
const rightSiblingIndex = Math.min(currentPage + siblingCount, totalPages)
|
|
603
|
+
|
|
604
|
+
const shouldShowLeftDots = leftSiblingIndex > 2
|
|
605
|
+
const shouldShowRightDots = rightSiblingIndex < totalPages - 1
|
|
606
|
+
|
|
607
|
+
if (!shouldShowLeftDots && shouldShowRightDots) {
|
|
608
|
+
const leftItemCount = 3 + 2 * siblingCount
|
|
609
|
+
return [...range(1, leftItemCount), -1, totalPages]
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if (shouldShowLeftDots && !shouldShowRightDots) {
|
|
613
|
+
const rightItemCount = 3 + 2 * siblingCount
|
|
614
|
+
return [1, -1, ...range(totalPages - rightItemCount + 1, totalPages)]
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
return [
|
|
618
|
+
1,
|
|
619
|
+
-1,
|
|
620
|
+
...range(leftSiblingIndex, rightSiblingIndex),
|
|
621
|
+
-2,
|
|
622
|
+
totalPages,
|
|
623
|
+
]
|
|
624
|
+
}, [currentPage, totalPages, siblingCount])
|
|
625
|
+
|
|
626
|
+
return {
|
|
627
|
+
currentPage,
|
|
628
|
+
totalPages,
|
|
629
|
+
pageSize,
|
|
630
|
+
totalItems,
|
|
631
|
+
setPage,
|
|
632
|
+
nextPage,
|
|
633
|
+
prevPage,
|
|
634
|
+
firstPage,
|
|
635
|
+
lastPage,
|
|
636
|
+
setPageSize: updatePageSize,
|
|
637
|
+
isFirstPage,
|
|
638
|
+
isLastPage,
|
|
639
|
+
visiblePages,
|
|
640
|
+
}
|
|
641
|
+
}`,
|
|
642
|
+
notes: [
|
|
643
|
+
"The hook centralizes all pagination transitions so consuming components can stay focused on rendering.",
|
|
644
|
+
"Changing page size preserves the currently visible item range as closely as possible instead of blindly resetting to page one.",
|
|
645
|
+
"The visiblePages array uses sentinel values for ellipsis slots, which keeps pagination UIs compact without hiding the first or last page.",
|
|
646
|
+
],
|
|
647
|
+
}}
|
|
648
|
+
/>
|
|
806
649
|
),
|
|
807
650
|
},
|
|
808
651
|
},
|
|
809
652
|
tags: ["autodocs"],
|
|
810
653
|
argTypes: {
|
|
654
|
+
label: {
|
|
655
|
+
control: "text",
|
|
656
|
+
description: "Label shown for the currently visible item range.",
|
|
657
|
+
},
|
|
811
658
|
totalItems: {
|
|
812
|
-
control: { type: "number"
|
|
813
|
-
description: "Total number of items
|
|
659
|
+
control: { type: "number" },
|
|
660
|
+
description: "Total number of items in the dataset.",
|
|
814
661
|
},
|
|
815
662
|
initialPage: {
|
|
816
|
-
control: { type: "number"
|
|
817
|
-
description: "
|
|
663
|
+
control: { type: "number" },
|
|
664
|
+
description: "Initial current page.",
|
|
818
665
|
},
|
|
819
666
|
initialPageSize: {
|
|
820
|
-
control: { type: "
|
|
821
|
-
description: "
|
|
667
|
+
control: { type: "number" },
|
|
668
|
+
description: "Initial number of items per page.",
|
|
822
669
|
},
|
|
823
670
|
siblingCount: {
|
|
824
|
-
control: { type: "number"
|
|
825
|
-
description: "
|
|
826
|
-
},
|
|
827
|
-
title: {
|
|
828
|
-
control: "text",
|
|
829
|
-
description: "Title for the demo component",
|
|
671
|
+
control: { type: "number" },
|
|
672
|
+
description: "How many neighboring page numbers stay visible.",
|
|
830
673
|
},
|
|
831
674
|
},
|
|
832
675
|
}
|
|
833
676
|
|
|
834
677
|
export default meta
|
|
835
|
-
type Story = StoryObj<typeof
|
|
836
|
-
|
|
837
|
-
// Story parameters for consistent dark theme
|
|
838
|
-
const storyParameters = {
|
|
839
|
-
backgrounds: {
|
|
840
|
-
default: "dark",
|
|
841
|
-
values: [
|
|
842
|
-
{ name: "dark", value: "#0a0a0a" },
|
|
843
|
-
{ name: "darker", value: "#000000" },
|
|
844
|
-
],
|
|
845
|
-
},
|
|
846
|
-
}
|
|
678
|
+
type Story = StoryObj<typeof meta>
|
|
847
679
|
|
|
848
|
-
export const
|
|
680
|
+
export const Playground: Story = {
|
|
849
681
|
args: {
|
|
850
|
-
|
|
851
|
-
|
|
682
|
+
label: "Dataset",
|
|
683
|
+
totalItems: 126,
|
|
684
|
+
initialPage: 4,
|
|
852
685
|
initialPageSize: 10,
|
|
853
686
|
siblingCount: 1,
|
|
854
|
-
title: "Basic Pagination",
|
|
855
687
|
},
|
|
856
|
-
|
|
857
|
-
render: (args) => (
|
|
858
|
-
<div className="min-h-dvh bg-gradient-to-br from-gray-900 to-gray-800 !p-8">
|
|
859
|
-
<PaginationDemo {...args} />
|
|
860
|
-
</div>
|
|
861
|
-
),
|
|
688
|
+
render: (args) => <PaginationPlaygroundDemo {...args} />,
|
|
862
689
|
}
|
|
863
690
|
|
|
864
|
-
export const
|
|
865
|
-
|
|
866
|
-
totalItems: 10000,
|
|
867
|
-
initialPage: 50,
|
|
868
|
-
initialPageSize: 20,
|
|
869
|
-
siblingCount: 2,
|
|
870
|
-
title: "Large Dataset Pagination",
|
|
871
|
-
},
|
|
872
|
-
parameters: {
|
|
873
|
-
...storyParameters,
|
|
874
|
-
docs: {
|
|
875
|
-
description: {
|
|
876
|
-
story:
|
|
877
|
-
"Pagination with a large dataset showing intelligent page number calculation.",
|
|
878
|
-
},
|
|
879
|
-
},
|
|
880
|
-
},
|
|
881
|
-
render: (args) => (
|
|
882
|
-
<div className="min-h-dvh bg-gradient-to-br from-gray-900 to-gray-800 !p-8">
|
|
883
|
-
<PaginationDemo {...args} />
|
|
884
|
-
</div>
|
|
885
|
-
),
|
|
691
|
+
export const Interactive: Story = {
|
|
692
|
+
render: () => <PaginationInteractiveDemo />,
|
|
886
693
|
}
|
|
887
694
|
|
|
888
|
-
export const
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
docs: {
|
|
899
|
-
description: {
|
|
900
|
-
story: "Pagination with a small dataset where all pages are visible.",
|
|
901
|
-
},
|
|
902
|
-
},
|
|
903
|
-
},
|
|
904
|
-
render: (args) => (
|
|
905
|
-
<div className="min-h-dvh bg-gradient-to-br from-gray-900 to-gray-800 !p-8">
|
|
906
|
-
<PaginationDemo {...args} />
|
|
907
|
-
</div>
|
|
908
|
-
),
|
|
909
|
-
}
|
|
695
|
+
export const UseCases: Story = {
|
|
696
|
+
render: () => (
|
|
697
|
+
<HookUsageCanvas>
|
|
698
|
+
<HookUsageSection title="Client-side search results">
|
|
699
|
+
<HookCodeBlock
|
|
700
|
+
code={`function SearchResults({ results }: { results: Result[] }) {
|
|
701
|
+
const pagination = useStandalonePagination({
|
|
702
|
+
totalItems: results.length,
|
|
703
|
+
initialPageSize: 20,
|
|
704
|
+
})
|
|
910
705
|
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
totalItems: 1000,
|
|
914
|
-
initialPage: 25,
|
|
915
|
-
initialPageSize: 10,
|
|
916
|
-
siblingCount: 3,
|
|
917
|
-
title: "Custom Sibling Count",
|
|
918
|
-
},
|
|
919
|
-
parameters: {
|
|
920
|
-
...storyParameters,
|
|
921
|
-
docs: {
|
|
922
|
-
description: {
|
|
923
|
-
story:
|
|
924
|
-
"Pagination with increased sibling count showing more page numbers around current page.",
|
|
925
|
-
},
|
|
926
|
-
},
|
|
927
|
-
},
|
|
928
|
-
render: (args) => (
|
|
929
|
-
<div className="min-h-dvh bg-gradient-to-br from-gray-900 to-gray-800 !p-8">
|
|
930
|
-
<PaginationDemo {...args} />
|
|
931
|
-
</div>
|
|
932
|
-
),
|
|
933
|
-
}
|
|
706
|
+
const start = (pagination.currentPage - 1) * pagination.pageSize
|
|
707
|
+
const pageResults = results.slice(start, start + pagination.pageSize)
|
|
934
708
|
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
initialPageSize: 10,
|
|
940
|
-
siblingCount: 1,
|
|
941
|
-
title: "Interactive Pagination Demo",
|
|
942
|
-
},
|
|
943
|
-
parameters: {
|
|
944
|
-
...storyParameters,
|
|
945
|
-
docs: {
|
|
946
|
-
description: {
|
|
947
|
-
story:
|
|
948
|
-
"Full interactive demo showing all features of the pagination hook with real-time state updates.",
|
|
949
|
-
},
|
|
950
|
-
},
|
|
951
|
-
},
|
|
952
|
-
render: (args) => (
|
|
953
|
-
<div className="min-h-dvh bg-gradient-to-br from-gray-900 to-gray-800 !p-8">
|
|
954
|
-
<div className="!space-y-8">
|
|
955
|
-
<PaginationDemo {...args} />
|
|
956
|
-
</div>
|
|
957
|
-
</div>
|
|
958
|
-
),
|
|
959
|
-
}
|
|
709
|
+
return <ResultsGrid results={pageResults} pagination={pagination} />
|
|
710
|
+
}`}
|
|
711
|
+
/>
|
|
712
|
+
</HookUsageSection>
|
|
960
713
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
"Interactive playground to experiment with different pagination configurations.",
|
|
968
|
-
},
|
|
969
|
-
},
|
|
970
|
-
},
|
|
971
|
-
args: {
|
|
972
|
-
totalItems: 300,
|
|
973
|
-
initialPage: 5,
|
|
974
|
-
initialPageSize: 15,
|
|
714
|
+
<HookUsageSection title="Server-driven admin table">
|
|
715
|
+
<HookCodeBlock
|
|
716
|
+
code={`function OrdersTable() {
|
|
717
|
+
const pagination = useStandalonePagination({
|
|
718
|
+
totalItems: 240,
|
|
719
|
+
initialPageSize: 25,
|
|
975
720
|
siblingCount: 2,
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
721
|
+
})
|
|
722
|
+
|
|
723
|
+
useEffect(() => {
|
|
724
|
+
loadOrders({
|
|
725
|
+
page: pagination.currentPage,
|
|
726
|
+
pageSize: pagination.pageSize,
|
|
727
|
+
})
|
|
728
|
+
}, [pagination.currentPage, pagination.pageSize])
|
|
729
|
+
|
|
730
|
+
return <Table pagination={pagination} />
|
|
731
|
+
}`}
|
|
732
|
+
/>
|
|
733
|
+
</HookUsageSection>
|
|
734
|
+
|
|
735
|
+
<HookUsageSection title="Reader-friendly archive navigation">
|
|
736
|
+
<HookCodeBlock
|
|
737
|
+
code={`function Archive({ totalItems }: { totalItems: number }) {
|
|
738
|
+
const pagination = useStandalonePagination({
|
|
739
|
+
totalItems,
|
|
740
|
+
initialPageSize: 12,
|
|
741
|
+
})
|
|
742
|
+
|
|
743
|
+
return (
|
|
744
|
+
<footer>
|
|
745
|
+
<button onClick={pagination.prevPage}>Prev</button>
|
|
746
|
+
{pagination.visiblePages.map(renderPageButton)}
|
|
747
|
+
<button onClick={pagination.nextPage}>Next</button>
|
|
748
|
+
</footer>
|
|
749
|
+
)
|
|
750
|
+
}`}
|
|
751
|
+
/>
|
|
752
|
+
</HookUsageSection>
|
|
753
|
+
</HookUsageCanvas>
|
|
982
754
|
),
|
|
983
755
|
}
|