Rozbudowa RWD

This commit is contained in:
2026-01-07 15:09:16 +01:00
parent df964a8088
commit e6a9465195
24 changed files with 728 additions and 311 deletions

178
IdoSellAddOn/script.js Normal file
View File

@@ -0,0 +1,178 @@
function idmObserveEachOnce(elements, callback, options = {}) {
const observer = new IntersectionObserver(
(entries, obs) => {
entries.forEach((entry) => {
if (!entry.isIntersecting) return;
callback(entry);
obs.unobserve(entry.target);
});
},
{
threshold: 0.1,
...options,
}
);
elements.forEach((el) => observer.observe(el));
return observer;
}
idmObserveEachOnce(
document.querySelectorAll(".idm_picture__module"),
(entry) => {
idmPictureModuleProducts(entry.target);
}
);
function idmGetSingleProdGraphQL(prodId) {
const IDM_PRICE_QUERY = (priceType) => `price {
rebateCodeActive
price {
${priceType} {
value
formatted
}
}
omnibusPrice {
${priceType} {
value
formatted
}
}
omnibusPriceDetails {
unit {
${priceType} {
value
formatted
}
}
youSavePercent
omnibusPriceIsHigherThanSellingPrice
newPriceEffectiveUntil {
formatted
}
}
max {
${priceType} {
value
formatted
}
}
unit {
${priceType} {
value
formatted
}
}
unitConvertedPrice {
${priceType} {
value
formatted
}
}
youSavePercent
beforeRebate {
${priceType} {
value
formatted
}
}
beforeRebateDetails {
youSavePercent
unit {
${priceType} {
value
formatted
}
}
}
advancePrice {
${priceType} {
value
formatted
}
}
suggested {
${priceType} {
value
formatted
}
}
rebateNumber {
number
${priceType} {
value
formatted
}
}
}`;
return `
prod${prodId}: product(productId: ${prodId}) {
product {
id
name
link
${IDM_PRICE_QUERY(app_shop.vars.priceType)}
}
}`;
}
async function idmPictureModuleProducts(containerEL) {
try {
const allProdEl = containerEL.querySelectorAll(".product_info[data-id]");
const productsId = [];
allProdEl.forEach((prodEl) => {
if (prodEl.dataset?.id) productsId.push(prodEl.dataset?.id);
});
const res = await fetch("/graphql/v1/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
query: `{${productsId.reduce(
(acc, val, index) => acc + `${idmGetSingleProdGraphQL(val)}`,
""
)}}`,
}),
});
const data = await res.json();
const products = Object.values(data?.data)?.map((prod) => prod.product);
allProdEl.forEach((prodEl) => {
const prodData = products.find((p) => p.id === +prodEl.dataset.id);
if (!prodData) return;
prodEl.classList.add("--mod-init");
prodEl.innerHTML = `
<a class="product_name" href="${prodData.link}">${prodData.name}</a>
<span class="product_price">${
prodData.price?.price?.[app_shop.vars.priceType]?.formatted
}</span>
`;
});
containerEL.querySelectorAll(".product_info:not(.--mod-init)");
} catch (err) {
console.error(err);
}
}
document.body.addEventListener("click", (e) => {
if (app_shop.vars.view === 3 || app_shop.vars.view === 4) return;
const prodContainerEl = e.target.closest(".idm_picture__product");
if (!prodContainerEl) return;
if (prodContainerEl.classList.contains("--show"))
return prodContainerEl.classList.remove("--show");
const moduleContainer = prodContainerEl.closest(".idm_picture__module");
if (!moduleContainer) return;
moduleContainer
.querySelectorAll(".idm_picture__product.--show")
.forEach((prodConEl) => prodConEl.classList.remove("--show"));
prodContainerEl.classList.add("--show");
});

208
IdoSellAddOn/style.html Normal file
View File

