@lateralus-ai/shipping-ui 1.4.13 → 1.4.15
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 +108 -108
- package/dist/index.cjs +38 -38
- package/dist/index.esm.js +4512 -4491
- package/dist/tailwind-theme.d.ts +21 -0
- package/package.json +99 -99
- package/src/components/DocumentEditor/DocumentEditor.tsx +871 -871
- package/src/components/HelloWorld.tsx +3 -3
- package/src/components/InputPrompt.tsx +96 -96
- package/src/components/ModalPanel.tsx +49 -49
- package/src/components/PdfViewer/ImageViewer.tsx +211 -211
- package/src/components/PdfViewer/PdfViewer.tsx +232 -232
- package/src/components/PdfViewer/index.ts +2 -2
- package/src/components/PdfViewer/usePageManagement.ts +32 -32
- package/src/components/PdfViewer/usePanning.ts +42 -42
- package/src/components/PdfViewer/useRefDimensions.ts +16 -16
- package/src/components/PdfViewer/useRotation.ts +13 -13
- package/src/components/PdfViewer/useZoom.ts +26 -26
- package/src/components/SearchModal.tsx +320 -320
- package/src/components/Sidebar/Button.tsx +20 -20
- package/src/components/Sidebar/Container.tsx +31 -31
- package/src/components/Sidebar/Item.tsx +39 -39
- package/src/components/Sidebar/Layout.tsx +32 -32
- package/src/components/Sidebar/Provider.tsx +47 -47
- package/src/components/Sidebar/SecondaryItem.tsx +39 -39
- package/src/components/Sidebar/SideContainer.tsx +31 -31
- package/src/components/Sidebar/ToggleCollapseButton.tsx +24 -24
- package/src/components/Sidebar/index.ts +8 -8
- package/src/components/Tabs.tsx +43 -43
- package/src/components/icons/CloseSidebarIcon.tsx +19 -19
- package/src/components/icons/CloseSidebarMidIcon.tsx +19 -19
- package/src/components/icons/ExpandIcon.tsx +21 -21
- package/src/components/icons/SendArrowIcon.tsx +23 -23
- package/src/components/icons/SendArrowIconGreen.tsx +17 -17
- package/src/components/icons/SettingsIcon.tsx +33 -33
- package/src/components/icons/XIcon.tsx +21 -21
- package/src/components/index.ts +6 -6
- package/src/index.ts +4 -4
- package/src/material-theme.ts +477 -477
- package/src/stories/Buttons.stories.tsx +15 -15
- package/src/stories/Buttons.tsx +52 -52
- package/src/stories/Checkbox.stories.tsx +15 -15
- package/src/stories/Checkbox.tsx +56 -56
- package/src/stories/ColorPalette.stories.tsx +15 -15
- package/src/stories/ColorPalette.tsx +85 -72
- package/src/stories/DocumentEditor.stories.tsx +287 -287
- package/src/stories/Dropdowns.stories.tsx +15 -15
- package/src/stories/Dropdowns.tsx +52 -52
- package/src/stories/InputPrompt.stories.tsx +15 -15
- package/src/stories/InputPrompt.tsx +63 -63
- package/src/stories/ModalHeader.stories.tsx +35 -35
- package/src/stories/PDFViewer.stories.tsx +39 -39
- package/src/stories/SearchModal.stories.tsx +132 -132
- package/src/stories/SearchModal.tsx +82 -82
- package/src/stories/Sidebar.stories.tsx +15 -15
- package/src/stories/Sidebar.tsx +108 -108
- package/src/stories/Tabs.stories.tsx +51 -51
- package/src/stories/Typography.stories.tsx +15 -15
- package/src/stories/Typography.tsx +110 -110
- package/src/style.css +2 -2
- package/src/styles/tabs.css +55 -55
- package/src/tailwind-theme.ts +258 -231
- package/src/types/documentEditor.ts +55 -55
- package/src/utils/checkboxModule.ts +241 -241
- package/src/utils/cn.ts +11 -11
|
@@ -1,232 +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
|
-
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
|
+
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
|
+
};
|