border + height section
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user