@@ -0,0 +1,208 @@
<style>
.idm_picture__module{
--photo-prod-box-bg: #fff;
--photo-prod-box-text: #111;
--photo-prod-point-shadow: rgba(255,255,255,.5);
--photo-prod-box-pad-top: 1rem;
--photo-prod-box-pad-left: 2rem;
--photo-prod-box-width: 30rem;
--photo-prod-point-size: 24px;
--photo-prod-box-radius-br: 0px 20px 20px 20px;
--photo-prod-box-radius-bl: 20px 0px 20px 20px;
--photo-prod-box-radius-tl: 20px 20px 0px 20px;
--photo-prod-box-radius-tr: 20px 20px 20px 0px;
--photo-prod-point-desktop-top: 0%;
--photo-prod-point-desktop-left: 0%;
--photo-prod-point-tablet-top: 0%;
--photo-prod-point-tablet-left: 0%;
--photo-prod-point-mobile-top: 0%;
--photo-prod-point-mobile-left: 0%;
--photo-box-offset: 1rem;
}
.idm_picture__module{
position: relative;
}
.idm_picture__product{
position: absolute;
z-index: 10;
}
.idm_picture__img{
width: 100%;
}
.idm_picture__overlay{
width: 100%;
height: 100%;
position: absolute;
top: 0;
}
/* =========================
PRODUCT POINT ( + )
========================= */
.idm_picture__product_point{
position: relative;
width: var(--photo-prod-point-size);
height: var(--photo-prod-point-size);
border-radius: 50%;
background: var(--photo-prod-box-bg);
color: var(--photo-prod-box-text);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.6rem;
font-weight: 600;
line-height: 1;
z-index: 1;
}
/* Pulsating halo */
.idm_picture__product_point::before{
content: "";
position: absolute;
inset: 0;
border-radius: 50%;
background: var(--photo-prod-point-shadow);
animation: idmPulse 1.5s ease-in-out infinite;
z-index: -1;
}
/* Optional: stop pulse on hover */
.idm_picture__product:hover .idm_picture__product_point:before{
animation-play-state: paused;
}
/* Focus accessibility */
.idm_picture__product_point:focus-visible{
outline: 2px solid #000;
outline-offset: 4px;
}
/* =========================
PULSE ANIMATION
========================= */
@keyframes idmPulse{
0%{
opacity: 1;
box-shadow: 0 0 0px 0px var(--photo-prod-point-shadow);
}
70%{
box-shadow: 0 0 5px 10px var(--photo-prod-point-shadow);
opacity: 0.8;
}
100%{
box-shadow: 0 0 5px 10px var(--photo-prod-point-shadow);
opacity: 0;
}
}
.product_info{
background: var(--photo-prod-box-bg);
/* display: flex; */
flex-direction: column;
padding: var(--photo-prod-box-pad-top) var(--photo-prod-box-pad-left);
gap: 0.5rem;
display: none;
max-width: var(--photo-prod-box-width);
position: absolute;
width: max-content;
box-shadow: 0px 0px 10px 1px #000;
}
.product_info::before{
content: "";
position: absolute;
display: block;
width: calc(100% + var(--photo-box-offset) + var(--photo-prod-point-size));
height: calc(100% + var(--photo-box-offset) + var(--photo-prod-point-size));
z-index: -1;
}
.product_info{
bottom: var(--photo-prod-box-dir-t, auto);
top: var(--photo-prod-box-dir-b, auto);
right: var(--photo-prod-box-dir-l, auto);
left: var(--photo-prod-box-dir-r, auto);
border-radius: var(--photo-prod-box-radius);
}
.product_info::before{
top: var(--photo-prod-box-dir-t-before, auto);
bottom: var(--photo-prod-box-dir-b-before, auto);
right: var(--photo-prod-box-dir-r-before, auto);
left: var(--photo-prod-box-dir-l-before, auto);
}
.idm_picture__product .product_info .product_name{
font-size: 1.6rem;
color: var(--photo-prod-box-text);
}
.idm_picture__product .product_info .product_name:hover{
color: var(--primary-color, #000)!important;
}
.idm_picture__product .product_info .product_price{
font-size: 1.6rem;
color: var(--photo-prod-box-text);
}
@media(min-width: 979px){
.idm_picture__product:hover .product_info{
display: flex;
animation: idmShowUp 0.3s ease-in-out;
/* opacity: 1; */
}
}
@media(max-width: 978px){
.idm_picture__product.--show .product_info{
display: flex;
animation: idmShowUp 0.3s ease-in-out;
/* opacity: 1; */
}
}
@keyframes idmShowUp{
from{
opacity: 0;
}
to{
opacity: 1;
}
}
.idm_picture__product{
top: var(--photo-prod-point-mobile-top);
left: var(--photo-prod-point-mobile-left);
display: var(--photo-prod-point-mobile-display, block);
}
@media(min-width: 757px){
.idm_picture__product{
top: var(--photo-prod-point-tablet-top);
left: var(--photo-prod-point-tablet-left);
display: var(--photo-prod-point-tablet-display, block);
}
}
@media(min-width: 979px){
.idm_picture__product{
top: var(--photo-prod-point-desktop-top);
left: var(--photo-prod-point-desktop-left);
display: var(--photo-prod-point-desktop-display, block);
}
}
</style>

View File

@@ -35,3 +35,7 @@ xd przez shadow roota stylowanie komponentów z MUI nie działa w preview
poprawić rerenderowanie szczególnie inputow poprawić rerenderowanie szczególnie inputow
problem rem em w shadow root problem rem em w shadow root
Zmiana szerokości kod|preview

Binary file not shown.

Before

Width:  |  Height:  |  Size: 617 KiB

View File

@@ -1,9 +1,9 @@
import { Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import Home from "./pages/Home"; import Home from "./pages/Home";
import "./styles/index.css";
import AppLayout from "./ui/AppLayout"; import AppLayout from "./ui/AppLayout";
import Instruction from "./pages/Instruction"; import Instruction from "./pages/Instruction";
import { Toaster } from "react-hot-toast"; import { Toaster } from "react-hot-toast";
import { Alert } from "@mui/material";
function App() { function App() {
return ( return (

View File

@@ -1,7 +1,4 @@
import { BREAKPOINTS } from "./rwd";
// Constants for PhotoModule // Constants for PhotoModule
// top-left, top-right, bottom-left, bottom-right // top-left, top-right, bottom-left, bottom-right
export const DIRECTIONS = { export const DIRECTIONS = {
"t-l": "Góra Lewo", "t-l": "Góra Lewo",
@@ -15,11 +12,11 @@ export const DEFAULT_POINT = {
y: 0, y: 0,
positions: { positions: {
single: { x: 0, y: 0 }, single: { x: 0, y: 0, hide: false, direction: Object.keys(DIRECTIONS)[0] },
// ...Object.BREAKPOINTS // ...Object.BREAKPOINTS
desktop: { x: 0, y: 0 }, desktop: { x: 0, y: 0, hide: false, direction: Object.keys(DIRECTIONS)[0] },
tablet: { x: 0, y: 0 }, tablet: { x: 0, y: 0, hide: false, direction: Object.keys(DIRECTIONS)[0] },
mobile: { x: 0, y: 0 }, mobile: { x: 0, y: 0, hide: false, direction: Object.keys(DIRECTIONS)[0] },
}, },
id: 0, id: 0,

View File

@@ -15,10 +15,10 @@ export default function CodeBox() {
if (hiddenRef.current) { if (hiddenRef.current) {
setCode(hiddenRef.current.innerHTML); setCode(hiddenRef.current.innerHTML);
} }
}, [state.urls, state.urlRadioPoint, state.points, state.photoAlt]); // reactive slices }, [state.urls, state.points, state.photoAlt, state.previewMode]); // reactive slices
const copyCode = () => { const copyCode = () => {
navigator.clipboard.writeText("code"); navigator.clipboard.writeText(code);
toast.success("Skopiowano tekst!"); toast.success("Skopiowano tekst!");
}; };
@@ -31,13 +31,7 @@ export default function CodeBox() {
<GenericBox variant="outer" title="Twój Kod" canCollapse={false}> <GenericBox variant="outer" title="Twój Kod" canCollapse={false}>
{/* Hidden live component */} {/* Hidden live component */}
<div style={{ display: "none" }} ref={hiddenRef}> <div style={{ display: "none" }} ref={hiddenRef}>
<GeneratePreview <GeneratePreview preview={false} />
preview={false}
urls={state.urls}
urlRadioPoint={state.urlRadioPoint}
points={state.points}
photoAlt={state.photoAlt}
/>
</div> </div>
<div style={{ display: "flex", gap: "1rem" }}> <div style={{ display: "flex", gap: "1rem" }}>
@@ -47,7 +41,7 @@ export default function CodeBox() {
<Button variant="contained" onClick={clearCode}> <Button variant="contained" onClick={clearCode}>
Wyczyść Wyczyść
</Button> </Button>
<Button variant="contained">Wczytaj kod</Button> {/* <Button variant="contained">Wczytaj kod</Button> */}
</div> </div>
{/* Textarea showing the HTML */} {/* Textarea showing the HTML */}

View File

@@ -1,5 +1,5 @@
import styled from "@emotion/styled"; import styled from "@emotion/styled";
import { Button } from "@mui/material"; import { Button, Tab, Tabs } from "@mui/material";
import { BREAKPOINTS } from "../../../constants/rwd"; import { BREAKPOINTS } from "../../../constants/rwd";
import { useSharedState } from "../../../store/useSharedState"; import { useSharedState } from "../../../store/useSharedState";
import { capitalizeFirstLetter } from "../../../utils/capitalizeFirstLetter"; import { capitalizeFirstLetter } from "../../../utils/capitalizeFirstLetter";
@@ -15,21 +15,43 @@ function PreviewRWDTabs() {
const currentPreviewMode = useSharedState((state) => state.previewMode); const currentPreviewMode = useSharedState((state) => state.previewMode);
const setPreviewMode = useSharedState((state) => state.setPreviewMode); const setPreviewMode = useSharedState((state) => state.setPreviewMode);
if (currentPreviewMode === "single") return null;
return ( return (
<StyledPreviewTabsContainer> // <StyledPreviewTabsContainer>
{currentPreviewMode === "single" // {currentPreviewMode === "single"
? null // ? null
: Object.keys(BREAKPOINTS).map((key) => ( // : Object.keys(BREAKPOINTS).map((key) => (
<Button // <Button
key={key} // key={key}
variant="contained" // variant="contained"
disabled={currentPreviewMode === key} // disabled={currentPreviewMode === key}
onClick={() => setPreviewMode(key)} // onClick={() => setPreviewMode(key)}
> // >
{capitalizeFirstLetter(key)} // {capitalizeFirstLetter(key)}
</Button> // </Button>
))} // ))}
</StyledPreviewTabsContainer> // </StyledPreviewTabsContainer>
<Tabs
value={currentPreviewMode}
onChange={(_, newVal) => setPreviewMode(newVal)}
variant="fullWidth"
sx={(theme) => ({
backgroundColor: theme.palette.background.header,
// borderRadius: "16px 16px 0 0",
})}
>
{Object.keys(BREAKPOINTS).map((key) => (
<Tab
key={key}
value={key}
label={capitalizeFirstLetter(key)}
sx={{
color: (theme) => theme.palette.secondary.light, // inactive color
}}
/>
))}
</Tabs>
); );
} }

View File

@@ -5,13 +5,14 @@ import GeneratePreviewPoints from "./GeneratePreviewPoints";
function GeneratePreview({ preview = true }) { function GeneratePreview({ preview = true }) {
const previewMode = useSharedState((state) => state.previewMode); // const previewMode = useSharedState((state) => state.previewMode); //
const urls = useSharedState((state) => state.urls); const urls = useSharedState((state) => state.urls);
const uniqueId = Math.ceil(Math.random() * 100000 + 1);
if (preview && urls[previewMode] === "") return null; if (preview && urls[previewMode] === "") return null;
return ( return (
<div className="idm_picture__module"> <div className="idm_picture__module" id={`idm-picture-module-${uniqueId}`}>
{<GeneratePreviewImage preview={preview} urls={urls} />} {<GeneratePreviewImage preview={preview} urls={urls} />}
{<GeneratePreviewPoints preview={preview} />} {<GeneratePreviewPoints preview={preview} uniqueId={uniqueId} />}
</div> </div>
); );
} }

View File

@@ -3,16 +3,15 @@ import { useSharedState } from "../../../store/useSharedState";
function GeneratePreviewImage({ preview, urls }) { function GeneratePreviewImage({ preview, urls }) {
const imagePrefix = const imagePrefix =
import.meta.env.MODE === "development" import.meta.env.MODE === "development"
? import.meta.env.VITE_PUBLIC_URL ? `${import.meta.env.VITE_PUBLIC_URL}/`
: ""; : "";
const previewMode = useSharedState((state) => state.previewMode); const previewMode = useSharedState((state) => state.previewMode);
const urlRadioPoint = useSharedState((state) => state.urlRadioPoint);
const photoAlt = useSharedState((state) => state.photoAlt); const photoAlt = useSharedState((state) => state.photoAlt);
if (preview) if (preview)
return ( return (
<img <img
src={`${imagePrefix}/${urls[previewMode]}`} src={`${imagePrefix}${urls[previewMode]}`}
alt={photoAlt || ""} alt={photoAlt || ""}
className="idm_picture__img" className="idm_picture__img"
loading="lazy" loading="lazy"
@@ -21,18 +20,18 @@ function GeneratePreviewImage({ preview, urls }) {
return ( return (
<> <>
{urlRadioPoint === "rwd" ? ( {previewMode !== "single" ? (
<picture> <picture>
<source <source
srcSet={`${imagePrefix}/${urls.desktop}`} srcSet={`${imagePrefix}${urls.desktop}`}
media="(min-width: 979px)" media="(min-width: 979px)"
/> />
<source <source
srcSet={`${imagePrefix}/${urls.tablet}`} srcSet={`${imagePrefix}${urls.tablet}`}
media="(min-width: 757px)" media="(min-width: 757px)"
/> />
<img <img
src={`${imagePrefix}/${urls.mobile}`} src={`${imagePrefix}${urls.mobile}`}
alt={photoAlt || ""} alt={photoAlt || ""}
className="idm_picture__img" className="idm_picture__img"
loading="lazy" loading="lazy"
@@ -40,7 +39,7 @@ function GeneratePreviewImage({ preview, urls }) {
</picture> </picture>
) : ( ) : (
<img <img
src={`${imagePrefix}/${urls.single}`} src={`${imagePrefix}${urls.single}`}
alt={photoAlt || ""} alt={photoAlt || ""}
className="idm_picture__img" className="idm_picture__img"
loading="lazy" loading="lazy"

View File

@@ -2,7 +2,7 @@ import { useRef } from "react";
import { useSharedState } from "../../../store/useSharedState"; import { useSharedState } from "../../../store/useSharedState";
import GeneratePreviewSinglePoint from "./GeneratePreviewSinglePoint"; import GeneratePreviewSinglePoint from "./GeneratePreviewSinglePoint";
function GeneratePreviewPoints({ preview }) { function GeneratePreviewPoints({ preview, uniqueId }) {
const pointsLength = useSharedState((state) => state.points.length); const pointsLength = useSharedState((state) => state.points.length);
const containerRef = useRef(); const containerRef = useRef();
@@ -14,6 +14,7 @@ function GeneratePreviewPoints({ preview }) {
index={index} index={index}
preview={preview} preview={preview}
containerRef={containerRef} containerRef={containerRef}
uniqueId={uniqueId}
/> />
))} ))}
</div> </div>

View File

@@ -1,49 +1,45 @@
import { BREAKPOINTS } from "../../../constants/rwd";
import { useDragMove } from "../../../hooks/useDragMove";
import { usePrepareRWDStyle } from "../../../hooks/usePrepareRWDStyle";
import { useSharedState } from "../../../store/useSharedState"; import { useSharedState } from "../../../store/useSharedState";
// Problemy - unikalne id elementu pod SEO // Problemy - unikalne id elementu pod SEO
//
function GeneratePreviewSinglePoint({ index, preview, containerRef }) { function GeneratePreviewSinglePoint({
const { positions, id, direction } = useSharedState( index,
(state) => state.points[index] preview,
); containerRef,
uniqueId,
}) {
const { positions, id } = useSharedState((state) => state.points[index]);
const previewMode = useSharedState((state) => state.previewMode); const previewMode = useSharedState((state) => state.previewMode);
const setSinglePointPosition = useSharedState( const setSinglePointPosition = useSharedState(
(state) => state.setSinglePointPosition (state) => state.setSinglePointPosition
); // you need a setter in your store ); // you need a setter in your store
const [directionY, directionX] = direction.split("-"); const onPointerDown = useDragMove({
ref: containerRef,
x: positions[previewMode].x,
y: positions[previewMode].y,
changeXFn: (newX) => setSinglePointPosition(index, previewMode, "x", newX),
changeYFn: (newY) => setSinglePointPosition(index, previewMode, "y", newY),
});
const onPointerDown = (e) => { const prodBoxUniqueId = `prod-id-${id}-${uniqueId}`;
e.preventDefault();
const container = containerRef.current;
if (!container) return;
const rect = container.getBoundingClientRect(); const generatePointStyles = (mode) => {
const startX = e.clientX; const [dirY, dirX] = positions[mode].direction.split("-");
const startY = e.clientY; return `#${prodBoxUniqueId}{
const initialX = positions[previewMode].x; --photo-prod-box-dir-${dirY}: calc(100% + var(--photo-box-offset));
const initialY = positions[previewMode].y; --photo-prod-box-dir-${dirX}: calc(100% + var(--photo-box-offset));
const onPointerMove = (moveEvent) => { --photo-prod-box-dir-${dirY}-before: 0;
const deltaX = ((moveEvent.clientX - startX) / rect.width) * 100; --photo-prod-box-dir-${dirX}-before: 0;
const deltaY = ((moveEvent.clientY - startY) / rect.height) * 100;
--photo-prod-box-radius: var(--photo-prod-box-radius-${
// clamp between 0% and 100% dirY + dirX
const newX = Math.min(100, Math.max(0, initialX + deltaX)); });
const newY = Math.min(100, Math.max(0, initialY + deltaY)); }`;
setSinglePointPosition(index, previewMode, "x", newX);
setSinglePointPosition(index, previewMode, "y", newY);
};
const onPointerUp = () => {
window.removeEventListener("pointermove", onPointerMove);
window.removeEventListener("pointerup", onPointerUp);
};
window.addEventListener("pointermove", onPointerMove);
window.addEventListener("pointerup", onPointerUp);
}; };
if (preview) if (preview)
@@ -51,35 +47,27 @@ function GeneratePreviewSinglePoint({ index, preview, containerRef }) {
<div <div
className="idm_picture__product" className="idm_picture__product"
style={{ style={{
top: `${positions[previewMode].x}%`, top: `${positions[previewMode].y}%`,
left: `${positions[previewMode].y}%`, left: `${positions[previewMode].x}%`,
display: positions[previewMode].hide ? "none" : "block",
}} }}
> >
<button <button
className="idm_picture__product_point" className="idm_picture__product_point"
// aria-describedby="prod_id_199974" aria-describedby={prodBoxUniqueId}
aria-label="Product Info" aria-label="Product Info"
tabIndex="-1" tabIndex="-1"
onPointerDown={onPointerDown} onPointerDown={onPointerDown}
> >
+ +
</button> </button>
<div <div className="product_info" id={prodBoxUniqueId} data-id={id}>
className="product_info" <a className="product_name" href="#">
// id="prod_id_199974" Produkt {index + 1}
data-id={id} </a>
data-direction-x={directionX} <span className="product_price">XX,XX </span>
data-direction-y={directionY}
>
{preview && (
<>
<a className="product_name" href="#">
Produkt {index + 1}
</a>
<span className="product_price">XX,XX </span>
</>
)}
</div> </div>
<style>{generatePointStyles(previewMode)}</style>
</div> </div>
); );
@@ -88,42 +76,37 @@ function GeneratePreviewSinglePoint({ index, preview, containerRef }) {
className="idm_picture__product" className="idm_picture__product"
style={ style={
previewMode === "single" previewMode === "single"
? { top: positions.single.y, left: positions.single.y } ? { top: `${positions.single.y}%`, left: `${positions.single.x}%` }
: { : Object.keys(BREAKPOINTS).reduce((acc, key) => {
"--photo-prod-point-desktop-top": `${positions.desktop.y}%`, acc[`--photo-prod-point-${key}-top`] = `${positions[key].y}%`;
"--photo-prod-point-desktop-left": `${positions.desktop.x}%`, acc[`--photo-prod-point-${key}-left`] = `${positions[key].x}%`;
"--photo-prod-point-tablet-top": `${positions.tablet.y}%`, acc[`--photo-prod-point-${key}-display`] = positions[key].hide
"--photo-prod-point-tablet-left": `${positions.tablet.x}%`, ? "none"
"--photo-prod-point-mobile-top": `${positions.mobile.y}%`, : "block";
"--photo-prod-point-mobile-left": `${positions.mobile.x}%`, return acc;
} }, {})
} }
> >
<button <button
className="idm_picture__product_point" className="idm_picture__product_point"
// aria-describedby="prod_id_199974" aria-describedby={prodBoxUniqueId}
aria-label="Product Info" aria-label="Product Info"
tabIndex="-1" tabIndex="-1"
onPointerDown={onPointerDown}
> >
+ +
</button> </button>
<div <div className="product_info" id={prodBoxUniqueId} data-id={id}></div>
className="product_info" <style>
// id="prod_id_199974" {previewMode === "single" ? (
data-id={id} <style>{generatePointStyles("single")}</style>
data-direction-x={directionX} ) : (
data-direction-y={directionY} usePrepareRWDStyle({
> desktopStyle: generatePointStyles("desktop"),
{preview && ( mobileStyle: generatePointStyles("mobile"),
<> tabletStyle: generatePointStyles("tablet"),
<a className="product_name" href="#"> })
Produkt {index + 1}
</a>
<span className="product_price">XX,XX </span>
</>
)} )}
</div> </style>
</div> </div>
); );
} }

View File

@@ -13,7 +13,6 @@ function GenerateStyle() {
--photo-prod-box-width: 30em; --photo-prod-box-width: 30em;
--photo-prod-point-size: 24px; --photo-prod-point-size: 24px;
--photo-prod-box-radius: 20px;
--photo-prod-box-radius-br: 0px 20px 20px 20px; --photo-prod-box-radius-br: 0px 20px 20px 20px;
--photo-prod-box-radius-bl: 20px 0px 20px 20px; --photo-prod-box-radius-bl: 20px 0px 20px 20px;
--photo-prod-box-radius-tl: 20px 20px 0px 20px; --photo-prod-box-radius-tl: 20px 20px 0px 20px;
@@ -29,11 +28,6 @@ function GenerateStyle() {
--photo-box-offset: 1em; --photo-box-offset: 1em;
// --photo-box-offset-t: calc(-1 * var(--photo-box-offset));
// --photo-box-offset-b: calc(var(--photo-prod-point-size) + var(--photo-box-offset));
// --photo-box-offset-l: calc(var(--photo-prod-point-size) + var(--photo-box-offset));
// --photo-box-offset-r: calc(-1 * var(--photo-box-offset));
} }
.idm_picture__module{ .idm_picture__module{
@@ -129,6 +123,8 @@ PULSE ANIMATION
max-width: var(--photo-prod-box-width); max-width: var(--photo-prod-box-width);
position: absolute; position: absolute;
width: max-content; width: max-content;
box-shadow: 0px 0px 10px 1px #000;
} }
.product_info::before{ .product_info::before{
content: ""; content: "";
@@ -136,61 +132,51 @@ PULSE ANIMATION
display: block; display: block;
width: calc(100% + var(--photo-box-offset) + var(--photo-prod-point-size)); width: calc(100% + var(--photo-box-offset) + var(--photo-prod-point-size));
height: calc(100% + var(--photo-box-offset) + var(--photo-prod-point-size)); height: calc(100% + var(--photo-box-offset) + var(--photo-prod-point-size));
} z-index: -1;
.product_info[data-direction-y="t"]{
bottom: calc(100% + var(--photo-box-offset));
}
.product_info[data-direction-y="b"]{
top: calc(100% + var(--photo-box-offset));
}
.product_info[data-direction-x="l"]{
right: calc(100% + var(--photo-box-offset));
}
.product_info[data-direction-x="r"]{
left: calc(100% + var(--photo-box-offset));
} }
.product_info[data-direction-y="t"]::before{ .product_info{
top: 0; bottom: var(--photo-prod-box-dir-t, auto);
top: var(--photo-prod-box-dir-b, auto);
right: var(--photo-prod-box-dir-l, auto);
left: var(--photo-prod-box-dir-r, auto);
border-radius: var(--photo-prod-box-radius);
} }
.product_info[data-direction-y="b"]::before{ .product_info::before{
bottom: 0; top: var(--photo-prod-box-dir-t-before, auto);
} bottom: var(--photo-prod-box-dir-b-before, auto);
.product_info[data-direction-x="l"]::before{ right: var(--photo-prod-box-dir-r-before, auto);
left: 0; left: var(--photo-prod-box-dir-l-before, auto);
}
.product_info[data-direction-x="r"]::before{
right: 0;
} }
.product_info[data-direction-y="t"][data-direction-x="l"]{ .idm_picture__product .product_info .product_name{
border-radius: var(--photo-prod-box-radius-tl); font-size: 1.6em;
color: var(--photo-prod-box-text);
} }
.product_info[data-direction-y="t"][data-direction-x="r"]{ .idm_picture__product .product_info .product_name:hover{
border-radius: var(--photo-prod-box-radius-tr); color: var(--primary-color, #000)!important;
}
.product_info[data-direction-y="b"][data-direction-x="l"]{
border-radius: var(--photo-prod-box-radius-bl);
}
.product_info[data-direction-y="b"][data-direction-x="r"]{
border-radius: var(--photo-prod-box-radius-br);
} }
.product_name{ .idm_picture__product .product_info .product_price{
font-size: 1.6em; font-size: 1.6em;
color: var(--photo-prod-box-text); color: var(--photo-prod-box-text);
} }
.product_price{ @media(min-width: 979px){
font-size: 1.6em; .idm_picture__product:hover .product_info{
color: var(--photo-prod-box-text); display: flex;
animation: idmShowUp 0.3s ease-in-out;
/* opacity: 1; */
}
} }
@media(max-width: 978px){
.idm_picture__product:hover .product_info{ .idm_picture__product.--show .product_info{
display: flex; display: flex;
animation: idmShowUp 0.3s ease-in-out; animation: idmShowUp 0.3s ease-in-out;
/* opacity: 1; */ /* opacity: 1; */
}
} }
@keyframes idmShowUp{ @keyframes idmShowUp{
@@ -206,23 +192,24 @@ PULSE ANIMATION
.idm_picture__product{ .idm_picture__product{
top: var(--photo-prod-point-mobile-top); top: var(--photo-prod-point-mobile-top);
left: var(--photo-prod-point-mobile-left); left: var(--photo-prod-point-mobile-left);
display: var(--photo-prod-point-mobile-display, block);
} }
@media(min-width: 757px){ @media(min-width: 757px){
.idm_picture__product{ .idm_picture__product{
top: var(--photo-prod-point-tablet-top); top: var(--photo-prod-point-tablet-top);
left: var(--photo-prod-point-tablet-left); left: var(--photo-prod-point-tablet-left);
display: var(--photo-prod-point-tablet-display, block);
} }
} }
@media(min-width: 979px){ @media(min-width: 979px){
.idm_picture__product{ .idm_picture__product{
top: var(--photo-prod-point-desktop-top); top: var(--photo-prod-point-desktop-top);
left: var(--photo-prod-point-desktop-left); left: var(--photo-prod-point-desktop-left);
display: var(--photo-prod-point-desktop-display, block);
} }
} }
.idm_picture__product_point{ .idm_picture__product_point{
cursor: grab; cursor: grab;
} }

View File

@@ -1,5 +0,0 @@
import { useSharedState } from "../../../store/useSharedState";
export default function useRecoverCode(code) {
const state = useSharedState();
}

View File

@@ -0,0 +1,29 @@
import { Checkbox, FormControl, FormControlLabel } from "@mui/material";
import { useSharedState } from "../../store/useSharedState";
function PhotoPointCheckbox({ index }) {
const setSinglePointPosition = useSharedState(
(state) => state.setSinglePointPosition
);
const previewMode = useSharedState((state) => state.previewMode);
const hide = useSharedState(
(state) => state.points[index].positions[previewMode].hide
);
if (previewMode === "single") return null;
const handleChange = () => {
setSinglePointPosition(index, previewMode, "hide", !hide);
};
return (
<FormControl>
<FormControlLabel
control={<Checkbox onChange={handleChange} checked={hide} />}
label="Schowaj punkt dla tego widoku"
/>
</FormControl>
);
}
export default PhotoPointCheckbox;

View File

@@ -0,0 +1,35 @@
import { FormControl, InputLabel, MenuItem, Select } from "@mui/material";
import { DIRECTIONS } from "../../constants/photo";
import { useSharedState } from "../../store/useSharedState";
export default function PhotoPointDirection({ index }) {
const setSinglePointPosition = useSharedState(
(state) => state.setSinglePointPosition
);
const previewMode = useSharedState((state) => state.previewMode);
const direction = useSharedState(
(state) => state.points[index].positions[previewMode].direction
);
const handleChange = (value) => {
setSinglePointPosition(index, previewMode, "direction", value);
};
return (
<FormControl fullWidth variant="outlined">
<InputLabel id="direction-label">Kierunek</InputLabel>
<Select
labelId="direction-label"
value={direction}
label="Kierunek"
onChange={(e) => handleChange(e.target.value)}
>
{Object.keys(DIRECTIONS).map((dir) => (
<MenuItem key={dir} value={dir}>
{DIRECTIONS[dir]}
</MenuItem>
))}
</Select>
</FormControl>
);
}

View File

@@ -1,5 +1,4 @@
import styled from "@emotion/styled"; import styled from "@emotion/styled";
import { BREAKPOINTS } from "../../constants/rwd";
import { useSharedState } from "../../store/useSharedState"; import { useSharedState } from "../../store/useSharedState";
import InputField from "../../ui/InputField"; import InputField from "../../ui/InputField";
import { capitalizeFirstLetter } from "../../utils/capitalizeFirstLetter"; import { capitalizeFirstLetter } from "../../utils/capitalizeFirstLetter";
@@ -15,8 +14,14 @@ function PhotoPointPosition({ index }) {
const setSinglePointPosition = useSharedState( const setSinglePointPosition = useSharedState(
(state) => state.setSinglePointPosition (state) => state.setSinglePointPosition
); );
const urlRadioPoint = useSharedState((state) => state.urlRadioPoint); const previewMode = useSharedState((state) => state.previewMode);
const positions = useSharedState((state) => state.points[index].positions);
const posX = useSharedState(
(state) => state.points[index].positions[previewMode].x
);
const posY = useSharedState(
(state) => state.points[index].positions[previewMode].y
);
const handleChangeNumber = ({ const handleChangeNumber = ({
e, e,
@@ -35,79 +40,44 @@ function PhotoPointPosition({ index }) {
return ( return (
<div style={{ display: "flex", gap: "1.5rem", flexDirection: "column" }}> <div style={{ display: "flex", gap: "1.5rem", flexDirection: "column" }}>
{urlRadioPoint === "single" ? ( <StyledPointsContainer>
<StyledPointsContainer> <span>
<span>Położenie:</span> {previewMode === "single"
<InputField ? "Położenie"
id={`point-${index}-x`} : capitalizeFirstLetter(previewMode)}
type="number" :
name="lewo-prawo" </span>
value={positions.single.x} <InputField
onChange={(e) => id={`point-${index}-x`}
handleChangeNumber({ type="number"
e, name="lewo-prawo"
brName: "single", value={posX}
positionName: "x", onChange={(e) =>
min: 0, handleChangeNumber({
max: 100, e,
}) brName: previewMode,
} positionName: "x",
/> min: 0,
<InputField max: 100,
id={`point-${index}-y`} })
type="number" }
name="góra-dół" />
value={positions.single.y} <InputField
onChange={(e) => id={`point-${index}-y`}
handleChangeNumber({ type="number"
e, name="góra-dół"
brName: "single", value={posY}
positionName: "y", onChange={(e) =>
min: 0, handleChangeNumber({
max: 100, e,
}) brName: previewMode,
} positionName: "y",
/> min: 0,
</StyledPointsContainer> max: 100,
) : ( })
<> }
{Object.keys(BREAKPOINTS).map((brPoint) => ( />
<StyledPointsContainer key={brPoint}> </StyledPointsContainer>
<span>{capitalizeFirstLetter(brPoint)}:</span>
<InputField
id={`point-${index}-x`}
type="number"
name="lewo-prawo"
value={positions[brPoint].x}
onChange={(e) =>
handleChangeNumber({
e,
brName: brPoint,
positionName: "x",
min: 0,
max: 100,
})
}
/>
<InputField
id={`point-${index}-y`}
type="number"
name="góra-dół"
value={positions[brPoint].y}
onChange={(e) =>
handleChangeNumber({
e,
brName: brPoint,
positionName: "y",
min: 0,
max: 100,
})
}
/>
</StyledPointsContainer>
))}
</>
)}
</div> </div>
); );
} }

View File

@@ -1,14 +1,13 @@
import { MenuItem, Select, FormControl, InputLabel } from "@mui/material";
import InputField from "../../ui/InputField"; import InputField from "../../ui/InputField";
import { DIRECTIONS } from "./../../constants/photo";
import GenericBox from "../../ui/GenericBox"; import GenericBox from "../../ui/GenericBox";
import { useSharedState } from "../../store/useSharedState"; import { useSharedState } from "../../store/useSharedState";
import PhotoPointPosition from "./PhotoPointPosition"; import PhotoPointPosition from "./PhotoPointPosition";
import PhotoPointCheckbox from "./PhotoPointCheckbox";
import PhotoPointDirection from "./PhotoPointDirection";
function PhotoSinglePoint({ index }) { function PhotoSinglePoint({ index }) {
const setSinglePoint = useSharedState((state) => state.setSinglePoint); const setSinglePoint = useSharedState((state) => state.setSinglePoint);
const removeSinglePoint = useSharedState((state) => state.removeSinglePoint); const removeSinglePoint = useSharedState((state) => state.removeSinglePoint);
const direction = useSharedState((state) => state.points[index].direction);
const id = useSharedState((state) => state.points[index].id); const id = useSharedState((state) => state.points[index].id);
return ( return (
@@ -25,21 +24,8 @@ function PhotoSinglePoint({ index }) {
onChange={(e) => setSinglePoint(index, { id: e.target.value })} onChange={(e) => setSinglePoint(index, { id: e.target.value })}
/> />
<PhotoPointPosition index={index} /> <PhotoPointPosition index={index} />
<FormControl fullWidth variant="outlined"> <PhotoPointDirection index={index} />
<InputLabel id="direction-label">Kierunek</InputLabel> <PhotoPointCheckbox index={index} />
<Select
labelId="direction-label"
value={direction}
label="Kierunek"
onChange={(e) => setSinglePoint(index, { direction: e.target.value })}
>
{Object.keys(DIRECTIONS).map((dir) => (
<MenuItem key={dir} value={dir}>
{DIRECTIONS[dir]}
</MenuItem>
))}
</Select>
</FormControl>
</GenericBox> </GenericBox>
); );
} }

View File

@@ -4,24 +4,22 @@ import GenericBox from "../../ui/GenericBox";
import GenericRadioGroup from "../../ui/GenericRadioGroup"; import GenericRadioGroup from "../../ui/GenericRadioGroup";
import InputField from "../../ui/InputField"; import InputField from "../../ui/InputField";
import { URL_RADIO_DATA } from "./../../constants/photo"; import { URL_RADIO_DATA } from "./../../constants/photo";
import { useState } from "react";
function PhotoUrl() { function PhotoUrl() {
const urlRadioPoint = useSharedState((state) => state.urlRadioPoint); const previewMode = useSharedState((state) => state.previewMode);
const setUrlRadioPoint = useSharedState((state) => state.setUrlRadioPoint); const setPreviewMode = useSharedState((state) => state.setPreviewMode);
const urlSingle = useSharedState((state) => state.urls.single); const [urlPoint, setUrlPoint] = useState("single");
const urlDesktop = useSharedState((state) => state.urls.desktop);
const urlTablet = useSharedState((state) => state.urls.tablet); const url = useSharedState((state) => state.urls[previewMode]);
const urlMobile = useSharedState((state) => state.urls.mobile);
const setUrl = useSharedState((state) => state.setUrl); const setUrl = useSharedState((state) => state.setUrl);
const photoAlt = useSharedState((state) => state.photoAlt); const photoAlt = useSharedState((state) => state.photoAlt);
const setPhotoAlt = useSharedState((state) => state.setPhotoAlt); const setPhotoAlt = useSharedState((state) => state.setPhotoAlt);
const setPreviewMode = useSharedState((state) => state.setPreviewMode);
const handleUrlRadioChange = (event) => { const handleUrlRadioChange = (event) => {
setUrlRadioPoint(event.target.value); setUrlPoint(event.target.value);
setPreviewMode(event.target.value === "rwd" ? "desktop" : "single"); setPreviewMode(event.target.value === "rwd" ? "desktop" : "single");
}; };
const handleChangeURL = ({ event, type }) => { const handleChangeURL = ({ event, type }) => {
@@ -33,35 +31,17 @@ function PhotoUrl() {
<GenericBox variant="inner" title="Zdjęcie"> <GenericBox variant="inner" title="Zdjęcie">
<GenericRadioGroup <GenericRadioGroup
radioData={URL_RADIO_DATA} radioData={URL_RADIO_DATA}
value={urlRadioPoint} value={urlPoint}
onChange={handleUrlRadioChange} onChange={handleUrlRadioChange}
direction="row" direction="row"
/> />
{urlRadioPoint === "rwd" ? ( <InputField
<> name={`URL zdjęcia ${
<InputField previewMode === "single" ? "" : `na ${previewMode}`
name="URL zdjęcia desktop" }`}
onChange={(e) => handleChangeURL({ event: e, type: "desktop" })} onChange={(e) => handleChangeURL({ event: e, type: previewMode })}
value={urlDesktop} value={url}
/> />
<InputField
name="URL zdjęcia tablet"
onChange={(e) => handleChangeURL({ event: e, type: "tablet" })}
value={urlTablet}
/>
<InputField
name="URL zdjęcia mobile"
onChange={(e) => handleChangeURL({ event: e, type: "mobile" })}
value={urlMobile}
/>
</>
) : (
<InputField
name="URL zdjęcia"
onChange={(e) => handleChangeURL({ event: e, type: "single" })}
value={urlSingle}
/>
)}
<Divider /> <Divider />
<InputField <InputField
name="Alt zdjęcia" name="Alt zdjęcia"

34
src/hooks/useDragMove.js Normal file
View File

@@ -0,0 +1,34 @@
// onPointerDown
export function useDragMove({ ref, x, y, changeXFn, changeYFn }) {
return function (e) {
e.preventDefault();
const container = ref.current;
if (!container) return;
const rect = container.getBoundingClientRect();
const startX = e.clientX;
const startY = e.clientY;
const initialX = x;
const initialY = y;
const onPointerMove = (moveEvent) => {
const deltaX = ((moveEvent.clientX - startX) / rect.width) * 100;
const deltaY = ((moveEvent.clientY - startY) / rect.height) * 100;
// clamp between 0% and 100%
const newX = Math.min(100, Math.max(0, initialX + deltaX));
const newY = Math.min(100, Math.max(0, initialY + deltaY));
changeXFn(newX);
changeYFn(newY);
};
const onPointerUp = () => {
window.removeEventListener("pointermove", onPointerMove);
window.removeEventListener("pointerup", onPointerUp);
};
window.addEventListener("pointermove", onPointerMove);
window.addEventListener("pointerup", onPointerUp);
};
}

View File

@@ -0,0 +1,17 @@
import { BREAKPOINTS } from "../constants/rwd";
export function usePrepareRWDStyle({ mobileStyle, desktopStyle, tabletStyle }) {
return `
@media(max-width: ${BREAKPOINTS.tablet - 1}px){
${mobileStyle}
}
@media(min-width: ${BREAKPOINTS.tablet}px) and (max-width: ${
BREAKPOINTS.desktop - 1
}px){
${tabletStyle}
}
@media(min-width: ${BREAKPOINTS.desktop}px){
${desktopStyle}
}
`;
}

View File

@@ -1,7 +1,7 @@
function Instruction() { function Instruction() {
return ( return (
<div> <div>
Tu jest instrukcja Tu miała być instrukcja
</div> </div>
) )
} }

View File

@@ -1,10 +1,9 @@
import { create } from "zustand"; import { create } from "zustand";
import { DEFAULT_POINT, URL_RADIO_DATA } from "./../constants/photo"; import { DEFAULT_POINT } from "./../constants/photo";
// export const createBox = () => ({}); // export const createBox = () => ({});
const defaultState = { const defaultState = {
urls: { desktop: "", tablet: "", mobile: "", single: "" }, urls: { desktop: "", tablet: "", mobile: "", single: "" },
urlRadioPoint: URL_RADIO_DATA[0].value,
points: [{ ...DEFAULT_POINT }], points: [{ ...DEFAULT_POINT }],
photoAlt: "Zdjęcie pokazowe", photoAlt: "Zdjęcie pokazowe",
previewMode: "single", // desktop, tablet, mobile previewMode: "single", // desktop, tablet, mobile
@@ -19,7 +18,6 @@ export const useSharedState = create((set, get) => ({
set((state) => ({ urls: { ...state.urls, [type]: url } })), set((state) => ({ urls: { ...state.urls, [type]: url } })),
setPhotoAlt: (alt) => set({ photoAlt: alt }), setPhotoAlt: (alt) => set({ photoAlt: alt }),
setUrlRadioPoint: (value) => set({ urlRadioPoint: value }),
setPoints: (points) => set({ points }), setPoints: (points) => set({ points }),
setPointsLength: (length) => set({ pointsLength: length }), setPointsLength: (length) => set({ pointsLength: length }),
setPreviewMode: (mode) => set({ previewMode: mode }), setPreviewMode: (mode) => set({ previewMode: mode }),

View File

@@ -1 +0,0 @@