лучшен визуальный стиль выбранных кнопок: добавлена толстая обводка, эффект приподнятия и сохранение увеличения иконки

This commit is contained in:
kazachilo 2025-03-17 17:14:44 +03:00
parent 12737caa1c
commit 0e586e693a
6 changed files with 107 additions and 47 deletions

View File

@ -10,19 +10,20 @@ import StepTitle from './StepTitle';
interface BlockRendererProps {
block: Block;
onAction?: (actionType: string, actionValue: string, blockId?: string) => void;
onAction?: (actionType: string, actionValue: string, blockId?: string, buttonId?: string) => void;
extraProps?: Record<string, any>;
selectedButtonId?: string; // Новый prop для передачи ID выбранной кнопки
}
const BlockRenderer: React.FC<BlockRendererProps> = ({ block, onAction, extraProps }) => {
const BlockRenderer: React.FC<BlockRendererProps> = ({ block, onAction, extraProps, selectedButtonId }) => {
switch (block.type) {
case 'scrollableButtons':
case 'gridButtons':
const buttonBlock = block as ButtonBlock;
if (block.type === 'scrollableButtons') {
return <ScrollableButtonsBlock block={buttonBlock} onAction={onAction} />;
return <ScrollableButtonsBlock block={buttonBlock} onAction={onAction} selectedButtonId={selectedButtonId} />;
}
return <GridButtonsBlock block={buttonBlock} onAction={onAction} isInputVisible={extraProps?.visible} />;
return <GridButtonsBlock block={buttonBlock} onAction={onAction} isInputVisible={extraProps?.visible} selectedButtonId={selectedButtonId} />;
case 'uploadPhoto':
return <UploadPhotoBlock
previewUrl={window.history.state?.usr?.previewUrl || localStorage.getItem('stickerPreviewUrl')}

View File

@ -7,10 +7,11 @@ interface GridButtonsBlockProps {
block: ButtonBlock;
onAction?: (actionType: string, actionValue: string, blockId?: string, buttonId?: string) => void;
isInputVisible?: boolean;
selectedButtonId?: string; // Новый prop для передачи ID выбранной кнопки
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const GridButtonsBlock: React.FC<GridButtonsBlockProps> = ({ block, onAction, isInputVisible }) => {
const GridButtonsBlock: React.FC<GridButtonsBlockProps> = ({ block, onAction, isInputVisible, selectedButtonId }) => {
const [expanded, setExpanded] = useState(false);
const { buttons, style } = block;
const {
@ -45,6 +46,7 @@ const GridButtonsBlock: React.FC<GridButtonsBlockProps> = ({ block, onAction, is
<SquareButton
{...button}
size={buttonSize}
isSelected={selectedButtonId === button.id}
onClick={() => {
if (button.action && onAction) {
onAction(button.action.type, button.action.value, block.id, button.id);

View File

@ -6,9 +6,10 @@ import styles from './ScrollableButtonsBlock.module.css';
interface ScrollableButtonsBlockProps {
block: ButtonBlock;
onAction?: (actionType: string, actionValue: string, blockId?: string, buttonId?: string) => void;
selectedButtonId?: string; // Новый prop для передачи ID выбранной кнопки
}
const ScrollableButtonsBlock: React.FC<ScrollableButtonsBlockProps> = ({ block, onAction }) => {
const ScrollableButtonsBlock: React.FC<ScrollableButtonsBlockProps> = ({ block, onAction, selectedButtonId }) => {
const { buttons, style } = block;
const { gap = 16, padding = 16, buttonSize = 150 } = style;
@ -29,6 +30,7 @@ const ScrollableButtonsBlock: React.FC<ScrollableButtonsBlockProps> = ({ block,
<SquareButton
{...button}
size={buttonSize}
isSelected={selectedButtonId === button.id}
onClick={() => {
if (button.action && onAction) {
onAction(button.action.type, button.action.value, block.id, button.id);

View File

@ -4,7 +4,7 @@
.button {
position: relative;
border: none;
border: 2px solid transparent; /* Добавляем прозрачную границу для всех кнопок */
border-radius: var(--border-radius);
padding: var(--spacing-small);
cursor: pointer;
@ -31,12 +31,41 @@
-webkit-tap-highlight-color: transparent;
}
/* Стиль для выбранной кнопки */
.selected {
border-color: var(--color-primary);
border-width: 3px; /* Увеличиваем толщину с 2px до 3px */
transform: translateY(-2px); /* Добавляем эффект приподнятия */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); /* Добавляем тень как при hover */
}
/* Сохраняем эффект увеличения иконки для выбранной кнопки */
.selected .icon,
.selected .iconImage {
transform: scale(1.1); /* Такой же scale как при hover */
}
/* Специальные стили для iOS */
@supports (-webkit-touch-callout: none) {
.selected {
border-color: var(--color-primary);
border-width: 3px;
box-shadow: 0 0 0 3px var(--color-primary), 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
}
.button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
background-position: center;
}
/* Предотвращаем двойное приподнятие при наведении на выбранную кнопку */
.selected:hover {
transform: translateY(-2px); /* Оставляем то же приподнятие */
}
.button:active {
transform: translateY(1px);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
@ -115,6 +144,12 @@
transform: scale(1.1);
}
/* Предотвращаем двойное увеличение иконки при наведении на выбранную кнопку */
.selected:hover .icon,
.selected:hover .iconImage {
transform: scale(1.1); /* Оставляем тот же масштаб */
}
.content {
display: flex;
flex-direction: column;

View File

@ -7,6 +7,7 @@ interface SquareButtonProps extends BlockButton {
size?: number;
onClick?: () => void;
disabled?: boolean;
isSelected?: boolean; // Новый prop для отображения выбранного состояния
}
const SquareButton: React.FC<SquareButtonProps> = ({
@ -19,7 +20,8 @@ const SquareButton: React.FC<SquareButtonProps> = ({
action,
size = 100,
onClick,
disabled
disabled,
isSelected = false // По умолчанию кнопка не выбрана
}) => {
const navigate = useNavigate();
@ -73,7 +75,7 @@ const SquareButton: React.FC<SquareButtonProps> = ({
return (
<div className={styles.buttonWrapper}>
<button
className={styles.button}
className={`${styles.button} ${isSelected ? styles.selected : ''}`}
onClick={handleClick}
style={{
width: size,
@ -82,6 +84,7 @@ const SquareButton: React.FC<SquareButtonProps> = ({
color: color || '#FFFFFF'
}}
disabled={disabled}
data-selected={isSelected ? 'true' : 'false'} /* Добавляем data-атрибут для дополнительной стилизации */
>
{imageUrl ? (
<img src={imageUrl} alt={title} className={styles.iconImage} />

View File

@ -24,7 +24,8 @@ const Home: React.FC = () => {
});
const [isInputVisible, setIsInputVisible] = useState(false);
const [selectedStyle, setSelectedStyle] = useState<string>('chibi'); // По умолчанию выбран первый стиль
const [selectedButtonId, setSelectedButtonId] = useState<string | undefined>(undefined); // Для хранения ID выбранной кнопки стиля
const [selectedStyleButtonId, setSelectedStyleButtonId] = useState<string | undefined>('chibi'); // Для хранения ID выбранной кнопки стиля
const [selectedPresetId, setSelectedPresetId] = useState<string | undefined>(undefined); // Для хранения ID выбранного пресета
const [customPrompt, setCustomPrompt] = useState<string>(''); // Для хранения пользовательского промпта
// Состояния для модального окна уведомления
@ -44,7 +45,22 @@ const Home: React.FC = () => {
setIsNotificationVisible(false);
}, []);
const handleBlockAction = useCallback(async (actionType: string, actionValue: string, _blockId?: string, buttonId?: string) => {
const handleBlockAction = useCallback(async (actionType: string, actionValue: string, blockId?: string, buttonId?: string) => {
if (actionType === 'selectStyle') {
// Обработка выбора стиля
setSelectedStyle(actionValue);
setSelectedStyleButtonId(buttonId);
console.log('Selected style:', actionValue, 'Button ID:', buttonId);
return;
}
if (actionType === 'selectPreset') {
// Обработка выбора пресета
setSelectedPresetId(buttonId);
console.log('Selected preset:', actionValue, 'Button ID:', buttonId);
return;
}
if (actionType === 'function') {
if (actionValue === 'startGeneration') {
if (!imageData) {
@ -61,10 +77,10 @@ const Home: React.FC = () => {
setIsNotificationVisible(true);
// Если выбран "Свой промпт" и введен текст, используем его
const userPrompt = selectedButtonId === 'customPrompt' && customPrompt ? customPrompt : undefined;
const userPrompt = selectedPresetId === 'customPrompt' && customPrompt ? customPrompt : undefined;
// Отправляем запрос на генерацию
const response = await apiService.generateImage(imageData, selectedStyle, selectedButtonId, userPrompt);
const response = await apiService.generateImage(imageData, selectedStyle, selectedPresetId, userPrompt);
console.log('Generation response:', response);
// Проверяем, была ли ошибка перевода
@ -117,8 +133,8 @@ const Home: React.FC = () => {
if (actionValue === 'toggleInput') {
setIsInputVisible(prev => !prev);
// Устанавливаем selectedButtonId в 'customPrompt' при нажатии на кнопку "Свой промпт"
setSelectedButtonId('customPrompt');
// Устанавливаем selectedPresetId в 'customPrompt' при нажатии на кнопку "Свой промпт"
setSelectedPresetId('customPrompt');
console.log('Выбран свой промпт, установлен ID:', 'customPrompt');
return;
}
@ -137,21 +153,11 @@ const Home: React.FC = () => {
// Добавляем обработку для действий типа 'route'
navigate(actionValue);
return;
} else if (actionType === 'selectStyle') {
// Обработка выбора стиля
setSelectedStyle(actionValue);
console.log('Selected style:', actionValue);
return;
} else if (actionType === 'selectPreset') {
// Обработка выбора пресета
setSelectedButtonId(buttonId);
console.log('Selected preset:', actionValue);
return;
}
// Если выбрана любая другая кнопка, скрываем поле ввода
setIsInputVisible(false);
}, [navigate, imageData, selectedStyle, selectedButtonId, customPrompt]);
}, [navigate, imageData, selectedStyle, selectedPresetId, customPrompt]);
// Эффект для обновления window.history.state при загрузке из localStorage
useEffect(() => {
@ -216,27 +222,38 @@ const Home: React.FC = () => {
<div className={styles.content}>
{/* Блоки из конфигурации */}
<div className={styles.blocks}>
{homeScreenConfig.homeScreen.blocks
.filter(block => block.type !== 'generateButton')
.map((block) => {
// Создаем копию блока с модифицированными кнопками
const modifiedBlock = {
...block,
buttons: getBlockButtons(block)
};
{homeScreenConfig.homeScreen.blocks
.filter(block => block.type !== 'generateButton')
.map((block) => {
// Создаем копию блока с модифицированными кнопками
const modifiedBlock = {
...block,
buttons: getBlockButtons(block)
};
return (
<BlockRenderer
key={block.id}
block={modifiedBlock}
onAction={handleBlockAction}
extraProps={block.type === 'textInput' ? {
visible: isInputVisible,
onTextChange: setCustomPrompt
} : undefined}
/>
);
})}
// Определяем, какой ID выбранной кнопки передавать в зависимости от типа блока
let selectedButtonId;
if (block.id === 'styleActions') {
// Для блока стилей передаем ID кнопки выбранного стиля
selectedButtonId = selectedStyleButtonId;
} else if (block.id === 'quickActions') {
// Для блока пресетов передаем ID выбранного пресета
selectedButtonId = selectedPresetId;
}
return (
<BlockRenderer
key={block.id}
block={modifiedBlock}
onAction={handleBlockAction}
selectedButtonId={selectedButtonId}
extraProps={block.type === 'textInput' ? {
visible: isInputVisible,
onTextChange: setCustomPrompt
} : undefined}
/>
);
})}
</div>
{homeScreenConfig.homeScreen.blocks
.filter(block => block.type === 'generateButton')