diff --git a/src/componants/shared/productcard/CategoryCard/CategoryCard.jsx b/src/componants/shared/productcard/CategoryCard/CategoryCard.jsx new file mode 100644 index 0000000..6a01baf --- /dev/null +++ b/src/componants/shared/productcard/CategoryCard/CategoryCard.jsx @@ -0,0 +1,29 @@ +import { useTranslation } from "react-i18next"; +import { Link, useNavigate } from "react-router-dom"; +import { camelCase } from "src/Functions/helper"; +import SvgIcon from "../../MiniComponents/SvgIcon"; +import s from "./CategoryCard.module.scss"; + +const CategoryCard = ({ categoryData }) => { + const { iconName, title } = categoryData; + const categoryType = title.toLowerCase(); + const navigateTo = useNavigate(); + const { t } = useTranslation(); + const categoryTitleTrans = t(`categoriesData.${camelCase(title)}`); + + function navigateToCategory() { + navigateTo(`/category?type=${categoryType}`); + } + + return ( + + + {categoryTitleTrans} + + ); +}; +export default CategoryCard; diff --git a/src/componants/shared/productcard/CategoryCard/CategoryCard.module.scss b/src/componants/shared/productcard/CategoryCard/CategoryCard.module.scss new file mode 100644 index 0000000..790d73b --- /dev/null +++ b/src/componants/shared/productcard/CategoryCard/CategoryCard.module.scss @@ -0,0 +1,76 @@ +@import "src/Styles/mixins"; + +.card { + -webkit-tap-highlight-color: transparent; + scroll-snap-align: start; + border-radius: 4px; + border: solid 1px #0000004D; + outline: 2px solid transparent; + outline-offset: -2px; + display: flex; + flex-direction: column; + align-items: center; + padding: 20px; + transition: background-color .2s; + + &:not(:focus-visible):hover { + background-color: var(--tomato); + border-color: var(--tomato); + } + + &:focus-visible { + background-color: #db444407; + outline-color: var(--tomato); + } +} + +.card svg { + width: 56px; + height: 56px; + transition: fill .2s .1s; + + & :where(path, line) { + stroke: var(--black); + } +} + +.card svg:where([data-fill-hover], [data-stroke-hover]) { + transition: fill .2s, stroke .2s; +} + +.card:not(:focus-visible):hover svg[data-fill-hover] { + & :where(path, line) { + fill: var(--white); + } +} + +.card:not(:focus-visible):hover svg[data-stroke-hover] { + & :where(path, line) { + stroke: var(--white); + } +} + +.card:focus-visible svg[data-fill-hover] { + & :where(path, line) { + fill: var(--tomato); + } +} + +.card:focus-visible svg[data-stroke-hover] { + & :where(path, line) { + stroke: var(--tomato); + } +} + +.card span { + margin-top: 26px; + color: var(--black); +} + +.card:not(:focus-visible):hover span { + color: var(--white); +} + +.card:focus-visible span { + color: var(--tomato); +} \ No newline at end of file diff --git a/src/componants/shared/productcard/ProductCard/AddToCartButton/AddToCartButton.jsx b/src/componants/shared/productcard/ProductCard/AddToCartButton/AddToCartButton.jsx new file mode 100644 index 0000000..b1d0e65 --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/AddToCartButton/AddToCartButton.jsx @@ -0,0 +1,88 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch, useSelector } from "react-redux"; +import { showAlert } from "src/Features/alertsSlice"; +import { addToArray, removeByKeyName } from "src/Features/productsSlice"; +import { compareDataToObjValue, isItemFound } from "src/Functions/helper"; +import SvgIcon from "../../../MiniComponents/SvgIcon"; +import s from "./AddToCartButton.module.scss"; + +const AddToCartButton = ({ product }) => { + const { t } = useTranslation(); + const { cartProducts, orderProducts } = useSelector( + (state) => state.products + ); + const { + loginInfo: { isSignIn }, + } = useSelector((state) => state.user); + const isProductAlreadyExist = isItemFound(cartProducts, product, "shortName"); + const iconName = isProductAlreadyExist ? "trashCan" : "cart3"; + const [iconNameState, setIconName] = useState(iconName); + const dispatch = useDispatch(); + const buttonText = t( + `productCard.buttonText.${ + isProductAlreadyExist ? "removeFromCart" : "addToCart" + }` + ); + + function handleCartButton() { + if (!isSignIn) { + showWarning("addToCart"); + return; + } + + const isAlreadyAddedToOrder = compareDataToObjValue( + orderProducts, + product, + "shortName" + ); + + if (isAlreadyAddedToOrder) { + showWarning("productAlreadyInOrder"); + return; + } + + isProductAlreadyExist ? removeFromCart() : addToCart(); + } + + function showWarning(translateKey) { + dispatch( + showAlert({ + alertText: t(`toastAlert.${translateKey}`), + alertState: "warning", + alertType: "alert", + }) + ); + } + + function addToCart() { + const addAction = addToArray({ key: "cartProducts", value: product }); + dispatch(addAction); + setIconName("trashCan"); + } + + function removeFromCart() { + const removeAction = removeByKeyName({ + dataKey: "cartProducts", + itemKey: "shortName", + keyValue: product.shortName, + }); + + dispatch(removeAction); + setIconName("cart3"); + } + + return ( + + ); +}; +export default AddToCartButton; diff --git a/src/componants/shared/productcard/ProductCard/AddToCartButton/AddToCartButton.module.scss b/src/componants/shared/productcard/ProductCard/AddToCartButton/AddToCartButton.module.scss new file mode 100644 index 0000000..dd81cd4 --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/AddToCartButton/AddToCartButton.module.scss @@ -0,0 +1,54 @@ +.addToCartBtn { + -webkit-tap-highlight-color: transparent; + border: none; + outline: none; + position: absolute; + left: 0; + bottom: 0; + border-radius: 0 0 4px 4px; + background: var(--black); + width: 100%; + height: 40px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transform: translateY(50px); + transition: background .2s, transform .4s .1s; + + &:hover { + background-color: #303030; + } + + &:focus-visible { + transform: translateY(0); + background-color: #303030; + transition: opacity .3s, var(--outline-transition), background .2s, transform 0; + outline: 2px solid var(--yellow); + outline-offset: -2px; + color: var(--yellow); + } +} + +.addToCartBtn>svg { + fill: var(--white); + margin-inline-end: 6px; + width: 19px; + height: 19px; + transition: fill .2s .05s; +} + +.addToCartBtn:hover>svg, +.addToCartBtn:focus-visible>svg { + fill: var(--yellow); +} + +.addToCartBtn>span { + color: var(--white); + transition: color .2s .05s; +} + +.addToCartBtn:hover>span, +.addToCartBtn:focus-visible>span { + color: var(--yellow); +} \ No newline at end of file diff --git a/src/componants/shared/productcard/ProductCard/ProductCard.jsx b/src/componants/shared/productcard/ProductCard/ProductCard.jsx new file mode 100644 index 0000000..794d883 --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCard.jsx @@ -0,0 +1,99 @@ +import { useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import { checkDateBeforeMonthToPresent } from "src/Functions/helper"; +import AddToCartButton from "./AddToCartButton/AddToCartButton"; +import s from "./ProductCard.module.scss"; +import ProductCardIcons from "./ProductCardIcons/ProductCardIcons"; +import ProductCardInfo from "./ProductCardInfo/ProductCardInfo"; + +const ProductCard = ({ + product, + customization = { + stopHover: false, + showDiscount: true, + showFavIcon: true, + showDetailsIcon: true, + showRemoveIcon: false, + showNewText: false, + showWishList: true, + showColors: false, + }, + removeFrom, + loading = "eager", +}) => { + const { name, discount, img, id, addedDate } = product; + const { + stopHover, + showDiscount, + showNewText, + showFavIcon, + showDetailsIcon, + showRemoveIcon, + showWishList, + showColors, + } = customization; + const noHoverClass = stopHover ? s.noHover : ""; + const hideDiscountClass = discount <= 0 || !showDiscount ? s.hide : ""; + const hideNewClass = shouldHideNewWord(); + const { loadingProductDetails } = useSelector((state) => state.loading); + const navigateTo = useNavigate(); + const iconsData = { + showFavIcon, + showDetailsIcon, + showRemoveIcon, + showWishList, + }; + + function shouldHideNewWord() { + return checkDateBeforeMonthToPresent(addedDate) || !showNewText + ? s.hide + : ""; + } + + function navigateToProductDetails() { + if (loadingProductDetails) return; + navigateTo(`/details?product=${name.toLowerCase()}`); + } + + return ( +
+
+
+ {name} +
+ +
+ {hideNewClass && ( +
+ -{discount}% +
+ )} + +
New
+ + + +
+
+ + +
+ ); +}; +export default ProductCard; diff --git a/src/componants/shared/productcard/ProductCard/ProductCard.module.scss b/src/componants/shared/productcard/ProductCard/ProductCard.module.scss new file mode 100644 index 0000000..d046884 --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCard.module.scss @@ -0,0 +1,124 @@ +@import "src/Styles/mixins"; + +.card { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 16px; + min-width: 256px; + scroll-snap-align: start; +} + +.productImg { + border-radius: 4px; + background: var(--very-light-gray2); + width: 100%; + height: 250px; + @include center-x-y; + user-select: none; + position: relative; +} + +.imgHolder { + position: relative; + z-index: 6; + transition: .4s scale; +} + +.productImg:hover .imgHolder { + scale: 1.1; +} + +.imgHolder img { + width: 172px; + height: 152px; + object-fit: contain; + cursor: pointer; +} + +.layerContent { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; + padding: 14px; + display: flex; + justify-content: space-between; + align-items: flex-start; + overflow: hidden; +} + +.layerContent .discount { + background: var(--dark-tomato); + color: var(--secondary-white); + width: fit-content; + padding: 4px 12px; + @include center-x-y; + gap: 10px; + border-radius: 4px; + user-select: none; + font-size: .75rem; +} + +.layerContent .new { + background-color: var(--green); + color: var(--white); + width: 51px; + height: 26px; + padding: 4px 12px; + border-radius: 4px; + user-select: none; + font-size: .75rem; + + &.hide { + display: none; + } +} + +.layerContent .discount.hide { + opacity: 0; + pointer-events: none; + z-index: -1; +} + +.card:hover [data-product-icons-hover], +.card.noHover [data-product-icons-hover] { + opacity: 1; + transform: translateX(0); + transition: transform .4s .1s, opacity .2s .2s; +} + +@include small { + .card [data-product-icons-hover] { + opacity: 1; + transform: translateX(0); + transition: transform .4s .1s, opacity .2s .2s; + } +} + +.card:has(:focus-visible) [data-product-icons-hover] { + opacity: 1; + transform: translateX(0); + transition: transform 0, opacity 0; +} + +.card:hover [data-add-to-cart-button], +.card.noHover [data-add-to-cart-button] { + transform: translateY(0); +} + +@include small { + .card [data-add-to-cart-button] { + transform: translateY(0); + } +} + +// Arabic styles +[lang=ar] .card { + direction: rtl; +} + +[lang=ar] .layerContent { + direction: ltr; +} \ No newline at end of file diff --git a/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardDetailsIcon.jsx b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardDetailsIcon.jsx new file mode 100644 index 0000000..d98bd6f --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardDetailsIcon.jsx @@ -0,0 +1,27 @@ +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { detailsIconToolTipLeftPos } from "src/Functions/componentsFunctions"; +import SvgIcon from "../../../MiniComponents/SvgIcon"; +import ToolTip from "../../../MiniComponents/ToolTip"; +import s from "./ProductCardDetailsIcon.module.scss"; + +const ProductCardDetailsIcon = ({ navigateToProductDetails }) => { + const { t, i18n } = useTranslation(); + const detailsIconLeftToolTipPos = detailsIconToolTipLeftPos(i18n.language); + + return ( + + + + + ); +}; +export default ProductCardDetailsIcon; diff --git a/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardDetailsIcon.module.scss b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardDetailsIcon.module.scss new file mode 100644 index 0000000..4162686 --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardDetailsIcon.module.scss @@ -0,0 +1,37 @@ +.iconHolder { + -webkit-tap-highlight-color: transparent; + outline: none; + border: none; + width: 34px; + height: 34px; + background: var(--white); + color: var(--black); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + position: relative; + + &:focus-visible { + background-color: #303030; + transition: opacity .3s, var(--outline-transition), background .2s, transform 0; + outline: 2px solid var(--yellow); + outline-offset: -2px; + } +} + +.iconHolder svg { + font-size: .9rem; + transition: fill .2s; + width: 16px; + height: 16px; +} + +.iconHolder:focus-visible svg { + fill: var(--yellow); +} + +.detailsIcon:not(:focus-visible):hover svg { + fill: #6f6f6f; +} \ No newline at end of file diff --git a/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardFavIcon.jsx b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardFavIcon.jsx new file mode 100644 index 0000000..015b9c6 --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardFavIcon.jsx @@ -0,0 +1,72 @@ +import { useTranslation } from "react-i18next"; +import { useDispatch, useSelector } from "react-redux"; +import { showAlert } from "src/Features/alertsSlice"; +import { addToArray, removeById } from "src/Features/productsSlice"; +import { favIconToolTipLeftPos } from "src/Functions/componentsFunctions"; +import { isItemFound } from "src/Functions/helper"; +import SvgIcon from "../../../MiniComponents/SvgIcon"; +import ToolTip from "../../../MiniComponents/ToolTip"; +import s from "./ProductCardFavIcon.module.scss"; + +const ProductCardFavIcon = ({ product, productId }) => { + const { + loginInfo: { isSignIn }, + } = useSelector((state) => state.user); + const { favoritesProducts } = useSelector((state) => state.products); + const { t, i18n } = useTranslation(); + const dispatch = useDispatch(); + const favIconLeftToolTipPos = favIconToolTipLeftPos(i18n.language); + const isAddedToFavorites = favoritesProducts?.find( + (favProduct) => favProduct.id === productId + ); + const activeClass = isAddedToFavorites ? s.active : ""; + + function addProductToFavorite() { + if (!isSignIn) { + dispatch( + showAlert({ + alertText: t("toastAlert.addToFavorite"), + alertState: "warning", + alertType: "alert", + }) + ); + return; + } + + const isProductAlreadyExist = isItemFound(favoritesProducts, product, "id"); + + if (isProductAlreadyExist) { + removeProduct(); + return; + } + + addProduct(); + } + + function removeProduct() { + dispatch(removeById({ key: "favoritesProducts", id: product.id })); + } + + function addProduct() { + dispatch(addToArray({ key: "favoritesProducts", value: product })); + } + + return ( + + ); +}; +export default ProductCardFavIcon; diff --git a/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardFavIcon.module.scss b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardFavIcon.module.scss new file mode 100644 index 0000000..7b880a4 --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardFavIcon.module.scss @@ -0,0 +1,81 @@ +.iconHolder { + -webkit-tap-highlight-color: transparent; + outline: none; + border: none; + width: 34px; + height: 34px; + background: var(--white); + color: var(--black); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + position: relative; + + &:focus-visible { + background-color: #303030; + transition: opacity .3s, var(--outline-transition), background .2s, transform 0; + outline: 2px solid var(--yellow); + outline-offset: -2px; + } +} + +.iconHolder svg { + font-size: .9rem; + transition: fill .2s; + width: 16px; + height: 16px; +} + +.iconHolder:focus-visible svg { + fill: var(--yellow); +} + +.favIcon { + --heart-color: var(--tomato); + + &:focus-visible { + --heart-color: var(--yellow); + } +} + +.favIcon.active { + fill: var(--tomato); + + & .heartBackground { + opacity: 1; + } +} + +.favIcon .heartBackground { + position: absolute; + top: 17px; + right: calc(50% + -0.4px); + translate: 50% 0; + border-style: solid; + border-color: var(--heart-color) transparent transparent transparent; + border-width: 7px; + opacity: 0; + pointer-events: none; + scale: 1.03; +} + +.favIcon .heartBackground::before, +.favIcon .heartBackground::after { + content: ''; + position: absolute; + top: -12px; + background-color: var(--heart-color); + width: 8px; + height: 8px; + border-radius: 50%; +} + +.favIcon .heartBackground::before { + left: -7.5px; +} + +.favIcon .heartBackground::after { + right: -7px; +} \ No newline at end of file diff --git a/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardIcons.jsx b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardIcons.jsx new file mode 100644 index 0000000..5799aa1 --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardIcons.jsx @@ -0,0 +1,36 @@ +import ProductCardDetailsIcon from "./ProductCardDetailsIcon"; +import ProductCardFavIcon from "./ProductCardFavIcon"; +import s from "./ProductCardIcons.module.scss"; +import ProductCardRemoveIcon from "./ProductCardRemoveIcon"; +import ProductCardWishListIcon from "./ProductCardWishListIcon"; + +const ProductCardIcons = ({ + iconsData: { showFavIcon, showDetailsIcon, showRemoveIcon, showWishList }, + productId, + navigateToProductDetails, + product, + removeFrom, +}) => { + return ( +
+ {showFavIcon && ( + + )} + + {showDetailsIcon && ( + + )} + + {showRemoveIcon && ( + + )} + + {showWishList && ( + + )} +
+ ); +}; +export default ProductCardIcons; diff --git a/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardIcons.module.scss b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardIcons.module.scss new file mode 100644 index 0000000..e18ac16 --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardIcons.module.scss @@ -0,0 +1,11 @@ +@import "src/Styles/mixins.scss"; + +.icons { + display: flex; + flex-direction: column; + gap: 8px; + position: relative; + z-index: 10; + opacity: 0; + transform: translateX(50px); +} \ No newline at end of file diff --git a/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardRemoveIcon.jsx b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardRemoveIcon.jsx new file mode 100644 index 0000000..a3e69fa --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardRemoveIcon.jsx @@ -0,0 +1,34 @@ +import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; +import { removeById } from "src/Features/productsSlice"; +import { trashcanIconToolTipLeftPos } from "src/Functions/componentsFunctions"; +import SvgIcon from "../../../MiniComponents/SvgIcon"; +import ToolTip from "../../../MiniComponents/ToolTip"; +import s from "./ProductCardRemoveIcon.module.scss"; + +const ProductCardRemoveIcon = ({ removeFrom, productId }) => { + const { t, i18n } = useTranslation(); + const dispatch = useDispatch(); + const trashcanIconLeftToolTipPos = trashcanIconToolTipLeftPos(i18n.language); + + function removeProduct() { + dispatch(removeById({ key: removeFrom, id: productId })); + } + + return ( + + ); +}; +export default ProductCardRemoveIcon; diff --git a/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardRemoveIcon.module.scss b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardRemoveIcon.module.scss new file mode 100644 index 0000000..9fee8e5 --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardRemoveIcon.module.scss @@ -0,0 +1,37 @@ +.iconHolder { + -webkit-tap-highlight-color: transparent; + outline: none; + border: none; + width: 34px; + height: 34px; + background: var(--white); + color: var(--black); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + position: relative; + + &:focus-visible { + background-color: #303030; + transition: opacity .3s, var(--outline-transition), background .2s, transform 0; + outline: 2px solid var(--yellow); + outline-offset: -2px; + } +} + +.iconHolder svg { + font-size: .9rem; + transition: fill .2s; + width: 16px; + height: 16px; +} + +.iconHolder:focus-visible svg { + fill: var(--yellow); +} + +.removeIcon:hover svg { + fill: var(--tomato); +} \ No newline at end of file diff --git a/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardWishListIcon.jsx b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardWishListIcon.jsx new file mode 100644 index 0000000..738bbd2 --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardWishListIcon.jsx @@ -0,0 +1,70 @@ +import { useTranslation } from "react-i18next"; +import { useDispatch, useSelector } from "react-redux"; +import { showAlert } from "src/Features/alertsSlice"; +import { addToArray, removeById } from "src/Features/productsSlice"; +import { wishlistIconToolTipLeftPos } from "src/Functions/componentsFunctions"; +import { isItemFound } from "src/Functions/helper"; +import SvgIcon from "../../../MiniComponents/SvgIcon"; +import ToolTip from "../../../MiniComponents/ToolTip"; +import s from "./ProductCardWishListIcon.module.scss"; + +const ProductCardWishListIcon = ({ product, productId }) => { + const { + loginInfo: { isSignIn }, + } = useSelector((state) => state.user); + const { wishList } = useSelector((state) => state.products); + const { t, i18n } = useTranslation(); + const dispatch = useDispatch(); + const wishlistIconLeftToolTipPos = wishlistIconToolTipLeftPos(i18n.language); + const isAddedToWishList = wishList?.find( + (wishProduct) => wishProduct.id === productId + ); + const activeClass = isAddedToWishList ? s.active : ""; + + function addProductToWishList() { + if (!isSignIn) { + dispatch( + showAlert({ + alertText: t("toastAlert.addToWishList"), + alertState: "warning", + alertType: "alert", + }) + ); + return; + } + + const isProductAlreadyExist = isItemFound(wishList, product, "id"); + + if (isProductAlreadyExist) { + removeProduct(); + return; + } + + addProduct(); + } + + function removeProduct() { + dispatch(removeById({ key: "wishList", id: product.id })); + } + + function addProduct() { + dispatch(addToArray({ key: "wishList", value: product })); + } + + return ( + + ); +}; +export default ProductCardWishListIcon; diff --git a/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardWishListIcon.module.scss b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardWishListIcon.module.scss new file mode 100644 index 0000000..799022a --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCardIcons/ProductCardWishListIcon.module.scss @@ -0,0 +1,45 @@ +.iconHolder { + -webkit-tap-highlight-color: transparent; + outline: none; + border: none; + width: 34px; + height: 34px; + background: var(--white); + color: var(--black); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + position: relative; + + &:focus-visible { + background-color: #303030; + transition: opacity .3s, var(--outline-transition), background .2s, transform 0; + outline: 2px solid var(--yellow); + outline-offset: -2px; + } +} + +.iconHolder svg { + font-size: .9rem; + transition: fill .2s; + width: 16px; + height: 16px; +} + +.iconHolder:focus-visible svg { + fill: var(--yellow); +} + +.wishListIcon svg { + fill: var(--black); +} + +.wishListIcon:not(:focus-visible):hover svg { + fill: #6f6f6f; +} + +.wishListIcon.active svg path { + fill-rule: nonzero; +} \ No newline at end of file diff --git a/src/componants/shared/productcard/ProductCard/ProductCardInfo/ProductCardInfo.jsx b/src/componants/shared/productcard/ProductCard/ProductCardInfo/ProductCardInfo.jsx new file mode 100644 index 0000000..3be50ee --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCardInfo/ProductCardInfo.jsx @@ -0,0 +1,44 @@ +import { useTranslation } from "react-i18next"; +import { translateProduct } from "../../../../Cart/CartProducts/CartProduct"; +import RateStars from "../../../MidComponents/RateStars/RateStars"; +import ProductColors from "../../../MiniComponents/ProductColors/ProductColors"; +import s from "./ProductCardInfo.module.scss"; + +const ProductCardInfo = ({ product, showColors, navigateToProductDetails }) => { + const { shortName, price, discount, afterDiscount, rate, votes, colors } = + product; + const { t } = useTranslation(); + + const translatedProductName = translateProduct({ + productName: shortName, + translateMethod: t, + translateKey: "shortName", + }); + + return ( +
+ + navigateToProductDetails()}> + {translatedProductName} + + + +
+ ${afterDiscount} + {discount > 0 && ${price}} +
+ +
+ + ({votes}) +
+ + {showColors && ( +
+ +
+ )} +
+ ); +}; +export default ProductCardInfo; diff --git a/src/componants/shared/productcard/ProductCard/ProductCardInfo/ProductCardInfo.module.scss b/src/componants/shared/productcard/ProductCard/ProductCardInfo/ProductCardInfo.module.scss new file mode 100644 index 0000000..8d4626a --- /dev/null +++ b/src/componants/shared/productcard/ProductCard/ProductCardInfo/ProductCardInfo.module.scss @@ -0,0 +1,63 @@ +@import "src/Styles/mixins"; + +.productInfo { + display: flex; + flex-direction: column; + gap: 8px; +} + +.productInfo .productName a { + outline: none; + border-bottom: solid 2px var(--website-bg); + font-weight: 500; + color: var(--black); + transition: color .2s; + + &:hover { + color: var(--brown); + } + + &:focus { + border-bottom: solid 2px var(--orange-degree2); + color: var(--orange-degree2); + } +} + +.productInfo .price { + color: var(--dark-tomato); + font-weight: 500; +} + +.productInfo .price .afterDiscount { + // margin-inline-start: 16px; + margin-left: 16px; + color: var(--primary); + font-weight: 500; +} + +.productInfo .rateContainer { + display: flex; + align-items: center; + gap: 8px; + user-select: none; +} + +.productInfo .rateContainer .numOfVotes { + font-weight: 600; + font-size: .875rem; + color: var(--primary); +} + +.productInfo .colors { + display: flex; + align-items: center; + margin-bottom: 18px; + gap: 15px; + margin-top: 10px; + margin-inline-start: 3px; +} + +// Arabic styles +[lang=ar] .productInfo { + direction: rtl; +} \ No newline at end of file