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

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 { interface BlockRendererProps {
block: Block; block: Block;
onAction?: (actionType: string, actionValue: string, blockId?: string) => void; onAction?: (actionType: string, actionValue: string, blockId?: string, buttonId?: string) => void;
extraProps?: Record<string, any>; 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) { switch (block.type) {
case 'scrollableButtons': case 'scrollableButtons':
case 'gridButtons': case 'gridButtons':
const buttonBlock = block as ButtonBlock; const buttonBlock = block as ButtonBlock;
if (block.type === 'scrollableButtons') { 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': case 'uploadPhoto':
return <UploadPhotoBlock return <UploadPhotoBlock
previewUrl={window.history.state?.usr?.previewUrl || localStorage.getItem('stickerPreviewUrl')} previewUrl={window.history.state?.usr?.previewUrl || localStorage.getItem('stickerPreviewUrl')}

View File

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

View File

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

View File

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

View File

@ -24,7 +24,8 @@ const Home: React.FC = () => {
}); });
const [isInputVisible, setIsInputVisible] = useState(false); const [isInputVisible, setIsInputVisible] = useState(false);
const [selectedStyle, setSelectedStyle] = useState<string>('chibi'); // По умолчанию выбран первый стиль 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>(''); // Для хранения пользовательского промпта const [customPrompt, setCustomPrompt] = useState<string>(''); // Для хранения пользовательского промпта
// Состояния для модального окна уведомления // Состояния для модального окна уведомления
@ -44,7 +45,22 @@ const Home: React.FC = () => {
setIsNotificationVisible(false); 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 (actionType === 'function') {
if (actionValue === 'startGeneration') { if (actionValue === 'startGeneration') {
if (!imageData) { if (!imageData) {
@ -61,10 +77,10 @@ const Home: React.FC = () => {
setIsNotificationVisible(true); 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); console.log('Generation response:', response);
// Проверяем, была ли ошибка перевода // Проверяем, была ли ошибка перевода
@ -117,8 +133,8 @@ const Home: React.FC = () => {
if (actionValue === 'toggleInput') { if (actionValue === 'toggleInput') {
setIsInputVisible(prev => !prev); setIsInputVisible(prev => !prev);
// Устанавливаем selectedButtonId в 'customPrompt' при нажатии на кнопку "Свой промпт" // Устанавливаем selectedPresetId в 'customPrompt' при нажатии на кнопку "Свой промпт"
setSelectedButtonId('customPrompt'); setSelectedPresetId('customPrompt');
console.log('Выбран свой промпт, установлен ID:', 'customPrompt'); console.log('Выбран свой промпт, установлен ID:', 'customPrompt');
return; return;
} }
@ -137,21 +153,11 @@ const Home: React.FC = () => {
// Добавляем обработку для действий типа 'route' // Добавляем обработку для действий типа 'route'
navigate(actionValue); navigate(actionValue);
return; 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); setIsInputVisible(false);
}, [navigate, imageData, selectedStyle, selectedButtonId, customPrompt]); }, [navigate, imageData, selectedStyle, selectedPresetId, customPrompt]);
// Эффект для обновления window.history.state при загрузке из localStorage // Эффект для обновления window.history.state при загрузке из localStorage
useEffect(() => { useEffect(() => {
@ -225,11 +231,22 @@ const Home: React.FC = () => {
buttons: getBlockButtons(block) buttons: getBlockButtons(block)
}; };
// Определяем, какой ID выбранной кнопки передавать в зависимости от типа блока
let selectedButtonId;
if (block.id === 'styleActions') {
// Для блока стилей передаем ID кнопки выбранного стиля
selectedButtonId = selectedStyleButtonId;
} else if (block.id === 'quickActions') {
// Для блока пресетов передаем ID выбранного пресета
selectedButtonId = selectedPresetId;
}
return ( return (
<BlockRenderer <BlockRenderer
key={block.id} key={block.id}
block={modifiedBlock} block={modifiedBlock}
onAction={handleBlockAction} onAction={handleBlockAction}
selectedButtonId={selectedButtonId}
extraProps={block.type === 'textInput' ? { extraProps={block.type === 'textInput' ? {
visible: isInputVisible, visible: isInputVisible,
onTextChange: setCustomPrompt onTextChange: setCustomPrompt