dst-rg 1.0.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/.gitlab-ci.yml +43 -0
- package/.storybook/main.ts +15 -0
- package/.storybook/preview.ts +15 -0
- package/README.md +254 -0
- package/components.json +21 -0
- package/dist/Avatar.png +0 -0
- package/dist/assets/index-CCq7hmG3.js +186 -0
- package/dist/assets/index-Mg-hjQGu.css +1 -0
- package/dist/index.html +15 -0
- package/dist/test.png +0 -0
- package/dist/vite.svg +1 -0
- package/eslint.config.js +29 -0
- package/index.html +14 -0
- package/package.json +102 -0
- package/postcss.config.mjs +11 -0
- package/rollup.config.mjs +55 -0
- package/src/assets/react.svg +1 -0
- package/src/assets/style/animation.css +27 -0
- package/src/assets/style/box-shadow.css +25 -0
- package/src/assets/style/colors.css +402 -0
- package/src/assets/style/dark-theme.css +288 -0
- package/src/assets/style/font-size.css +14 -0
- package/src/assets/style/gradient.css +3 -0
- package/src/assets/style/index.css +12 -0
- package/src/assets/style/light-theme.css +148 -0
- package/src/assets/style/line-height.css +13 -0
- package/src/assets/style/max-width.css +5 -0
- package/src/assets/style/radius.css +13 -0
- package/src/assets/style/utility-colors.css +166 -0
- package/src/components/Accordion/_.stories.tsx +75 -0
- package/src/components/Accordion/_.test.tsx +77 -0
- package/src/components/Accordion/index.tsx +47 -0
- package/src/components/Accordion/type.ts +24 -0
- package/src/components/Avatar/_.stories.tsx +179 -0
- package/src/components/Avatar/_.style.ts +40 -0
- package/src/components/Avatar/_.test.tsx +150 -0
- package/src/components/Avatar/_.types.ts +66 -0
- package/src/components/Avatar/index.tsx +63 -0
- package/src/components/Badge/_.stories.tsx +75 -0
- package/src/components/Badge/_.style.ts +53 -0
- package/src/components/Badge/_.test.tsx +27 -0
- package/src/components/Badge/_.types.ts +11 -0
- package/src/components/Badge/index.tsx +42 -0
- package/src/components/Breadcrumbs/_.stories.tsx +95 -0
- package/src/components/Breadcrumbs/_.test.tsx +29 -0
- package/src/components/Breadcrumbs/_.type.ts +15 -0
- package/src/components/Breadcrumbs/index.tsx +103 -0
- package/src/components/Button/_.stories.tsx +85 -0
- package/src/components/Button/_.style.ts +56 -0
- package/src/components/Button/_.test.tsx +103 -0
- package/src/components/Button/_.types.ts +14 -0
- package/src/components/Button/index.tsx +70 -0
- package/src/components/Checkbox/_.stories.tsx +96 -0
- package/src/components/Checkbox/_.style.ts +24 -0
- package/src/components/Checkbox/_.test.tsx +85 -0
- package/src/components/Checkbox/_.types.ts +23 -0
- package/src/components/Checkbox/index.tsx +93 -0
- package/src/components/CheckboxGroup/PaymentCard/_.stories.tsx +104 -0
- package/src/components/CheckboxGroup/PaymentCard/_.style.ts +28 -0
- package/src/components/CheckboxGroup/PaymentCard/_.test.tsx +58 -0
- package/src/components/CheckboxGroup/PaymentCard/_.types.ts +28 -0
- package/src/components/CheckboxGroup/PaymentCard/index.tsx +71 -0
- package/src/components/CheckboxGroup/PlanCard/_.stories.tsx +165 -0
- package/src/components/CheckboxGroup/PlanCard/_.style.ts +32 -0
- package/src/components/CheckboxGroup/PlanCard/_.test.tsx +54 -0
- package/src/components/CheckboxGroup/PlanCard/_.types.ts +35 -0
- package/src/components/CheckboxGroup/PlanCard/index.tsx +53 -0
- package/src/components/CheckboxGroup/UserCard/_.stories.tsx +89 -0
- package/src/components/CheckboxGroup/UserCard/_.style.ts +42 -0
- package/src/components/CheckboxGroup/UserCard/_.test.tsx +66 -0
- package/src/components/CheckboxGroup/UserCard/_.types.ts +26 -0
- package/src/components/CheckboxGroup/UserCard/index.tsx +75 -0
- package/src/components/Dropdown/_.stories.tsx +180 -0
- package/src/components/Dropdown/_.style.ts +108 -0
- package/src/components/Dropdown/_.test.tsx +334 -0
- package/src/components/Dropdown/_.types.ts +12 -0
- package/src/components/Dropdown/index.tsx +130 -0
- package/src/components/FileUpload/_.stories.tsx +74 -0
- package/src/components/FileUpload/_.style.ts +0 -0
- package/src/components/FileUpload/_.test.tsx +222 -0
- package/src/components/FileUpload/_.types.ts +53 -0
- package/src/components/FileUpload/index.tsx +44 -0
- package/src/components/ImageMagnify/_.stories.tsx +226 -0
- package/src/components/ImageMagnify/_.style.ts +109 -0
- package/src/components/ImageMagnify/_.types.ts +44 -0
- package/src/components/ImageMagnify/index.tsx +204 -0
- package/src/components/Input/_.stories.tsx +177 -0
- package/src/components/Input/_.style.ts +79 -0
- package/src/components/Input/_.test.tsx +146 -0
- package/src/components/Input/_.types.ts +66 -0
- package/src/components/Input/index.tsx +231 -0
- package/src/components/InputTags/_.stories.tsx +51 -0
- package/src/components/InputTags/_.style.ts +28 -0
- package/src/components/InputTags/_.test.tsx +123 -0
- package/src/components/InputTags/_.types.ts +26 -0
- package/src/components/InputTags/index.tsx +140 -0
- package/src/components/Message/_.stories.tsx +79 -0
- package/src/components/Message/_.style.ts +87 -0
- package/src/components/Message/_.test.tsx +73 -0
- package/src/components/Message/_.types.ts +13 -0
- package/src/components/Message/index.tsx +57 -0
- package/src/components/Metric/_.stories.tsx +142 -0
- package/src/components/Metric/_.style.ts +14 -0
- package/src/components/Metric/_.test.tsx +166 -0
- package/src/components/Metric/_.types.ts +18 -0
- package/src/components/Metric/index.tsx +100 -0
- package/src/components/Modal/_.stories.tsx +93 -0
- package/src/components/Modal/_.style.ts +31 -0
- package/src/components/Modal/_.test.tsx +90 -0
- package/src/components/Modal/_.types.ts +14 -0
- package/src/components/Modal/index.tsx +82 -0
- package/src/components/Pagination/_.stories.tsx +118 -0
- package/src/components/Pagination/_.test.tsx +51 -0
- package/src/components/Pagination/index.tsx +256 -0
- package/src/components/Pagination/type.ts +48 -0
- package/src/components/PriceSlider/_.stories.tsx +107 -0
- package/src/components/PriceSlider/_.test.tsx +63 -0
- package/src/components/PriceSlider/_.type.tsx +19 -0
- package/src/components/PriceSlider/index.tsx +86 -0
- package/src/components/Progress/_.stories.tsx +93 -0
- package/src/components/Progress/_.style.ts +15 -0
- package/src/components/Progress/_.test.tsx +34 -0
- package/src/components/Progress/_.types.ts +17 -0
- package/src/components/Progress/index.tsx +173 -0
- package/src/components/Radio/_.stories.tsx +391 -0
- package/src/components/Radio/_.style.ts +33 -0
- package/src/components/Radio/_.test.tsx +77 -0
- package/src/components/Radio/_.types.ts +14 -0
- package/src/components/Radio/index.tsx +59 -0
- package/src/components/Select/_.stories.tsx +308 -0
- package/src/components/Select/_.style.ts +5 -0
- package/src/components/Select/_.types.ts +24 -0
- package/src/components/Select/index.tsx +172 -0
- package/src/components/Switch/_.stories.tsx +61 -0
- package/src/components/Switch/_.test.tsx +69 -0
- package/src/components/Switch/_.type.ts +12 -0
- package/src/components/Switch/index.tsx +70 -0
- package/src/components/Tabs/_.stories.tsx +508 -0
- package/src/components/Tabs/_.style.ts +63 -0
- package/src/components/Tabs/_.test.tsx +174 -0
- package/src/components/Tabs/_.type.ts +19 -0
- package/src/components/Tabs/index.tsx +35 -0
- package/src/components/Tag/_.stories.tsx +78 -0
- package/src/components/Tag/_.style.ts +71 -0
- package/src/components/Tag/_.test.tsx +44 -0
- package/src/components/Tag/_.types.ts +27 -0
- package/src/components/Tag/index.tsx +46 -0
- package/src/components/TextArea/_.stories.tsx +62 -0
- package/src/components/TextArea/_.style.ts +11 -0
- package/src/components/TextArea/_.test.tsx +43 -0
- package/src/components/TextArea/_.types.ts +29 -0
- package/src/components/TextArea/index.tsx +83 -0
- package/src/components/Toast/_.style.tsx +27 -0
- package/src/components/Toast/_.type.ts +30 -0
- package/src/components/Toast/_.utils.ts +23 -0
- package/src/components/Toast/container.tsx +171 -0
- package/src/components/Toast/index.tsx +29 -0
- package/src/components/Tooltip/_.stories.tsx +106 -0
- package/src/components/Tooltip/_.style.ts +27 -0
- package/src/components/Tooltip/_.test.tsx +54 -0
- package/src/components/Tooltip/_.types.ts +31 -0
- package/src/components/Tooltip/index.tsx +80 -0
- package/src/components/developers/AmirHossein.tsx +149 -0
- package/src/components/developers/Fardin.tsx +130 -0
- package/src/components/developers/Maryam.tsx +260 -0
- package/src/components/developers/Milad.tsx +431 -0
- package/src/components/developers/Rasoul.tsx +198 -0
- package/src/components/index.ts +28 -0
- package/src/components/ui/accordion.tsx +162 -0
- package/src/components/ui/avatars-component/avatar-description.tsx +30 -0
- package/src/components/ui/avatars-component/avatar-groups.tsx +68 -0
- package/src/components/ui/avatars-component/avatar-single.tsx +50 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/checkbox-group/plan-card/basic/_.test.tsx +66 -0
- package/src/components/ui/checkbox-group/plan-card/basic/index.tsx +70 -0
- package/src/components/ui/checkbox-group/plan-card/with-header/_.test.tsx +110 -0
- package/src/components/ui/checkbox-group/plan-card/with-header/header.test.tsx +96 -0
- package/src/components/ui/checkbox-group/plan-card/with-header/header.tsx +74 -0
- package/src/components/ui/checkbox-group/plan-card/with-header/index.tsx +65 -0
- package/src/components/ui/file-content/File-content.tsx +43 -0
- package/src/components/ui/file-uploader-components/file-uploader-box.tsx +76 -0
- package/src/components/ui/file-uploader-components/file-uploader-item.tsx +64 -0
- package/src/components/ui/icon-wrapper/_.test.tsx +60 -0
- package/src/components/ui/icon-wrapper/index.tsx +19 -0
- package/src/components/ui/input-component/input-label.tsx +11 -0
- package/src/components/ui/number.tsx +18 -0
- package/src/components/ui/pagination/card-minimal-center-align.tsx +96 -0
- package/src/components/ui/pagination/card-minimal-right-aligne.tsx +90 -0
- package/src/components/ui/pagination/default-pagination.tsx +128 -0
- package/src/components/ui/pagination/get-pagination-item.tsx +36 -0
- package/src/components/ui/pagination/pagination-card-button-group-aligned.tsx +94 -0
- package/src/components/ui/pagination/pagination-card-minimal-left-aligned.tsx +90 -0
- package/src/components/ui/pagination/pagination-content.tsx +15 -0
- package/src/components/ui/pagination/pagination-item.tsx +11 -0
- package/src/components/ui/pagination/pagination-link.tsx +42 -0
- package/src/components/ui/tab-components/tabs-content.tsx +15 -0
- package/src/components/ui/tab-components/tabs-list.tsx +27 -0
- package/src/components/ui/tab-components/tabs-trigger.tsx +25 -0
- package/src/components/ui/text-content-wrapper.tsx +36 -0
- package/src/hooks/useClickOutside.ts +23 -0
- package/src/icons/general/ArrowLeft.tsx +31 -0
- package/src/icons/general/ArrowRight.tsx +31 -0
- package/src/icons/general/activity-heart.tsx +31 -0
- package/src/icons/general/activity.tsx +31 -0
- package/src/icons/general/anchor.tsx +31 -0
- package/src/icons/general/archive.tsx +31 -0
- package/src/icons/general/arrow-left.tsx +25 -0
- package/src/icons/general/arrow-right.tsx +25 -0
- package/src/icons/general/asterisk-01.tsx +31 -0
- package/src/icons/general/asterisk-02.tsx +31 -0
- package/src/icons/general/at-sign.tsx +31 -0
- package/src/icons/general/attention-mark.tsx +43 -0
- package/src/icons/general/bookmark-add.tsx +31 -0
- package/src/icons/general/bookmark.tsx +31 -0
- package/src/icons/general/chevron-left.tsx +25 -0
- package/src/icons/general/chevron-right.tsx +25 -0
- package/src/icons/general/circle-minues.tsx +25 -0
- package/src/icons/general/circle-plus.tsx +25 -0
- package/src/icons/general/circle-question-mark.tsx +32 -0
- package/src/icons/general/circle.tsx +32 -0
- package/src/icons/general/copy.tsx +43 -0
- package/src/icons/general/email.tsx +32 -0
- package/src/icons/general/home.tsx +25 -0
- package/src/icons/general/layer.tsx +36 -0
- package/src/icons/general/leading.tsx +19 -0
- package/src/icons/general/master-card.tsx +37 -0
- package/src/icons/general/minus.tsx +36 -0
- package/src/icons/general/plus.tsx +19 -0
- package/src/icons/general/remove.tsx +32 -0
- package/src/icons/general/slash-divider.tsx +26 -0
- package/src/icons/general/tick-box.tsx +37 -0
- package/src/icons/general/trailing.tsx +19 -0
- package/src/icons/general/unkown-format.tsx +25 -0
- package/src/icons/general/visa-card.tsx +38 -0
- package/src/icons/general/x-close.tsx +35 -0
- package/src/icons/icons.type.ts +7 -0
- package/src/index.css +21 -0
- package/src/index.ts +3 -0
- package/src/lib/utils.ts +6 -0
- package/src/lib/zIndexUtils.ts +2 -0
- package/src/main.tsx +50 -0
- package/src/vite-env.d.ts +1 -0
- package/tests/setup.ts +8 -0
- package/tsconfig.app.json +31 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +24 -0
- package/tsconfig.rollup.json +12 -0
- package/vite.config.ts +20 -0
- package/vitest.config.ts +47 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { ComponentProps } from "react";
|
|
2
|
+
import type { Decorator, Meta, StoryObj } from "@storybook/react-vite";
|
|
3
|
+
import { ImageMagnify } from "./index";
|
|
4
|
+
|
|
5
|
+
type LayoutOption = "centered" | "left" | "right";
|
|
6
|
+
type ImageMagnifyStoryArgs = ComponentProps<typeof ImageMagnify> & {
|
|
7
|
+
layout: LayoutOption;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const withDynamicLayout: Decorator = (Story, context) => {
|
|
11
|
+
const layout = context.args?.layout ?? "centered";
|
|
12
|
+
const justifyContent =
|
|
13
|
+
layout === "left"
|
|
14
|
+
? "flex-start"
|
|
15
|
+
: layout === "right"
|
|
16
|
+
? "flex-end"
|
|
17
|
+
: "center";
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div style={{ display: "flex", justifyContent, width: "100%" }}>
|
|
21
|
+
<div style={{ maxWidth: 420 }}>
|
|
22
|
+
<Story />
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const meta: Meta<ImageMagnifyStoryArgs> = {
|
|
29
|
+
title: "Components/ImageMagnify",
|
|
30
|
+
component: ImageMagnify,
|
|
31
|
+
tags: ["autodocs"],
|
|
32
|
+
decorators: [withDynamicLayout],
|
|
33
|
+
parameters: {
|
|
34
|
+
docs: {
|
|
35
|
+
description: {
|
|
36
|
+
component:
|
|
37
|
+
"A magnifying glass component that allows users to zoom in on images with mouse hover or touch interactions.",
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
argTypes: {
|
|
42
|
+
src: {
|
|
43
|
+
control: "text",
|
|
44
|
+
description: "Image source URL",
|
|
45
|
+
},
|
|
46
|
+
alt: {
|
|
47
|
+
control: "text",
|
|
48
|
+
description: "Alt text for the image",
|
|
49
|
+
},
|
|
50
|
+
zoom: {
|
|
51
|
+
control: { type: "range", min: 1, max: 5, step: 0.5 },
|
|
52
|
+
description: "Zoom level multiplier",
|
|
53
|
+
},
|
|
54
|
+
layout: {
|
|
55
|
+
control: "inline-radio",
|
|
56
|
+
options: ["centered", "left", "right"],
|
|
57
|
+
description: "Horizontal alignment of the story canvas",
|
|
58
|
+
},
|
|
59
|
+
lensSize: {
|
|
60
|
+
control: { type: "range", min: 50, max: 300, step: 10 },
|
|
61
|
+
description: "Size of the magnifying lens in pixels",
|
|
62
|
+
},
|
|
63
|
+
zoomPanePosition: {
|
|
64
|
+
control: "inline-radio",
|
|
65
|
+
options: ["right", "left"],
|
|
66
|
+
description: "Position of the zoom pane relative to the source image",
|
|
67
|
+
},
|
|
68
|
+
zoomMode: {
|
|
69
|
+
control: "inline-radio",
|
|
70
|
+
options: ["pane", "inline"],
|
|
71
|
+
description: "Display mode: standalone zoom pane or inline lens",
|
|
72
|
+
},
|
|
73
|
+
touchToggle: {
|
|
74
|
+
control: "boolean",
|
|
75
|
+
description: "Enable touch toggle on mobile",
|
|
76
|
+
},
|
|
77
|
+
zoomPaneSize: {
|
|
78
|
+
control: { type: "range", min: 100, max: 800, step: 50 },
|
|
79
|
+
description: "Custom zoom pane size in pixels",
|
|
80
|
+
},
|
|
81
|
+
className: {
|
|
82
|
+
control: "text",
|
|
83
|
+
description: "Additional CSS class name",
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
args: {
|
|
87
|
+
layout: "centered",
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export default meta;
|
|
92
|
+
type Story = StoryObj<ImageMagnifyStoryArgs>;
|
|
93
|
+
|
|
94
|
+
// Sample images for stories
|
|
95
|
+
const sampleImage =
|
|
96
|
+
"https://images.unsplash.com/photo-1506905925346-21bda4d32df4?w=400&h=300&fit=crop";
|
|
97
|
+
|
|
98
|
+
export const Default: Story = {
|
|
99
|
+
args: {
|
|
100
|
+
src: sampleImage,
|
|
101
|
+
alt: "Sample image for magnification",
|
|
102
|
+
zoom: 2,
|
|
103
|
+
lensSize: 140,
|
|
104
|
+
touchToggle: true,
|
|
105
|
+
zoomMode: "pane",
|
|
106
|
+
zoomPanePosition: "right",
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export const HighZoom: Story = {
|
|
111
|
+
args: {
|
|
112
|
+
src: sampleImage,
|
|
113
|
+
alt: "High zoom magnification",
|
|
114
|
+
zoom: 3,
|
|
115
|
+
lensSize: 120,
|
|
116
|
+
touchToggle: true,
|
|
117
|
+
zoomMode: "pane",
|
|
118
|
+
zoomPanePosition: "right",
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
export const LargeLens: Story = {
|
|
123
|
+
args: {
|
|
124
|
+
src: sampleImage,
|
|
125
|
+
alt: "Large lens magnification",
|
|
126
|
+
zoom: 2,
|
|
127
|
+
lensSize: 200,
|
|
128
|
+
touchToggle: true,
|
|
129
|
+
zoomMode: "pane",
|
|
130
|
+
zoomPanePosition: "right",
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
export const LeftPosition: Story = {
|
|
135
|
+
args: {
|
|
136
|
+
src: sampleImage,
|
|
137
|
+
alt: "Zoom pane on the left",
|
|
138
|
+
zoom: 2,
|
|
139
|
+
lensSize: 140,
|
|
140
|
+
touchToggle: true,
|
|
141
|
+
zoomMode: "pane",
|
|
142
|
+
zoomPanePosition: "left",
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export const CustomZoomPaneSize: Story = {
|
|
147
|
+
args: {
|
|
148
|
+
src: sampleImage,
|
|
149
|
+
alt: "Custom zoom pane size",
|
|
150
|
+
zoom: 2.5,
|
|
151
|
+
lensSize: 140,
|
|
152
|
+
touchToggle: true,
|
|
153
|
+
zoomPaneSize: 300,
|
|
154
|
+
zoomMode: "pane",
|
|
155
|
+
zoomPanePosition: "right",
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const InlineLens: Story = {
|
|
160
|
+
args: {
|
|
161
|
+
src: sampleImage,
|
|
162
|
+
alt: "Inline lens magnification",
|
|
163
|
+
zoom: 2.5,
|
|
164
|
+
lensSize: 160,
|
|
165
|
+
touchToggle: true,
|
|
166
|
+
zoomMode: "inline",
|
|
167
|
+
zoomPanePosition: "right",
|
|
168
|
+
},
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export const NoTouchToggle: Story = {
|
|
172
|
+
args: {
|
|
173
|
+
src: sampleImage,
|
|
174
|
+
alt: "No touch toggle",
|
|
175
|
+
zoom: 2,
|
|
176
|
+
lensSize: 140,
|
|
177
|
+
touchToggle: false,
|
|
178
|
+
zoomMode: "pane",
|
|
179
|
+
zoomPanePosition: "right",
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export const SmallLens: Story = {
|
|
184
|
+
args: {
|
|
185
|
+
src: sampleImage,
|
|
186
|
+
alt: "Small lens for precision",
|
|
187
|
+
zoom: 3,
|
|
188
|
+
lensSize: 80,
|
|
189
|
+
touchToggle: true,
|
|
190
|
+
zoomMode: "pane",
|
|
191
|
+
zoomPanePosition: "right",
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
export const WithCustomClass: Story = {
|
|
196
|
+
args: {
|
|
197
|
+
src: sampleImage,
|
|
198
|
+
alt: "With custom styling",
|
|
199
|
+
zoom: 2,
|
|
200
|
+
lensSize: 140,
|
|
201
|
+
touchToggle: true,
|
|
202
|
+
className: "border-2 border-blue-500 rounded-lg p-4",
|
|
203
|
+
zoomMode: "pane",
|
|
204
|
+
zoomPanePosition: "right",
|
|
205
|
+
},
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
export const Interactive: Story = {
|
|
209
|
+
args: {
|
|
210
|
+
src: sampleImage,
|
|
211
|
+
alt: "Interactive magnification",
|
|
212
|
+
zoom: 2,
|
|
213
|
+
lensSize: 140,
|
|
214
|
+
touchToggle: true,
|
|
215
|
+
zoomMode: "pane",
|
|
216
|
+
zoomPanePosition: "right",
|
|
217
|
+
},
|
|
218
|
+
parameters: {
|
|
219
|
+
docs: {
|
|
220
|
+
description: {
|
|
221
|
+
story:
|
|
222
|
+
"Hover over the image to see the magnification effect. On mobile devices, tap to toggle the zoom.",
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Tailwind CSS classes for ImageMagnify component
|
|
2
|
+
|
|
3
|
+
export const imageMagnifyClasses = {
|
|
4
|
+
// Main container
|
|
5
|
+
container: 'flex items-start gap-3',
|
|
6
|
+
|
|
7
|
+
// Image container
|
|
8
|
+
imageContainer: 'relative select-none touch-none',
|
|
9
|
+
|
|
10
|
+
// Image element
|
|
11
|
+
image: 'block max-w-full h-auto select-none',
|
|
12
|
+
|
|
13
|
+
// Lens styles
|
|
14
|
+
lens: 'pointer-events-none absolute rounded-full border-2 border-white shadow-lg bg-transparent z-10',
|
|
15
|
+
lensAnimated: 'pointer-events-none absolute rounded-full border-2 border-white shadow-lg bg-transparent z-10 transition-opacity duration-200 ease-in-out',
|
|
16
|
+
|
|
17
|
+
// Lens variants
|
|
18
|
+
lensGlow: 'pointer-events-none absolute rounded-full border-2 border-white shadow-lg bg-transparent z-10 shadow-[0_0_0_2px_white,0_0_20px_rgba(59,130,246,0.5),0_4px_6px_-1px_rgba(0,0,0,0.1)]',
|
|
19
|
+
lensDashed: 'pointer-events-none absolute rounded-full border-2 border-dashed border-white bg-white/10 z-10',
|
|
20
|
+
|
|
21
|
+
// Zoom pane
|
|
22
|
+
zoomPane: 'overflow-hidden rounded-lg border border-slate-200 shadow-sm relative flex-shrink-0',
|
|
23
|
+
zoomPaneAnimated: 'overflow-hidden rounded-lg border border-slate-200 shadow-sm relative flex-shrink-0 transition-opacity duration-200 ease-in-out',
|
|
24
|
+
|
|
25
|
+
// Zoom pane variants
|
|
26
|
+
zoomPaneRounded: 'overflow-hidden rounded-2xl border border-slate-200 shadow-sm relative flex-shrink-0',
|
|
27
|
+
zoomPaneSharp: 'overflow-hidden border border-slate-200 shadow-sm relative flex-shrink-0',
|
|
28
|
+
zoomPaneGlow: 'overflow-hidden rounded-lg border border-slate-200 shadow-sm relative flex-shrink-0 shadow-[0_4px_6px_-1px_rgba(0,0,0,0.1),0_2px_4px_-1px_rgba(0,0,0,0.06),0_0_0_1px_rgba(59,130,246,0.2)]',
|
|
29
|
+
|
|
30
|
+
// Responsive container
|
|
31
|
+
responsiveContainer: 'flex items-start gap-3 md:flex-row flex-col md:items-start items-center gap-4',
|
|
32
|
+
|
|
33
|
+
// Hidden/visible states
|
|
34
|
+
hidden: 'hidden',
|
|
35
|
+
visible: 'block',
|
|
36
|
+
|
|
37
|
+
// Touch action
|
|
38
|
+
touchNone: 'touch-none',
|
|
39
|
+
|
|
40
|
+
// Image drag prevention
|
|
41
|
+
noDrag: 'select-none',
|
|
42
|
+
|
|
43
|
+
// Lens positioning utilities
|
|
44
|
+
lensPosition: (x: number, y: number, size: number) => ({
|
|
45
|
+
left: Math.max(0, x - size / 2),
|
|
46
|
+
top: Math.max(0, y - size / 2),
|
|
47
|
+
width: size,
|
|
48
|
+
height: size,
|
|
49
|
+
}),
|
|
50
|
+
|
|
51
|
+
// Zoom pane background utilities
|
|
52
|
+
zoomPaneBackground: (imageUrl: string, size: string, position: string) => ({
|
|
53
|
+
backgroundImage: `url(${imageUrl})`,
|
|
54
|
+
backgroundRepeat: 'no-repeat',
|
|
55
|
+
backgroundSize: size,
|
|
56
|
+
backgroundPosition: position,
|
|
57
|
+
}),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Utility functions for dynamic styles
|
|
61
|
+
export const getLensStyle = (
|
|
62
|
+
lensSize: number,
|
|
63
|
+
lensPos: { x: number; y: number },
|
|
64
|
+
visible: boolean
|
|
65
|
+
) => ({
|
|
66
|
+
width: lensSize,
|
|
67
|
+
height: lensSize,
|
|
68
|
+
left: Math.max(0, lensPos.x - lensSize / 2),
|
|
69
|
+
top: Math.max(0, lensPos.y - lensSize / 2),
|
|
70
|
+
display: visible ? 'block' : 'none',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
export const getZoomPaneStyle = (
|
|
74
|
+
width: number,
|
|
75
|
+
height: number,
|
|
76
|
+
backgroundImage: string,
|
|
77
|
+
backgroundSize: string,
|
|
78
|
+
backgroundPosition: string,
|
|
79
|
+
visible: boolean
|
|
80
|
+
) => ({
|
|
81
|
+
width,
|
|
82
|
+
height,
|
|
83
|
+
backgroundImage: `url(${backgroundImage})`,
|
|
84
|
+
backgroundRepeat: 'no-repeat',
|
|
85
|
+
backgroundSize,
|
|
86
|
+
backgroundPosition,
|
|
87
|
+
display: visible ? 'block' : 'none',
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// Predefined class combinations for common use cases
|
|
91
|
+
export const lensVariants = {
|
|
92
|
+
default: imageMagnifyClasses.lens,
|
|
93
|
+
animated: imageMagnifyClasses.lensAnimated,
|
|
94
|
+
glow: imageMagnifyClasses.lensGlow,
|
|
95
|
+
dashed: imageMagnifyClasses.lensDashed,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export const zoomPaneVariants = {
|
|
99
|
+
default: imageMagnifyClasses.zoomPane,
|
|
100
|
+
animated: imageMagnifyClasses.zoomPaneAnimated,
|
|
101
|
+
rounded: imageMagnifyClasses.zoomPaneRounded,
|
|
102
|
+
sharp: imageMagnifyClasses.zoomPaneSharp,
|
|
103
|
+
glow: imageMagnifyClasses.zoomPaneGlow,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const containerVariants = {
|
|
107
|
+
default: imageMagnifyClasses.container,
|
|
108
|
+
responsive: imageMagnifyClasses.responsiveContainer,
|
|
109
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export interface ImageMagnifyProps {
|
|
2
|
+
/** Image source URL */
|
|
3
|
+
src: string;
|
|
4
|
+
/** Alt text for the image */
|
|
5
|
+
alt?: string;
|
|
6
|
+
/** Zoom level multiplier (e.g., 2 = 200% zoom) */
|
|
7
|
+
zoom?: number;
|
|
8
|
+
/** Size of the magnifying lens in pixels (square) */
|
|
9
|
+
lensSize?: number;
|
|
10
|
+
/** Additional CSS class name */
|
|
11
|
+
className?: string;
|
|
12
|
+
/** Tailwind classes applied when lens is visible */
|
|
13
|
+
lensVisibleClassName?: string;
|
|
14
|
+
/** Tailwind classes applied when zoom pane is visible */
|
|
15
|
+
zoomPaneVisibleClassName?: string;
|
|
16
|
+
/** Position of the zoom pane relative to the image */
|
|
17
|
+
zoomPanePosition?: 'right' | 'left';
|
|
18
|
+
/** Enable touch toggle functionality on mobile devices */
|
|
19
|
+
touchToggle?: boolean;
|
|
20
|
+
/** Custom zoom pane size in pixels (optional) */
|
|
21
|
+
zoomPaneSize?: number;
|
|
22
|
+
/** Zoom mode: 'pane' for separate zoom pane, 'inline' for zoom directly on image */
|
|
23
|
+
zoomMode?: 'pane' | 'inline';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface LensPosition {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface ImageSize {
|
|
32
|
+
w: number;
|
|
33
|
+
h: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ImageMagnifyState {
|
|
37
|
+
visible: boolean;
|
|
38
|
+
lensPos: LensPosition;
|
|
39
|
+
imgSize: ImageSize;
|
|
40
|
+
isTouchZoom: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type ZoomPanePosition = 'right' | 'left';
|
|
44
|
+
export type ZoomMode = 'pane' | 'inline';
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from "react";
|
|
2
|
+
import type { ImageMagnifyProps, LensPosition } from "./_.types";
|
|
3
|
+
|
|
4
|
+
const clamp = (value: number, min: number, max: number) =>
|
|
5
|
+
Math.min(Math.max(value, min), max);
|
|
6
|
+
|
|
7
|
+
const defaultLensPosition: LensPosition = { x: 0, y: 0 };
|
|
8
|
+
|
|
9
|
+
export function ImageMagnify({
|
|
10
|
+
src,
|
|
11
|
+
alt = "",
|
|
12
|
+
zoom = 2,
|
|
13
|
+
lensSize = 140,
|
|
14
|
+
className = "",
|
|
15
|
+
lensVisibleClassName = "hidden md:block",
|
|
16
|
+
zoomPaneVisibleClassName = "hidden md:block",
|
|
17
|
+
touchToggle = true,
|
|
18
|
+
zoomPaneSize,
|
|
19
|
+
zoomPanePosition = "right",
|
|
20
|
+
zoomMode = "pane",
|
|
21
|
+
}: ImageMagnifyProps) {
|
|
22
|
+
const containerRef = useRef<HTMLDivElement | null>(null);
|
|
23
|
+
const imgRef = useRef<HTMLImageElement | null>(null);
|
|
24
|
+
|
|
25
|
+
const [visible, setVisible] = useState(false);
|
|
26
|
+
const [lensPos, setLensPos] = useState<LensPosition>(defaultLensPosition);
|
|
27
|
+
const [imgSize, setImgSize] = useState({ w: 0, h: 0 });
|
|
28
|
+
const [isTouchZoom, setIsTouchZoom] = useState(false);
|
|
29
|
+
|
|
30
|
+
useEffect(() => {
|
|
31
|
+
const update = () => {
|
|
32
|
+
const img = imgRef.current;
|
|
33
|
+
if (!img) return;
|
|
34
|
+
setImgSize({ w: img.width, h: img.height });
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const img = imgRef.current;
|
|
38
|
+
if (!img) return;
|
|
39
|
+
|
|
40
|
+
if (img.complete) update();
|
|
41
|
+
img.addEventListener("load", update);
|
|
42
|
+
window.addEventListener("resize", update);
|
|
43
|
+
|
|
44
|
+
return () => {
|
|
45
|
+
img.removeEventListener("load", update);
|
|
46
|
+
window.removeEventListener("resize", update);
|
|
47
|
+
};
|
|
48
|
+
}, [src]);
|
|
49
|
+
|
|
50
|
+
const getRelativePosition = (
|
|
51
|
+
clientX: number,
|
|
52
|
+
clientY: number
|
|
53
|
+
): LensPosition => {
|
|
54
|
+
const img = imgRef.current;
|
|
55
|
+
if (!img) return defaultLensPosition;
|
|
56
|
+
|
|
57
|
+
const rect = img.getBoundingClientRect();
|
|
58
|
+
if (rect.width === 0 || rect.height === 0) return defaultLensPosition;
|
|
59
|
+
|
|
60
|
+
const x = clamp(clientX - rect.left, 0, rect.width);
|
|
61
|
+
const y = clamp(clientY - rect.top, 0, rect.height);
|
|
62
|
+
|
|
63
|
+
return { x, y };
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const updateLensFromEvent = (event: React.MouseEvent | React.TouchEvent) => {
|
|
67
|
+
const nativeEvent =
|
|
68
|
+
"nativeEvent" in event ? (event.nativeEvent as any) : (event as any);
|
|
69
|
+
|
|
70
|
+
if ("touches" in nativeEvent && nativeEvent.touches?.length > 0) {
|
|
71
|
+
const touch = nativeEvent.touches[0];
|
|
72
|
+
setLensPos(getRelativePosition(touch.clientX, touch.clientY));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (typeof nativeEvent.clientX === "number") {
|
|
77
|
+
setLensPos(getRelativePosition(nativeEvent.clientX, nativeEvent.clientY));
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const handleMove = (event: React.MouseEvent<HTMLDivElement>) => {
|
|
82
|
+
updateLensFromEvent(event);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const handleEnter = () => {
|
|
86
|
+
setVisible(true);
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const handleLeave = () => {
|
|
90
|
+
setVisible(false);
|
|
91
|
+
if (isTouchZoom) setIsTouchZoom(false);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {
|
|
95
|
+
if (!touchToggle) return;
|
|
96
|
+
|
|
97
|
+
event.preventDefault();
|
|
98
|
+
|
|
99
|
+
if (!isTouchZoom) {
|
|
100
|
+
setIsTouchZoom(true);
|
|
101
|
+
setVisible(true);
|
|
102
|
+
updateLensFromEvent(event);
|
|
103
|
+
} else {
|
|
104
|
+
setIsTouchZoom(false);
|
|
105
|
+
setVisible(false);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {
|
|
110
|
+
if (!isTouchZoom) return;
|
|
111
|
+
event.preventDefault();
|
|
112
|
+
updateLensFromEvent(event);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const currentRect = imgRef.current?.getBoundingClientRect();
|
|
116
|
+
const displayW = currentRect?.width ?? imgSize.w;
|
|
117
|
+
const displayH = currentRect?.height ?? imgSize.h;
|
|
118
|
+
const safeDisplayW = displayW || 1;
|
|
119
|
+
const safeDisplayH = displayH || 1;
|
|
120
|
+
|
|
121
|
+
const backgroundSize = `${safeDisplayW * zoom}px ${safeDisplayH * zoom}px`;
|
|
122
|
+
const backgroundPosition = `${(lensPos.x / safeDisplayW) * 100}% ${
|
|
123
|
+
(lensPos.y / safeDisplayH) * 100
|
|
124
|
+
}%`;
|
|
125
|
+
|
|
126
|
+
const paneWidth = zoomPaneSize ?? safeDisplayW;
|
|
127
|
+
const paneHeight = zoomPaneSize ?? safeDisplayH;
|
|
128
|
+
|
|
129
|
+
const lensStyle: React.CSSProperties = {
|
|
130
|
+
width: lensSize,
|
|
131
|
+
height: lensSize,
|
|
132
|
+
left: clamp(
|
|
133
|
+
lensPos.x - lensSize / 2,
|
|
134
|
+
0,
|
|
135
|
+
Math.max(safeDisplayW - lensSize, 0)
|
|
136
|
+
),
|
|
137
|
+
top: clamp(
|
|
138
|
+
lensPos.y - lensSize / 2,
|
|
139
|
+
0,
|
|
140
|
+
Math.max(safeDisplayH - lensSize, 0)
|
|
141
|
+
),
|
|
142
|
+
backgroundImage: zoomMode === "inline" ? `url(${src})` : "none",
|
|
143
|
+
backgroundRepeat: "no-repeat",
|
|
144
|
+
backgroundSize: zoomMode === "inline" ? backgroundSize : undefined,
|
|
145
|
+
backgroundPosition: zoomMode === "inline" ? backgroundPosition : undefined,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const zoomPaneStyle: React.CSSProperties = {
|
|
149
|
+
width: paneWidth,
|
|
150
|
+
height: paneHeight,
|
|
151
|
+
backgroundImage: `url(${src})`,
|
|
152
|
+
backgroundRepeat: "no-repeat",
|
|
153
|
+
backgroundSize,
|
|
154
|
+
backgroundPosition,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const isPaneLeft = zoomPanePosition === "left";
|
|
158
|
+
const lensVisibilityClass = visible ? lensVisibleClassName : "hidden";
|
|
159
|
+
const zoomPaneVisibilityClass =
|
|
160
|
+
zoomMode === "pane" && visible ? zoomPaneVisibleClassName : "hidden";
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<div className={`relative w-fit inline-block align-top ${className}`}>
|
|
164
|
+
<div
|
|
165
|
+
ref={containerRef}
|
|
166
|
+
className="relative select-none touch-none"
|
|
167
|
+
onMouseMove={handleMove}
|
|
168
|
+
onMouseEnter={handleEnter}
|
|
169
|
+
onMouseLeave={handleLeave}
|
|
170
|
+
onTouchStart={handleTouchStart}
|
|
171
|
+
onTouchMove={handleTouchMove}
|
|
172
|
+
onTouchEnd={handleLeave}
|
|
173
|
+
>
|
|
174
|
+
<img
|
|
175
|
+
ref={imgRef}
|
|
176
|
+
src={src}
|
|
177
|
+
alt={alt}
|
|
178
|
+
className="block max-w-full h-auto select-none"
|
|
179
|
+
draggable={false}
|
|
180
|
+
onDragStart={(event) => event.preventDefault()}
|
|
181
|
+
/>
|
|
182
|
+
|
|
183
|
+
<div
|
|
184
|
+
className={`pointer-events-none absolute rounded-full border-2 border-white shadow-lg ${lensVisibilityClass} ${
|
|
185
|
+
zoomMode === "inline"
|
|
186
|
+
? "bg-white/10 backdrop-blur"
|
|
187
|
+
: "bg-transparent"
|
|
188
|
+
}`}
|
|
189
|
+
style={lensStyle}
|
|
190
|
+
/>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
{/* {zoomMode === "pane" && ( */}
|
|
194
|
+
<div
|
|
195
|
+
// aria-hidden={!visible}
|
|
196
|
+
className={`${zoomPaneVisibilityClass} absolute top-1/2 -translate-y-1/2 overflow-hidden rounded-lg border border-slate-200 shadow-sm
|
|
197
|
+
${isPaneLeft ? "right-full mr-3" : "left-full ml-3"}
|
|
198
|
+
`}
|
|
199
|
+
style={zoomPaneStyle}
|
|
200
|
+
/>
|
|
201
|
+
{/* )} */}
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
}
|