@lateralus-ai/shipping-ui 1.4.12 → 1.4.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +108 -108
  2. package/dist/index.cjs +38 -38
  3. package/dist/index.esm.js +4936 -4913
  4. package/dist/tailwind-theme.d.ts +12 -0
  5. package/package.json +99 -99
  6. package/src/components/DocumentEditor/DocumentEditor.tsx +871 -871
  7. package/src/components/HelloWorld.tsx +3 -3
  8. package/src/components/InputPrompt.tsx +96 -96
  9. package/src/components/ModalPanel.tsx +49 -49
  10. package/src/components/PdfViewer/ImageViewer.tsx +211 -211
  11. package/src/components/PdfViewer/PdfViewer.tsx +232 -209
  12. package/src/components/PdfViewer/index.ts +2 -2
  13. package/src/components/PdfViewer/usePageManagement.ts +32 -32
  14. package/src/components/PdfViewer/usePanning.ts +42 -42
  15. package/src/components/PdfViewer/useRefDimensions.ts +16 -16
  16. package/src/components/PdfViewer/useRotation.ts +13 -13
  17. package/src/components/PdfViewer/useZoom.ts +26 -26
  18. package/src/components/SearchModal.tsx +320 -320
  19. package/src/components/Sidebar/Button.tsx +20 -20
  20. package/src/components/Sidebar/Container.tsx +31 -31
  21. package/src/components/Sidebar/Item.tsx +39 -39
  22. package/src/components/Sidebar/Layout.tsx +32 -32
  23. package/src/components/Sidebar/Provider.tsx +47 -47
  24. package/src/components/Sidebar/SecondaryItem.tsx +39 -39
  25. package/src/components/Sidebar/SideContainer.tsx +31 -31
  26. package/src/components/Sidebar/ToggleCollapseButton.tsx +24 -24
  27. package/src/components/Sidebar/index.ts +8 -8
  28. package/src/components/Tabs.tsx +43 -43
  29. package/src/components/icons/CloseSidebarIcon.tsx +19 -19
  30. package/src/components/icons/CloseSidebarMidIcon.tsx +19 -19
  31. package/src/components/icons/ExpandIcon.tsx +21 -21
  32. package/src/components/icons/SendArrowIcon.tsx +23 -23
  33. package/src/components/icons/SendArrowIconGreen.tsx +17 -17
  34. package/src/components/icons/SettingsIcon.tsx +33 -33
  35. package/src/components/icons/XIcon.tsx +21 -21
  36. package/src/components/index.ts +6 -6
  37. package/src/index.ts +4 -4
  38. package/src/material-theme.ts +477 -477
  39. package/src/stories/Buttons.stories.tsx +15 -15
  40. package/src/stories/Buttons.tsx +52 -52
  41. package/src/stories/Checkbox.stories.tsx +15 -15
  42. package/src/stories/Checkbox.tsx +56 -56
  43. package/src/stories/ColorPalette.stories.tsx +15 -15
  44. package/src/stories/ColorPalette.tsx +85 -72
  45. package/src/stories/DocumentEditor.stories.tsx +287 -287
  46. package/src/stories/Dropdowns.stories.tsx +15 -15
  47. package/src/stories/Dropdowns.tsx +52 -52
  48. package/src/stories/InputPrompt.stories.tsx +15 -15
  49. package/src/stories/InputPrompt.tsx +63 -63
  50. package/src/stories/ModalHeader.stories.tsx +35 -35
  51. package/src/stories/PDFViewer.stories.tsx +39 -39
  52. package/src/stories/SearchModal.stories.tsx +132 -132
  53. package/src/stories/SearchModal.tsx +82 -82
  54. package/src/stories/Sidebar.stories.tsx +15 -15
  55. package/src/stories/Sidebar.tsx +108 -108
  56. package/src/stories/Tabs.stories.tsx +51 -51
  57. package/src/stories/Typography.stories.tsx +15 -15
  58. package/src/stories/Typography.tsx +110 -110
  59. package/src/style.css +2 -2
  60. package/src/styles/tabs.css +55 -55
  61. package/src/tailwind-theme.ts +243 -231
  62. package/src/types/documentEditor.ts +55 -55
  63. package/src/utils/checkboxModule.ts +241 -241
  64. package/src/utils/cn.ts +11 -11
