border + height section

This commit is contained in:
hamid zarghami
2025-08-06 08:49:11 +03:30
parent dcd5ab07e0
commit a47e10402c
6 changed files with 291 additions and 22 deletions
@@ -11,7 +11,7 @@ import { toast } from '@/components/Toast'
const Personality: FC = () => {
const { id } = useParams()
const { setData, data, addSection, getSortedSectionKeys } = usePersonalityStore()
const { setData, addSection, getSortedSectionKeys } = usePersonalityStore()
const { data: template } = useGetTemplate(id || '')
useEffect(() => {
@@ -63,8 +63,6 @@ const Personality: FC = () => {
)
}
console.log('data', data);
const sortedSectionKeys = getSortedSectionKeys();
@@ -15,6 +15,37 @@ const GenericSection: FC<GenericSectionProps> = ({ sectionKey }) => {
const { activeSection, activeItemIndex, data, setActiveSection, setActiveItemIndex } = usePersonalityStore()
// Helper function to get numeric height
const getNumericHeight = (height?: string) => {
return parseInt(height?.replace(/px|em|rem|%/g, '') || "123")
}
// Helper function to generate border style object based on selected sides
const generateBorderStyle = (border?: { width?: string; color?: string; style?: string; sides?: string[] }) => {
if (!border || border.style === 'none') {
return {}
}
const { width = '0px', color = '#E5E7EB', style = 'solid', sides = [] } = border
// اگر width صفر باشد یا تعریف نشده باشد، border نمایش نده
if (!width || width === '0px') {
return {}
}
if (sides.length === 0 || sides.length === 4) {
return { border: `${width} ${style} ${color}` }
}
// Generate individual border sides
const borderStyle: any = {}
sides.forEach(side => {
borderStyle[`border${side.charAt(0).toUpperCase() + side.slice(1)}`] = `${width} ${style} ${color}`
})
return borderStyle
}
// Early return if data is not available
if (!data || !data[sectionKey]) {
return (
@@ -95,9 +126,30 @@ const GenericSection: FC<GenericSectionProps> = ({ sectionKey }) => {
sectionData.columnsCount > 0 ?
<td style={{
padding: '10px',
border: activeSection === sectionKey ? '1px dashed #0038FF' : '1px dashed #E5E7EB',
...(() => {
const borderStyle = generateBorderStyle(sectionData.border)
if (activeSection === sectionKey) {
// اگر سکشن انتخاب شده است، border آبی + border سکشن را نمایش بده
if (Object.keys(borderStyle).length === 0) {
return { border: '1px dashed #0038FF' }
} else {
return {
...borderStyle,
outline: '1px dashed #0038FF',
outlineOffset: '2px'
}
}
} else {
// اگر سکشن انتخاب نشده، فقط border سکشن یا border پیش‌فرض
if (Object.keys(borderStyle).length === 0) {
return { border: '1px dashed #E5E7EB' }
}
return borderStyle
}
})(),
borderRadius: '24px',
cursor: 'pointer'
cursor: 'pointer',
height: sectionData.height || '123px'
}}>
<table width="100%" cellPadding="0" cellSpacing="0" border={0} style={{ tableLayout: 'fixed' }}>
<tbody>
@@ -114,7 +166,7 @@ const GenericSection: FC<GenericSectionProps> = ({ sectionKey }) => {
onClick={(e) => handleItemClick(index, e)}
style={{
width: `${100 / sectionData.columnsCount}%`,
height: '123px',
height: sectionData.height || '123px',
border: isActive ? '1px dashed #0038FF' : '1px dashed #EAECF4',
backgroundColor: item?.backgroundColor || '#ffffff',
backgroundImage: item?.backgroundImage ? `url(${item.backgroundImage})` : 'none',
@@ -130,7 +182,7 @@ const GenericSection: FC<GenericSectionProps> = ({ sectionKey }) => {
>
<table width="100%" cellPadding="0" cellSpacing="0" border={0} style={{
borderCollapse: 'collapse',
height: '123px'
height: sectionData.height || '123px'
}}>
<tbody>
{/* اگر column خالی است */}
@@ -141,7 +193,7 @@ const GenericSection: FC<GenericSectionProps> = ({ sectionKey }) => {
<tr>
<td
width="100%"
height="123"
height={getNumericHeight(sectionData.height).toString()}
style={{
padding: '8px',
cursor: 'pointer'
@@ -158,7 +210,7 @@ const GenericSection: FC<GenericSectionProps> = ({ sectionKey }) => {
<tr>
<td
width="100%"
height="123"
height={getNumericHeight(sectionData.height).toString()}
align={getButtonHorizontalAlign(item.buttons[0])}
valign={getButtonVerticalAlign(item.buttons[0])}
style={{
@@ -178,7 +230,9 @@ const GenericSection: FC<GenericSectionProps> = ({ sectionKey }) => {
<tr>
<td
width="100%"
height={item?.buttons && item.buttons.length > 0 ? "60" : "87"}
height={item?.buttons && item.buttons.length > 0 ?
Math.floor(getNumericHeight(sectionData.height) * 0.6).toString() :
Math.floor(getNumericHeight(sectionData.height) * 0.8).toString()}
align={item?.texts?.[0]?.alignment || 'center'}
valign={item?.texts?.[0]?.verticalAlignment === VerticalAlignment.TOP ? 'top' :
item?.texts?.[0]?.verticalAlignment === VerticalAlignment.BOTTOM ? 'bottom' : 'middle'}
@@ -210,7 +264,7 @@ const GenericSection: FC<GenericSectionProps> = ({ sectionKey }) => {
<tr>
<td
width="100%"
height="27"
height={Math.floor(getNumericHeight(sectionData.height) * 0.2).toString()}
align={getButtonHorizontalAlign(item.buttons[0])}
valign={getButtonVerticalAlign(item.buttons[0])}
style={{
@@ -247,8 +301,28 @@ const GenericSection: FC<GenericSectionProps> = ({ sectionKey }) => {
<td
onClick={(e) => handleSectionClick(sectionKey, e)}
style={{
height: '123px',
border: activeSection === sectionKey ? '1px dashed #0038FF' : '1px dashed #E5E7EB',
height: sectionData.height || '123px',
...(() => {
const borderStyle = generateBorderStyle(sectionData.border)
if (activeSection === sectionKey) {
// اگر سکشن انتخاب شده است، border آبی + border سکشن را نمایش بده
if (Object.keys(borderStyle).length === 0) {
return { border: '1px dashed #0038FF' }
} else {
return {
...borderStyle,
outline: '1px dashed #0038FF',
outlineOffset: '2px'
}
}
} else {
// اگر سکشن انتخاب نشده، فقط border سکشن یا border پیش‌فرض
if (Object.keys(borderStyle).length === 0) {
return { border: '1px dashed #E5E7EB' }
}
return borderStyle
}
})(),
borderRadius: '24px',
textAlign: 'center',
verticalAlign: 'middle',
@@ -82,13 +82,13 @@ const ImageSideBar: FC = () => {
}, [selectedElement, isEditMode, data]);
const sizeOptions = [
{ label: 'کاور - تمام فضا را پوشش دهد (Cover)', value: ImageSize.COVER },
{ label: 'در مقیاس - در فضا جا شود (Contain)', value: ImageSize.CONTAIN },
{ label: 'تکرار - عکس تکرار شود (Repeat)', value: ImageSize.REPEAT },
{ label: 'تکرار افقی (Repeat-X)', value: ImageSize.REPEAT_X },
{ label: 'تکرار عمودی (Repeat-Y)', value: ImageSize.REPEAT_Y },
{ label: 'بدون تکرار (No-Repeat)', value: ImageSize.NO_REPEAT },
{ label: 'اندازه اصلی (Auto)', value: ImageSize.AUTO }
{ label: 'کاور ', value: ImageSize.COVER },
{ label: 'در مقیاس', value: ImageSize.CONTAIN },
{ label: 'تکرار', value: ImageSize.REPEAT },
{ label: 'تکرار افقی', value: ImageSize.REPEAT_X },
{ label: 'تکرار عمودی', value: ImageSize.REPEAT_Y },
{ label: 'بدون تکرار', value: ImageSize.NO_REPEAT },
{ label: 'اندازه اصلی', value: ImageSize.AUTO }
]
const handleImageUpload = async (files: File[]) => {
@@ -5,6 +5,8 @@ import ColorPicker from '@/components/ColorPicker';
import { usePersonalityStore } from '../store/Store';
import { SectionItemType, BackgroundSize, BackgroundPosition } from '../types/Types';
import Select from '@/components/Select';
import Input from '@/components/Input';
import { Checkbox } from '@/components/ui/checkbox';
import { useSingleUpload } from '../hooks/usePersonalityData';
import { toast } from '@/components/Toast';
import Button from '@/components/Button';
@@ -12,11 +14,16 @@ import { Trash } from 'iconsax-react';
const SettingSideBar: FC = () => {
const { t } = useTranslation()
const { activeSection, setColumnsCount, updateActiveItem, data, activeItemIndex, removeSection } = usePersonalityStore()
const { activeSection, setColumnsCount, updateActiveItem, data, activeItemIndex, removeSection, setSectionHeight, setSectionBorder } = usePersonalityStore()
const [color, setColor] = useState("#aabbcc");
const [backgroundSize, setBackgroundSize] = useState<BackgroundSize>(BackgroundSize.COVER)
const [backgroundPosition, setBackgroundPosition] = useState<BackgroundPosition>(BackgroundPosition.CENTER)
const [isUploading, setIsUploading] = useState<boolean>(false)
const [sectionHeight, setSectionHeightState] = useState<string>('123px')
const [borderWidth, setBorderWidth] = useState<string>('0px')
const [borderColor, setBorderColor] = useState<string>('#E5E7EB')
const [borderStyle, setBorderStyle] = useState<'solid' | 'dashed' | 'dotted' | 'none'>('solid')
const [borderSides, setBorderSides] = useState<('top' | 'bottom' | 'left' | 'right')[]>(['top', 'bottom', 'left', 'right'])
const { mutateAsync: singleUpload } = useSingleUpload()
@@ -44,13 +51,23 @@ const SettingSideBar: FC = () => {
// به‌روزرسانی state وقتی activeSection یا activeItem تغییر می‌کند
useEffect(() => {
if (activeSection && data[activeSection as keyof typeof data]) {
const currentItem = data[activeSection as keyof typeof data]?.items[activeItemIndex]
const currentSection = data[activeSection as keyof typeof data]
const currentItem = currentSection?.items[activeItemIndex]
// بروزرسانی background settings
if (currentItem?.style) {
const currentType = getCurrentBackgroundSizeType(currentItem.style)
const currentPosition = getCurrentBackgroundPosition(currentItem.style)
setBackgroundSize(currentType)
setBackgroundPosition(currentPosition)
}
// بروزرسانی section height و border
setSectionHeightState(currentSection?.height || '123px')
setBorderWidth(currentSection?.border?.width || '0px')
setBorderColor(currentSection?.border?.color || '#E5E7EB')
setBorderStyle(currentSection?.border?.style || 'solid')
setBorderSides(currentSection?.border?.sides || ['top', 'bottom', 'left', 'right'])
}
}, [activeSection, activeItemIndex, data])
@@ -80,6 +97,65 @@ const SettingSideBar: FC = () => {
updateActiveItem({ backgroundColor: color })
}
const handleSectionHeightChange = (height: string) => {
if (activeSection) {
setSectionHeightState(height)
setSectionHeight(activeSection, height)
}
}
const handleBorderWidthChange = (width: string) => {
if (activeSection) {
setBorderWidth(width)
setSectionBorder(activeSection, {
width,
color: borderColor,
style: borderStyle,
sides: borderSides
})
}
}
const handleBorderColorChange = (color: string) => {
if (activeSection) {
setBorderColor(color)
setSectionBorder(activeSection, {
width: borderWidth,
color,
style: borderStyle,
sides: borderSides
})
}
}
const handleBorderStyleChange = (style: 'solid' | 'dashed' | 'dotted' | 'none') => {
if (activeSection) {
setBorderStyle(style)
setSectionBorder(activeSection, {
width: borderWidth,
color: borderColor,
style,
sides: borderSides
})
}
}
const handleBorderSidesChange = (side: 'top' | 'bottom' | 'left' | 'right') => {
if (activeSection) {
const newSides = borderSides.includes(side)
? borderSides.filter(s => s !== side)
: [...borderSides, side]
setBorderSides(newSides)
setSectionBorder(activeSection, {
width: borderWidth,
color: borderColor,
style: borderStyle,
sides: newSides
})
}
}
const handleUploadBackground = async (file: File[]) => {
if (activeSection && file.length > 0) {
setIsUploading(true)
@@ -166,6 +242,13 @@ const SettingSideBar: FC = () => {
{ label: 'پایین راست', value: BackgroundPosition.BOTTOM_RIGHT }
]
const borderStyleOptions = [
{ label: 'ساده', value: 'solid' },
{ label: 'خط‌چین', value: 'dashed' },
{ label: 'نقطه‌چین', value: 'dotted' },
{ label: 'بدون حاشیه', value: 'none' }
]
if (!activeSection) {
return (
<div className='text-center text-gray-500 mt-8'>
@@ -294,6 +377,73 @@ const SettingSideBar: FC = () => {
disabled={isUploading}
/>
</div>
{/* Section Height Control */}
<div className='mt-5'>
<Input
label="ارتفاع سکشن"
value={sectionHeight}
onChange={(e) => handleSectionHeightChange(e.target.value)}
placeholder="مثال: 123px"
disabled={isUploading}
/>
</div>
{/* Border Controls */}
<div className='mt-5 border-t border-gray-200 pt-5'>
<div className='text-sm font-medium mb-3'>تنظیمات حاشیه</div>
<div className='mt-3'>
<Input
label="ضخامت حاشیه"
value={borderWidth}
onChange={(e) => handleBorderWidthChange(e.target.value)}
placeholder="مثال: 1px"
disabled={isUploading}
/>
</div>
<div className='mt-3'>
<ColorPicker
label="رنگ حاشیه"
defaultColor={borderColor}
changeColor={handleBorderColorChange}
/>
</div>
<div className='mt-3'>
<Select
items={borderStyleOptions}
label="نوع حاشیه"
value={borderStyle}
onChange={(e) => handleBorderStyleChange(e.target.value as 'solid' | 'dashed' | 'dotted' | 'none')}
placeholder="انتخاب نوع حاشیه"
disabled={isUploading}
/>
</div>
<div className='mt-3'>
<div className='text-sm font-medium mb-2'>جهات حاشیه</div>
<div className='grid grid-cols-2 gap-2'>
{[
{ value: 'top', label: 'بالا' },
{ value: 'bottom', label: 'پایین' },
{ value: 'left', label: 'چپ' },
{ value: 'right', label: 'راست' }
].map(side => (
<div key={side.value} className='flex items-center space-x-2 rtl:space-x-reverse'>
<Checkbox
id={`border-${side.value}`}
checked={borderSides.includes(side.value as 'top' | 'bottom' | 'left' | 'right')}
onCheckedChange={() => handleBorderSidesChange(side.value as 'top' | 'bottom' | 'left' | 'right')}
disabled={isUploading}
/>
<label htmlFor={`border-${side.value}`} className='text-sm'>{side.label}</label>
</div>
))}
</div>
</div>
</div>
</div>
)
}
@@ -273,6 +273,36 @@ export const usePersonalityStore = create<PersonalityStore>((set, get) => ({
};
}),
setSectionHeight: (sectionKey: string, height: string) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
height,
},
},
})),
setSectionBorder: (
sectionKey: string,
border: {
width?: string;
color?: string;
style?: "solid" | "dashed" | "dotted" | "none";
sides?: ("top" | "bottom" | "left" | "right")[];
}
) =>
set((state) => ({
data: {
...state.data,
[sectionKey]: {
...state.data[sectionKey],
border,
},
},
})),
setItems: (sectionKey: string, items: SectionItemType[]) =>
set((state) => ({
data: {
@@ -103,6 +103,13 @@ export type SectionType = {
columnsCount: number;
sortNumber: number;
items: SectionItemType[];
height?: string;
border?: {
width?: string;
color?: string;
style?: "solid" | "dashed" | "dotted" | "none";
sides?: ("top" | "bottom" | "left" | "right")[];
};
};
export type SectionItemType = {
@@ -200,6 +207,16 @@ export type PersonalityStore = {
updateActiveItem: (updates: Partial<SectionItemType>) => void;
removeItem: (sectionKey: string, id: string) => void;
setColumnsCount: (sectionKey: string, columnsCount: number) => void;
setSectionHeight: (sectionKey: string, height: string) => void;
setSectionBorder: (
sectionKey: string,
border: {
width?: string;
color?: string;
style?: "solid" | "dashed" | "dotted" | "none";
sides?: ("top" | "bottom" | "left" | "right")[];
}
) => void;
addText: (
sectionKey: string,
itemId: string,