social icons + background size

This commit is contained in:
hamid zarghami
2025-08-17 11:14:29 +03:30
parent 521d53d1c8
commit 5c5df9556f
44 changed files with 218 additions and 73 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

+4 -6
View File
@@ -150,10 +150,11 @@ const UploadBoxDraggble: FC<Props> = (props: Props) => {
</div>
{
files.length > 0 || perviews ? (
(perviews && perviews.length > 0) || files.length > 0 ? (
<div className='mt-4 flex gap-4 items-center'>
{/* اگر preview از props وجود دارد، فقط previewها را نمایش بده */}
{
perviews && perviews.map((item, index) => {
perviews && perviews.length > 0 ? perviews.map((item, index) => {
return (
<div key={index} className='flex items-center gap-2'>
<div key={index} className='flex relative items-center gap-2'>
@@ -173,10 +174,7 @@ const UploadBoxDraggble: FC<Props> = (props: Props) => {
</div>
</div>
)
})
}
{
files.map((file, index) => {
}) : files.map((file, index) => {
if (props.isFile) {
return (
<div key={index} className='flex border p-2 rounded-lg items-center gap-2'>
+2 -1
View File
@@ -158,7 +158,8 @@
"icon_color": "رنگ آیکون",
"add_social": "اضافه کردن شبکه اجتماعی",
"update_social": "بروزرسانی شبکه اجتماعی",
"delete": "حذف"
"delete": "حذف",
"preview": "پیش نمایش"
},
"save": "ذخیره",
"export_html": "خروجی HTML",
@@ -19,6 +19,8 @@ const SettingSideBar: FC = () => {
const [backgroundSize, setBackgroundSize] = useState<BackgroundSize>(BackgroundSize.COVER)
const [backgroundPosition, setBackgroundPosition] = useState<BackgroundPosition>(BackgroundPosition.CENTER)
const [isUploading, setIsUploading] = useState<boolean>(false)
const [backgroundWidth, setBackgroundWidth] = useState<string>('auto')
const [backgroundHeight, setBackgroundHeight] = useState<string>('auto')
const [sectionPadding, setSectionPaddingState] = useState<string>('0px')
const [sectionHeight, setSectionHeightState] = useState<string>('123px')
const [borderWidth, setBorderWidth] = useState<string>('0px')
@@ -40,6 +42,10 @@ const SettingSideBar: FC = () => {
if (bgSize === 'auto' || !bgSize) {
if (bgRepeat === 'round') return BackgroundSize.ROUND
}
// اگر مقدار backgroundSize شامل دو مقدار باشد (مثلاً "100px 200px") آن را سفارشی در نظر بگیر
if (typeof bgSize === 'string' && /\s+/.test(bgSize)) return BackgroundSize.CUSTOM
// اگر مقدار دارای واحد باشد نیز سفارشی است
if (typeof bgSize === 'string' && /(px|%|rem|em|vh|vw)/.test(bgSize)) return BackgroundSize.CUSTOM
return BackgroundSize.COVER
}
@@ -61,6 +67,17 @@ const SettingSideBar: FC = () => {
const currentPosition = getCurrentBackgroundPosition(currentItem.style)
setBackgroundSize(currentType)
setBackgroundPosition(currentPosition)
// تنظیم مقدار اولیه عرض و ارتفاع بک‌گراند در حالت سفارشی
const bgSize = currentItem.style.backgroundSize
if (currentType === BackgroundSize.CUSTOM && typeof bgSize === 'string') {
const parts = bgSize.split(/\s+/)
setBackgroundWidth(parts[0] || 'auto')
setBackgroundHeight(parts[1] || 'auto')
} else {
setBackgroundWidth('auto')
setBackgroundHeight('auto')
}
}
// بروزرسانی section padding, height و border
@@ -179,13 +196,17 @@ const SettingSideBar: FC = () => {
if (imageUrl) {
const currentItem = data[activeSection as keyof typeof data]?.items[activeItemIndex]
const finalBackgroundSize = currentItem?.style?.backgroundSize ?? getBackgroundSize(backgroundSize)
const finalBackgroundRepeat = currentItem?.style?.backgroundRepeat ?? getBackgroundRepeat(backgroundSize)
const finalBackgroundPosition = currentItem?.style?.backgroundPosition ?? backgroundPosition
updateActiveItem({
backgroundImage: imageUrl,
style: {
...currentItem?.style,
backgroundSize: getBackgroundSize(backgroundSize),
backgroundRepeat: getBackgroundRepeat(backgroundSize),
backgroundPosition: backgroundPosition
backgroundSize: finalBackgroundSize,
backgroundRepeat: finalBackgroundRepeat,
backgroundPosition: finalBackgroundPosition
}
})
toast('تصویر پس‌زمینه با موفقیت آپلود شد', 'success')
@@ -198,13 +219,17 @@ const SettingSideBar: FC = () => {
const imageUrl = URL.createObjectURL(file[0])
const currentItem = data[activeSection as keyof typeof data]?.items[activeItemIndex]
const finalBackgroundSize = currentItem?.style?.backgroundSize ?? getBackgroundSize(backgroundSize)
const finalBackgroundRepeat = currentItem?.style?.backgroundRepeat ?? getBackgroundRepeat(backgroundSize)
const finalBackgroundPosition = currentItem?.style?.backgroundPosition ?? backgroundPosition
updateActiveItem({
backgroundImage: imageUrl,
style: {
...currentItem?.style,
backgroundSize: getBackgroundSize(backgroundSize),
backgroundRepeat: getBackgroundRepeat(backgroundSize),
backgroundPosition: backgroundPosition
backgroundSize: finalBackgroundSize,
backgroundRepeat: finalBackgroundRepeat,
backgroundPosition: finalBackgroundPosition
}
})
} finally {
@@ -232,6 +257,8 @@ const SettingSideBar: FC = () => {
return 'contain'
case BackgroundSize.ROUND:
return 'auto'
case BackgroundSize.CUSTOM:
return `${backgroundWidth || 'auto'} ${backgroundHeight || 'auto'}`
default:
return 'cover'
}
@@ -240,7 +267,8 @@ const SettingSideBar: FC = () => {
const sizeOptions = [
{ label: 'کاور', value: BackgroundSize.COVER },
{ label: 'در مقیاس', value: BackgroundSize.CONTAIN },
{ label: 'تکرار گرد', value: BackgroundSize.ROUND }
{ label: 'تکرار گرد', value: BackgroundSize.ROUND },
{ label: 'سفارشی', value: BackgroundSize.CUSTOM }
]
const positionOptions = [
@@ -336,7 +364,7 @@ const SettingSideBar: FC = () => {
<UploadBoxDraggble
label={isUploading ? 'در حال آپلود...' : t('setting.upload_background')}
onChange={handleUploadBackground}
preview={data?.[activeSection]?.items?.[0]?.backgroundImage ? [data?.[activeSection]?.items?.[0]?.backgroundImage] : undefined}
preview={data?.[activeSection]?.items?.[activeItemIndex]?.backgroundImage ? [data?.[activeSection]?.items?.[activeItemIndex]?.backgroundImage as string] : undefined}
onDelete={() => {
updateActiveItem({
backgroundImage: undefined
@@ -355,6 +383,15 @@ const SettingSideBar: FC = () => {
setBackgroundSize(newSize)
const currentItem = activeSection ? data[activeSection as keyof typeof data]?.items[activeItemIndex] : null
// اگر سفارشی انتخاب شد و هنوز مقدار width/height تعیین نشده، مقادیر پیش‌فرض را ست کن
if (newSize === BackgroundSize.CUSTOM) {
const width = backgroundWidth || 'auto'
const height = backgroundHeight || 'auto'
setBackgroundWidth(width)
setBackgroundHeight(height)
}
updateActiveItem({
style: {
...currentItem?.style,
@@ -369,6 +406,46 @@ const SettingSideBar: FC = () => {
/>
</div>
{backgroundSize === BackgroundSize.CUSTOM && (
<div className='mt-5 grid grid-cols-2 gap-3'>
<Input
label="عرض بک‌گراند"
value={backgroundWidth}
onChange={(e) => {
const value = e.target.value
setBackgroundWidth(value)
const currentItem = activeSection ? data[activeSection as keyof typeof data]?.items[activeItemIndex] : null
updateActiveItem({
style: {
...currentItem?.style,
backgroundSize: `${value || 'auto'} ${backgroundHeight || 'auto'}`
}
})
}}
placeholder="مثال: 100px یا 50%"
disabled={isUploading}
/>
<Input
label="ارتفاع بک‌گراند"
value={backgroundHeight}
onChange={(e) => {
const value = e.target.value
setBackgroundHeight(value)
const currentItem = activeSection ? data[activeSection as keyof typeof data]?.items[activeItemIndex] : null
updateActiveItem({
style: {
...currentItem?.style,
backgroundSize: `${backgroundWidth || 'auto'} ${value || 'auto'}`,
backgroundRepeat: getBackgroundRepeat(BackgroundSize.CUSTOM)
}
})
}}
placeholder="مثال: 200px یا auto"
disabled={isUploading}
/>
</div>
)}
<div className='mt-5'>
<Select
items={positionOptions}
@@ -1,5 +1,5 @@
import React, { FC } from 'react'
import { SocialType, SocialNetworkType, SocialIconSize, SocialIconStyle, ElementType, PersonalityDataType, HorizontalAlignment, VerticalAlignment, SectionName } from '../types/Types'
import { SocialType, SocialNetworkType, SocialIconSize, SocialIconStyle, ElementType, PersonalityDataType, HorizontalAlignment, VerticalAlignment, SectionName, SocialIconPack } from '../types/Types'
import { usePersonalityStore } from '../store/Store'
import { getSocialIconPath, hasPngIcon, getSocialColor as getSocialColorFromUtils, getSocialSvgIcon } from '../utils/socialIcons'
@@ -26,10 +26,10 @@ const SocialRenderer: FC<SocialRendererProps> = ({ socials, itemId, sectionKey }
})
}
const getSocialIcon = (networkType: SocialNetworkType) => {
const getSocialIcon = (networkType: SocialNetworkType, iconPack: SocialIconPack = SocialIconPack.ORIGINAL) => {
// Check if PNG icon is available, if so use it
if (hasPngIcon(networkType)) {
const iconPath = getSocialIconPath(networkType)
if (hasPngIcon(networkType, iconPack)) {
const iconPath = getSocialIconPath(networkType, iconPack)
return (
<img
src={iconPath}
@@ -149,7 +149,7 @@ const SocialRenderer: FC<SocialRendererProps> = ({ socials, itemId, sectionKey }
}}
>
<div style={getIconStyle(social)}>
{getSocialIcon(social.networkType)}
{getSocialIcon(social.networkType, social.iconPack)}
</div>
</div>
))}
@@ -1,12 +1,11 @@
import Button from '@/components/Button'
import ColorPicker from '@/components/ColorPicker'
import Input from '@/components/Input'
import Select from '@/components/Select'
import { AlignRight, AlignBottom, AlignHorizontally, AlignLeft, AlignTop, AlignVertically, Add, Edit, Trash, Link2 } from 'iconsax-react'
import React, { FC, useState, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { usePersonalityStore } from '../store/Store'
import { SocialNetworkType, SocialIconSize, SocialIconStyle, HorizontalAlignment, VerticalAlignment, ElementType } from '../types/Types'
import { SocialNetworkType, SocialIconSize, SocialIconStyle, HorizontalAlignment, VerticalAlignment, ElementType, SocialIconPack } from '../types/Types'
import { getSocialIconPath, hasPngIcon, getSocialSvgIcon } from '../utils/socialIcons'
const SocialSidebar: FC = () => {
@@ -28,6 +27,7 @@ const SocialSidebar: FC = () => {
const [color, setColor] = useState<string>('#000000')
const [horizontalAlignment, setHorizontalAlignment] = useState<HorizontalAlignment>(HorizontalAlignment.CENTER)
const [verticalAlignment, setVerticalAlignment] = useState<VerticalAlignment>(VerticalAlignment.MIDDLE)
const [iconPack, setIconPack] = useState<SocialIconPack>(SocialIconPack.ORIGINAL)
const isEditMode = selectedElement?.type === ElementType.SOCIAL;
@@ -57,6 +57,7 @@ const SocialSidebar: FC = () => {
setSize(social.size);
setStyle(social.style);
setColor(social.color || '#000000');
setIconPack(social.iconPack || SocialIconPack.ORIGINAL);
setHorizontalAlignment(social.alignment || HorizontalAlignment.CENTER);
setVerticalAlignment(social.verticalAlignment || VerticalAlignment.MIDDLE);
} else {
@@ -98,10 +99,13 @@ const SocialSidebar: FC = () => {
{ label: 'بزرگ', value: SocialIconSize.LARGE }
]
const styleOptions = [
{ label: 'دایره', value: SocialIconStyle.CIRCLE },
{ label: 'مربع', value: SocialIconStyle.SQUARE },
{ label: 'گرد', value: SocialIconStyle.ROUNDED }
const iconPackOptions = [
{ label: 'اصلی', value: SocialIconPack.ORIGINAL },
{ label: 'ست ۱', value: SocialIconPack.ICON1 },
{ label: 'ست ۲', value: SocialIconPack.ICON2 },
{ label: 'ست ۳', value: SocialIconPack.ICON3 },
]
const handleAddSocial = () => {
@@ -112,6 +116,7 @@ const SocialSidebar: FC = () => {
size,
style,
color,
iconPack,
alignment: horizontalAlignment,
verticalAlignment,
})
@@ -139,6 +144,7 @@ const SocialSidebar: FC = () => {
size,
style,
color,
iconPack,
alignment: horizontalAlignment,
verticalAlignment,
}
@@ -214,8 +220,8 @@ const SocialSidebar: FC = () => {
}
const renderIcon = () => {
if (hasPngIcon(networkType)) {
const iconPath = getSocialIconPath(networkType)
if (hasPngIcon(networkType, iconPack)) {
const iconPath = getSocialIconPath(networkType, iconPack)
return (
<img
src={iconPath}
@@ -258,6 +264,16 @@ const SocialSidebar: FC = () => {
)}
</div>
<div className='mt-5'>
<Select
label={'ست آیکون'}
items={iconPackOptions}
value={iconPack}
onChange={(e) => setIconPack(e.target.value as SocialIconPack)}
placeholder={t('setting.select')}
/>
</div>
<div className='mt-8'>
<Select
label={t('setting.social_network')}
@@ -281,6 +297,8 @@ const SocialSidebar: FC = () => {
/>
</div>
<div className='mt-5'>
<Select
label={t('setting.size')}
@@ -291,23 +309,7 @@ const SocialSidebar: FC = () => {
/>
</div>
<div className='mt-5'>
<Select
label={t('setting.icon_style')}
items={styleOptions}
value={style}
onChange={(e) => setStyle(e.target.value as SocialIconStyle)}
placeholder={t('setting.select')}
/>
</div>
<div className='mt-5'>
<ColorPicker
label={t('setting.icon_color')}
defaultColor={color}
changeColor={setColor}
/>
</div>
@@ -31,6 +31,7 @@ export enum BackgroundSize {
COVER = "cover",
CONTAIN = "contain",
ROUND = "round",
CUSTOM = "custom",
}
export enum BackgroundPosition {
@@ -87,6 +88,14 @@ export enum SocialIconStyle {
ROUNDED = "rounded",
}
// Icon pack types for social icons
export enum SocialIconPack {
ORIGINAL = "original",
ICON1 = "icon1",
ICON2 = "icon2",
ICON3 = "icon3",
}
// New type for selected element
export type SelectedElement = {
type: ElementType;
@@ -176,6 +185,7 @@ export type SocialType = {
size: SocialIconSize;
style: SocialIconStyle;
color?: string;
iconPack?: SocialIconPack; // default to ORIGINAL when not provided
alignment?: HorizontalAlignment;
verticalAlignment?: VerticalAlignment;
};
@@ -12,6 +12,7 @@ import {
SocialNetworkType,
SocialIconSize,
SocialIconStyle,
SocialIconPack,
} from "../types/Types";
import { getSortedSectionKeys } from "../store/Store";
import {
@@ -316,10 +317,13 @@ export const exportPersonalityToHTML = async (
}
};
const getSocialIcon = (networkType: SocialNetworkType) => {
const getSocialIcon = (
networkType: SocialNetworkType,
iconPack?: SocialIconPack
) => {
// Check if PNG icon is available, if so use it
if (hasPngIcon(networkType)) {
const iconPath = getSocialIconPath(networkType);
if (hasPngIcon(networkType, iconPack)) {
const iconPath = getSocialIconPath(networkType, iconPack);
return `<img src="${iconPath}" alt="${networkType} icon" style="width: 100%; height: 100%; object-fit: contain; display: block;" />`;
}
@@ -343,7 +347,7 @@ export const exportPersonalityToHTML = async (
`<a href="${social.link || "#"}" style="${getIconStyle(
social
)} text-decoration: none; display: inline-block;">
${getSocialIcon(social.networkType)}
${getSocialIcon(social.networkType, social.iconPack)}
</a>`
)
.join("")}
@@ -1,36 +1,89 @@
import React from "react";
import { SocialNetworkType } from "../types/Types";
import { SocialNetworkType, SocialIconPack } from "../types/Types";
// Map social network types to their corresponding icon file names
export const getSocialIconPath = (networkType: SocialNetworkType): string => {
const iconMap: Record<SocialNetworkType, string> = {
[SocialNetworkType.FACEBOOK]:
"https://dmail-admin.danakcorp.com/facebook.png",
[SocialNetworkType.TWITTER]:
"https://dmail-admin.danakcorp.com/twitter.png",
[SocialNetworkType.INSTAGRAM]:
"https://dmail-admin.danakcorp.com/instagram.png",
[SocialNetworkType.LINKEDIN]:
"https://dmail-admin.danakcorp.com/linkedin.png",
[SocialNetworkType.TELEGRAM]:
"https://dmail-admin.danakcorp.com/telegram.png",
[SocialNetworkType.WHATSAPP]:
"https://dmail-admin.danakcorp.com/whatsapp.png",
// For social networks without PNG icons, we'll use a default or keep SVG
[SocialNetworkType.YOUTUBE]: "", // Will fallback to SVG
[SocialNetworkType.TIKTOK]: "", // Will fallback to SVG
[SocialNetworkType.PINTEREST]: "", // Will fallback to SVG
[SocialNetworkType.SNAPCHAT]: "", // Will fallback to SVG
[SocialNetworkType.GITHUB]: "", // Will fallback to SVG
[SocialNetworkType.DISCORD]: "", // Will fallback to SVG
export const getSocialIconPath = (
networkType: SocialNetworkType,
iconPack: SocialIconPack = SocialIconPack.ORIGINAL
): string => {
// filename mapping differences between our public packs
const filenameMap: Record<SocialNetworkType, string> = {
[SocialNetworkType.FACEBOOK]: "Facebook.png",
[SocialNetworkType.TWITTER]: "X.png",
[SocialNetworkType.INSTAGRAM]: "Instagram.png",
[SocialNetworkType.LINKEDIN]: "Linkedin.png",
[SocialNetworkType.YOUTUBE]: "Youtube.png",
[SocialNetworkType.TELEGRAM]: "Telegram.png",
[SocialNetworkType.WHATSAPP]: "Whatsapp.png",
[SocialNetworkType.TIKTOK]: "TikTok.png",
[SocialNetworkType.PINTEREST]: "Pinterest.png",
[SocialNetworkType.SNAPCHAT]: "SnappChat.png",
[SocialNetworkType.GITHUB]: "GitHub.png",
[SocialNetworkType.DISCORD]: "Discord.png",
};
return iconMap[networkType] || "";
// original pack uses top-level public pngs
if (iconPack === SocialIconPack.ORIGINAL) {
const originalMap: Record<SocialNetworkType, string> = {
[SocialNetworkType.FACEBOOK]: "/facebook.png",
[SocialNetworkType.TWITTER]: "/twitter.png",
[SocialNetworkType.INSTAGRAM]: "/instagram.png",
[SocialNetworkType.LINKEDIN]: "/linkedin.png",
[SocialNetworkType.TELEGRAM]: "/telegram.png",
[SocialNetworkType.WHATSAPP]: "/whatsapp.png",
[SocialNetworkType.YOUTUBE]: "",
[SocialNetworkType.TIKTOK]: "",
[SocialNetworkType.PINTEREST]: "",
[SocialNetworkType.SNAPCHAT]: "",
[SocialNetworkType.GITHUB]: "",
[SocialNetworkType.DISCORD]: "",
};
return originalMap[networkType] || "";
}
// edited packs in public/icon1, icon2, icon3
const packFolder =
iconPack === SocialIconPack.ICON1
? "icon1"
: iconPack === SocialIconPack.ICON2
? "icon2"
: "icon3";
const filename = filenameMap[networkType];
if (!filename) return "";
// files are named like `01 Facebook.png`, etc. We can find by suffix
// but since we cannot read FS at runtime, construct deterministic path by suffix
return `/${packFolder}/${padPrefix(filename)}`;
};
// Helper to add numeric prefix "01 ", "02 ", ... based on network ordering in packs
const padPrefix = (filename: string): string => {
const order: string[] = [
"Facebook.png",
"Instagram.png",
"Linkedin.png",
"Youtube.png",
"X.png",
"TikTok.png",
"SnappChat.png",
"Pinterest.png",
"Telegram.png",
"Whatsapp.png",
"Discord.png",
"GitHub.png",
];
const index = order.indexOf(filename);
const number = index >= 0 ? String(index + 1).padStart(2, "0") : "00";
return `${number} ${filename}`;
};
// Check if a PNG icon is available for the social network
export const hasPngIcon = (networkType: SocialNetworkType): boolean => {
const iconPath = getSocialIconPath(networkType);
export const hasPngIcon = (
networkType: SocialNetworkType,
iconPack: SocialIconPack = SocialIconPack.ORIGINAL
): boolean => {
const iconPath = getSocialIconPath(networkType, iconPack);
return iconPath !== "";
};