@@ -1,209 +1,232 @@
1
- import { IconButton, ButtonGroup, Tooltip } from "@material-tailwind/react"
2
- import { Document, Page, pdfjs } from "react-pdf"
3
- import { ModalPanel } from "../ModalPanel"
4
- import ExpandIcon from "../icons/ExpandIcon"
5
- import { Icon } from "@iconify/react"
6
- import { cn } from "../../utils/cn"
7
- import "react-pdf/dist/Page/AnnotationLayer.css"
8
- import "react-pdf/dist/Page/TextLayer.css"
9
- import {
10
- useState,
11
- ChangeEvent,
12
- useMemo,
13
- MouseEvent as ReactMouseEvent,
14
- } from "react"
15
- import { useZoom } from "./useZoom"
16
- import { useRotation } from "./useRotation"
17
- import { usePageManagement } from "./usePageManagement"
18
- import { usePanning } from "./usePanning"
19
-
20
- pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`
21
-
22
- interface PdfViewerProps extends React.HTMLProps<HTMLDivElement> {
23
- onClose: () => void
24
- src: string
25
- title?: string
26
- onOpen?: (event: ReactMouseEvent<HTMLButtonElement>) => void
27
- }
28
-
29
- export const PdfViewer = ({
30
- onClose,
31
- src,
32
- title = "PDF Viewer",
33
- className,
34
- onOpen,
35
- }: PdfViewerProps) => {
36
- const [zoom, zoomActions] = useZoom()
37
- const [rotation, rotationActions] = useRotation()
38
- const [{ currentPage, totalPages }, pageActions] = usePageManagement()
39
- const [{ pan, isDragging }, panActions] = usePanning()
40
-
41
- const handleOpen = (event: ReactMouseEvent<HTMLButtonElement>) => {
42
- if (onOpen) {
43
- console.debug("[PdfViewer] onOpen callback triggered", {
44
- hasHandler: true,
45
- })
46
- onOpen(event)
47
- } else {
48
- console.debug("[PdfViewer] open button clicked", {
49
- hasHandler: false,
50
- })
51
- }
52
- if (event.defaultPrevented) {
53
- return
54
- }
55
-
56
- if (!src) {
57
- event.preventDefault()
58
- return
59
- }
60
-
61
- window.open(src, "_blank", "noopener,noreferrer")
62
- }
63
-
64
- const rightButtons = (
65
- <IconButton variant="text" color="gray" onClick={handleOpen}>
66
- <ExpandIcon className="size-4" />
67
- </IconButton>
68
- )
69
-
70
- return (
71
- <div className={cn("shadow rounded-t-lg flex flex-col h-full", className)}>
72
- <ModalPanel.Header onClose={onClose} right={rightButtons}>
73
- {title}
74
- </ModalPanel.Header>
75
-
76
- <div className="grid grow h-full overflow-hidden shadow">
77
- <div
78
- className={cn(
79
- "col-start-1 row-start-1 bg-gray-200 h-full overflow-hidden select-none p-8 flex justify-center items-center mt-8",
80
- isDragging ? "cursor-grabbing" : "cursor-grab"
81
- )}
82
- onMouseDown={panActions.handleMouseDown}
83
- onMouseMove={panActions.handleMouseMove}
84
- onMouseUp={panActions.handleMouseUp}
85
- onMouseLeave={panActions.handleMouseUp}
86
- style={{
87
- userSelect: "none",
88
- }}
89
- >
90
- <div
91
- style={{
92
- transform: `translate(${pan.x}px, ${pan.y}px)`,
93
- transition: isDragging ? "none" : "transform 0.1s",
94
- pointerEvents: "none",
95
- maxWidth: "650px",
96
- }}
97
- >
98
- <Document
99
- externalLinkRel="noopener noreferrer"
100
- externalLinkTarget="_blank"
101
- file={src}
102
- onLoadSuccess={({ numPages }) => {
103
- pageActions.setTotalPages(numPages)
104
- }}
105
- scale={zoom / 100}
106
- rotate={rotation}
107
- >
108
- <Page
109
- pageNumber={currentPage}
110
- renderTextLayer={true}
111
- renderAnnotationLayer={true}
112
- />
113
- </Document>
114
- </div>
115
- </div>
116
-
117
- <div className="col-start-1 row-start-1 self-start flex justify-between w-full z-10 flex-wrap items-center gap-3 bg-gray-50 p-2 shadow">
118
- <ButtonGroup className="divide-x-1 gap-1">
119
- <IconButton
120
- variant="filled"
121
- color="white"
122
- className="w-8 h-8 rounded border border-gray-300 bg-white p-2 text-gray-700 hover:bg-gray-200 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 focus:outline-none max-w-auto"
123
- onClick={pageActions.prevPage}
124
- disabled={currentPage === 1}
125
- >
126
- <Icon icon="lucide:chevron-left" />
127
- </IconButton>
128
- <input
129
- className="w-14 h-8 rounded border !border-gray-300 bg-white px-1 py-1 text-center text-sm text-gray-700 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
130
- value={currentPage}
131
- onChange={(e: ChangeEvent<HTMLInputElement>) => {
132
- const page = parseInt(e.target.value)
133
- if (!isNaN(page)) {
134
- pageActions.goToPage(page)
135
- }
136
- }}
137
- type="number"
138
- min="1"
139
- max={totalPages}
140
- />
141
- <div className="flex items-center bg-transparent px-2">
142
- of {totalPages}
143
- </div>
144
- <IconButton
145
- variant="filled"
146
- color="white"
147
- className="w-8 h-8 rounded border !border-gray-300 bg-white p-2 text-gray-700 hover:bg-gray-200 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 focus:outline-none max-w-auto"
148
- onClick={pageActions.nextPage}
149
- disabled={currentPage === totalPages}
150
- >
151
- <Icon icon="lucide:chevron-right" />
152
- </IconButton>
153
- </ButtonGroup>
154
- <div className="flex gap-2 items-center">
155
- <ButtonGroup className="divide-x-1 gap-1">
156
- <IconButton
157
- variant="filled"
158
- color="white"
159
- className="w-8 h-8 rounded border !border-gray-300 bg-white p-2 text-gray-700 hover:bg-gray-200 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 focus:outline-none max-w-auto"
160
- onClick={zoomActions.zoomOut}
161
- >
162
- <Icon icon="lucide:minus" />
163
- </IconButton>
164
- <Tooltip content="Click to reset zoom and pan">
165
- <button
166
- className="w-14 h-8 rounded border !border-gray-300 bg-white px-1 py-1 text-center text-sm text-gray-700 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
167
- onClick={() => {
168
- zoomActions.reset()
169
- panActions.reset()
170
- }}
171
- >
172
- {zoom}%
173
- </button>
174
- </Tooltip>
175
- <IconButton
176
- variant="filled"
177
- color="white"
178
- className="w-8 h-8 rounded border !border-gray-300 bg-white p-2 text-gray-700 hover:bg-gray-200 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 focus:outline-none max-w-auto"
179
- onClick={zoomActions.zoomIn}
180
- >
181
- <Icon icon="lucide:plus" />
182
- </IconButton>
183
- </ButtonGroup>
184
- <div className="h-6 w-px bg-gray-300 dark:bg-gray-600"></div>
185
- <ButtonGroup className="divide-x-1 gap-1">
186
- {/* <IconButton
187
- variant="filled"
188
- color="white"
189
- className="w-8 h-8 rounded border !border-gray-300 bg-white p-2 text-gray-700 hover:bg-gray-200 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 focus:outline-none max-w-auto"
190
- onClick={rotationActions.rotateClockwise}
191
- >
192
- <Icon icon="lucide:iteration-cw" />
193
- </IconButton> */}
194
-
195
- <IconButton
196
- variant="filled"
197
- color="white"
198
- className="w-8 h-8 rounded border !border-gray-300 bg-white p-2 text-gray-700 hover:bg-gray-200 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 focus:outline-none max-w-auto"
199
- onClick={rotationActions.rotateCounterClockwise}
200
- >
201
- <Icon icon="lucide:iteration-ccw" />
202
- </IconButton>
203
- </ButtonGroup>
204
- </div>
205
- </div>
206
- </div>
207
- </div>
208
- )
209
- }
1
+ import { IconButton, ButtonGroup, Tooltip } from "@material-tailwind/react"
2
+ import { Document, Page, pdfjs } from "react-pdf"
3
+ import { ModalPanel } from "../ModalPanel"
4
+ import ExpandIcon from "../icons/ExpandIcon"
5
+ import { Icon } from "@iconify/react"
6
+ import { cn } from "../../utils/cn"
7
+ import "react-pdf/dist/Page/AnnotationLayer.css"
8
+ import "react-pdf/dist/Page/TextLayer.css"
9
+ import {
10
+ useState,
11
+ ChangeEvent,
12
+ useMemo,
13
+ MouseEvent as ReactMouseEvent,
14
+ useRef,
15
+ useEffect,
16
+ } from "react"
17
+ import { useZoom } from "./useZoom"
18
+ import { useRotation } from "./useRotation"
19
+ import { usePageManagement } from "./usePageManagement"
20
+ import { usePanning } from "./usePanning"
21
+
22
+ pdfjs.GlobalWorkerOptions.workerSrc = `//unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`
23
+
24
+ interface PdfViewerProps extends React.HTMLProps<HTMLDivElement> {
25
+ onClose: () => void
26
+ src: string
27
+ title?: string
28
+ onOpen?: (event: ReactMouseEvent<HTMLButtonElement>) => void
29
+ }
30
+
31
+ export const PdfViewer = ({
32
+ onClose,
33
+ src,
34
+ title = "PDF Viewer",
35
+ className,
36
+ onOpen,
37
+ }: PdfViewerProps) => {
38
+ const [zoom, zoomActions] = useZoom()
39
+ const [rotation, rotationActions] = useRotation()
40
+ const [{ currentPage, totalPages }, pageActions] = usePageManagement()
41
+ const [{ pan, isDragging }, panActions] = usePanning()
42
+ const [scale, setScale] = useState(1)
43
+ const containerRef = useRef<HTMLDivElement>(null)
44
+
45
+ useEffect(() => {
46
+ const calculateScale = () => {
47
+ if (containerRef.current) {
48
+ const containerWidth = containerRef.current.offsetWidth
49
+
50
+ // Assuming standard PDF page width is ~612 points (8.5 inches)
51
+ const baseScale = containerWidth / 612
52
+ setScale(baseScale * (zoom / 100))
53
+ }
54
+ }
55
+
56
+ calculateScale()
57
+ window.addEventListener("resize", calculateScale)
58
+ return () => window.removeEventListener("resize", calculateScale)
59
+ }, [zoom])
60
+
61
+ const handleOpen = (event: ReactMouseEvent<HTMLButtonElement>) => {
62
+ if (onOpen) {
63
+ console.debug("[PdfViewer] onOpen callback triggered", {
64
+ hasHandler: true,
65
+ })
66
+ onOpen(event)
67
+ } else {
68
+ console.debug("[PdfViewer] open button clicked", {
69
+ hasHandler: false,
70
+ })
71
+ }
72
+ if (event.defaultPrevented) {
73
+ return
74
+ }
75
+
76
+ if (!src) {
77
+ event.preventDefault()
78
+ return
79
+ }
80
+
81
+ window.open(src, "_blank", "noopener,noreferrer")
82
+ }
83
+
84
+ const rightButtons = (
85
+ <IconButton variant="text" color="gray" onClick={handleOpen}>
86
+ <ExpandIcon className="size-4" />
87
+ </IconButton>
88
+ )
89
+
90
+ return (
91
+ <div
92
+ className={cn("shadow rounded-t-lg flex flex-col h-full", className)}
93
+ ref={containerRef}
94
+ >
95
+ <ModalPanel.Header onClose={onClose} right={rightButtons}>
96
+ {title}
97
+ </ModalPanel.Header>
98
+
99
+ <div className="grid grow h-full overflow-hidden shadow">
100
+ <div
101
+ className={cn(
102
+ "col-start-1 row-start-1 bg-gray-200 h-full overflow-hidden select-none p-8 flex justify-center items-center mt-8",
103
+ isDragging ? "cursor-grabbing" : "cursor-grab"
104
+ )}
105
+ onMouseDown={panActions.handleMouseDown}
106
+ onMouseMove={panActions.handleMouseMove}
107
+ onMouseUp={panActions.handleMouseUp}
108
+ onMouseLeave={panActions.handleMouseUp}
109
+ style={{
110
+ userSelect: "none",
111
+ }}
112
+ >
113
+ <div
114
+ style={{
115
+ transform: `translate(${pan.x}px, ${pan.y}px)`,
116
+ transition: isDragging ? "none" : "transform 0.1s",
117
+ pointerEvents: "none",
118
+ maxWidth: "650px",
119
+ }}
120
+ >
121
+ <Document
122
+ externalLinkRel="noopener noreferrer"
123
+ externalLinkTarget="_blank"
124
+ file={src}
125
+ onLoadSuccess={({ numPages }) => {
126
+ pageActions.setTotalPages(numPages)
127
+ }}
128
+ scale={scale}
129
+ rotate={rotation}
130
+ >
131
+ <Page
132
+ pageNumber={currentPage}
133
+ renderTextLayer={true}
134
+ renderAnnotationLayer={true}
135
+ />
136
+ </Document>
137
+ </div>
138
+ </div>
139
+
140
+ <div className="col-start-1 row-start-1 self-start flex justify-between w-full z-10 flex-wrap items-center gap-3 bg-gray-50 p-2 shadow">
141
+ <ButtonGroup className="divide-x-1 gap-1">
142
+ <IconButton
143
+ variant="filled"
144
+ color="white"
145
+ className="w-8 h-8 rounded border border-gray-300 bg-white p-2 text-gray-700 hover:bg-gray-200 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 focus:outline-none max-w-auto"
146
+ onClick={pageActions.prevPage}
147
+ disabled={currentPage === 1}
148
+ >
149
+ <Icon icon="lucide:chevron-left" />
150
+ </IconButton>
151
+ <input
152
+ className="w-14 h-8 rounded border !border-gray-300 bg-white px-1 py-1 text-center text-sm text-gray-700 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
153
+ value={currentPage}
154
+ onChange={(e: ChangeEvent<HTMLInputElement>) => {
155
+ const page = parseInt(e.target.value)
156
+ if (!isNaN(page)) {
157
+ pageActions.goToPage(page)
158
+ }
159
+ }}
160
+ type="number"
161
+ min="1"
162
+ max={totalPages}
163
+ />
164
+ <div className="flex items-center bg-transparent px-2">
165
+ of {totalPages}
166
+ </div>
167
+ <IconButton
168
+ variant="filled"
169
+ color="white"
170
+ className="w-8 h-8 rounded border !border-gray-300 bg-white p-2 text-gray-700 hover:bg-gray-200 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 focus:outline-none max-w-auto"
171
+ onClick={pageActions.nextPage}
172
+ disabled={currentPage === totalPages}
173
+ >
174
+ <Icon icon="lucide:chevron-right" />
175
+ </IconButton>
176
+ </ButtonGroup>
177
+ <div className="flex gap-2 items-center">
178
+ <ButtonGroup className="divide-x-1 gap-1">
179
+ <IconButton
180
+ variant="filled"
181
+ color="white"
182
+ className="w-8 h-8 rounded border !border-gray-300 bg-white p-2 text-gray-700 hover:bg-gray-200 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 focus:outline-none max-w-auto"
183
+ onClick={zoomActions.zoomOut}
184
+ >
185
+ <Icon icon="lucide:minus" />
186
+ </IconButton>
187
+ <Tooltip content="Click to reset zoom and pan">
188
+ <button
189
+ className="w-14 h-8 rounded border !border-gray-300 bg-white px-1 py-1 text-center text-sm text-gray-700 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
190
+ onClick={() => {
191
+ zoomActions.reset()
192
+ panActions.reset()
193
+ }}
194
+ >
195
+ {zoom}%
196
+ </button>
197
+ </Tooltip>
198
+ <IconButton
199
+ variant="filled"
200
+ color="white"
201
+ className="w-8 h-8 rounded border !border-gray-300 bg-white p-2 text-gray-700 hover:bg-gray-200 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 focus:outline-none max-w-auto"
202
+ onClick={zoomActions.zoomIn}
203
+ >
204
+ <Icon icon="lucide:plus" />
205
+ </IconButton>
206
+ </ButtonGroup>
207
+ <div className="h-6 w-px bg-gray-300 dark:bg-gray-600"></div>
208
+ <ButtonGroup className="divide-x-1 gap-1">
209
+ {/* <IconButton
210
+ variant="filled"
211
+ color="white"
212
+ className="w-8 h-8 rounded border !border-gray-300 bg-white p-2 text-gray-700 hover:bg-gray-200 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 focus:outline-none max-w-auto"
213
+ onClick={rotationActions.rotateClockwise}
214
+ >
215
+ <Icon icon="lucide:iteration-cw" />
216
+ </IconButton> */}
217
+
218
+ <IconButton
219
+ variant="filled"
220
+ color="white"
221
+ className="w-8 h-8 rounded border !border-gray-300 bg-white p-2 text-gray-700 hover:bg-gray-200 disabled:opacity-50 dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:hover:bg-gray-600 focus:outline-none max-w-auto"
222
+ onClick={rotationActions.rotateCounterClockwise}
223
+ >
224
+ <Icon icon="lucide:iteration-ccw" />
225
+ </IconButton>
226
+ </ButtonGroup>
227
+ </div>
228
+ </div>
229
+ </div>
230
+ </div>
231
+ )
232
+ }
@@ -1,2 +1,2 @@
1
- export * from "./ImageViewer";
2
- export * from "./PdfViewer";
1
+ export * from "./ImageViewer";
2
+ export * from "./PdfViewer";
@@ -1,32 +1,32 @@
1
- import { useState, useCallback } from "react"
2
-
3
- export const usePageManagement = (
4
- initialTotalPages: number = 0,
5
- initialPage: number = 1
6
- ) => {
7
- const [currentPage, setCurrentPage] = useState<number>(initialPage)
8
- const [totalPages, setTotalPages] = useState<number>(initialTotalPages)
9
-
10
- const actions = {
11
- nextPage: useCallback(() => {
12
- setCurrentPage((prev) => {
13
- return prev < totalPages ? prev + 1 : prev
14
- })
15
- }, [totalPages]),
16
- prevPage: useCallback(() => {
17
- setCurrentPage((prev) => Math.max(prev - 1, 1))
18
- }, []),
19
- goToPage: useCallback(
20
- (page: number) => {
21
- setCurrentPage(() => Math.max(1, Math.min(page, totalPages)))
22
- },
23
- [totalPages]
24
- ),
25
- setTotalPages: useCallback((pages: number) => {
26
- setTotalPages(pages)
27
- setCurrentPage((prev) => Math.min(prev, pages))
28
- }, []),
29
- }
30
-
31
- return [{ currentPage, totalPages }, actions] as const
32
- }
1
+ import { useState, useCallback } from "react"
2
+
3
+ export const usePageManagement = (
4
+ initialTotalPages: number = 0,
5
+ initialPage: number = 1
6
+ ) => {
7
+ const [currentPage, setCurrentPage] = useState<number>(initialPage)
8
+ const [totalPages, setTotalPages] = useState<number>(initialTotalPages)
9
+
10
+ const actions = {
11
+ nextPage: useCallback(() => {
12
+ setCurrentPage((prev) => {
13
+ return prev < totalPages ? prev + 1 : prev
14
+ })
15
+ }, [totalPages]),
16
+ prevPage: useCallback(() => {
17
+ setCurrentPage((prev) => Math.max(prev - 1, 1))
18
+ }, []),
19
+ goToPage: useCallback(
20
+ (page: number) => {
21
+ setCurrentPage(() => Math.max(1, Math.min(page, totalPages)))
22
+ },
23
+ [totalPages]
24
+ ),
25
+ setTotalPages: useCallback((pages: number) => {
26
+ setTotalPages(pages)
27
+ setCurrentPage((prev) => Math.min(prev, pages))
28
+ }, []),
29
+ }
30
+
31
+ return [{ currentPage, totalPages }, actions] as const
32
+ }
@@ -1,42 +1,42 @@
1
- import { useState, MouseEvent } from "react";
2
-
3
- interface PanState {
4
- x: number;
5
- y: number;
6
- }
7
-
8
- export const usePanning = () => {
9
- const [pan, setPan] = useState<PanState>({ x: 0, y: 0 });
10
- const [isDragging, setIsDragging] = useState<boolean>(false);
11
- const [dragStart, setDragStart] = useState<PanState>({ x: 0, y: 0 });
12
-
13
- const handleMouseDown = (e: MouseEvent) => {
14
- setIsDragging(true);
15
- setDragStart({
16
- x: e.clientX - pan.x,
17
- y: e.clientY - pan.y,
18
- });
19
- };
20
-
21
- const handleMouseMove = (e: MouseEvent) => {
22
- if (!isDragging) return;
23
-
24
- setPan({
25
- x: e.clientX - dragStart.x,
26
- y: e.clientY - dragStart.y,
27
- });
28
- };
29
-
30
- const handleMouseUp = () => {
31
- setIsDragging(false);
32
- };
33
-
34
- const actions = {
35
- handleMouseDown,
36
- handleMouseMove,
37
- handleMouseUp,
38
- reset: () => setPan({ x: 0, y: 0 }),
39
- };
40
-
41
- return [{ pan, isDragging }, actions] as const;
42
- };
1
+ import { useState, MouseEvent } from "react";
2
+
3
+ interface PanState {
4
+ x: number;
5
+ y: number;
6
+ }
7
+
8
+ export const usePanning = () => {
9
+ const [pan, setPan] = useState<PanState>({ x: 0, y: 0 });
10
+ const [isDragging, setIsDragging] = useState<boolean>(false);
11
+ const [dragStart, setDragStart] = useState<PanState>({ x: 0, y: 0 });
12
+
13
+ const handleMouseDown = (e: MouseEvent) => {
14
+ setIsDragging(true);
15
+ setDragStart({
16
+ x: e.clientX - pan.x,
17
+ y: e.clientY - pan.y,
18
+ });
19
+ };
20
+
21
+ const handleMouseMove = (e: MouseEvent) => {
22
+ if (!isDragging) return;
23
+
24
+ setPan({
25
+ x: e.clientX - dragStart.x,
26
+ y: e.clientY - dragStart.y,
27
+ });
28
+ };
29
+
30
+ const handleMouseUp = () => {
31
+ setIsDragging(false);
32
+ };
33
+
34
+ const actions = {
35
+ handleMouseDown,
36
+ handleMouseMove,
37
+ handleMouseUp,
38
+ reset: () => setPan({ x: 0, y: 0 }),
39
+ };
40
+
41
+ return [{ pan, isDragging }, actions] as const;
42
+ };
@@ -1,16 +1,16 @@
1
- import { useEffect, useState } from "react";
2
-
3
- export const useRefDimensions = (ref) => {
4
- const [dimensions, setDimensions] = useState({ width: 1, height: 2 });
5
-
6
- useEffect(() => {
7
- if (ref.current) {
8
- const { current } = ref;
9
- const boundingRect = current.getBoundingClientRect();
10
- const { width, height } = boundingRect;
11
- setDimensions({ width: Math.round(width), height: Math.round(height) });
12
- }
13
- }, [ref]);
14
-
15
- return dimensions;
16
- };
1
+ import { useEffect, useState } from "react";
2
+
3
+ export const useRefDimensions = (ref) => {
4
+ const [dimensions, setDimensions] = useState({ width: 1, height: 2 });
5
+
6
+ useEffect(() => {
7
+ if (ref.current) {
8
+ const { current } = ref;
9
+ const boundingRect = current.getBoundingClientRect();
10
+ const { width, height } = boundingRect;
11
+ setDimensions({ width: Math.round(width), height: Math.round(height) });
12
+ }
13
+ }, [ref]);
14
+
15
+ return dimensions;
16
+ };