From a2a591420c07317e33506b605228e4c8b1898fd6 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Wed, 8 Jul 2026 09:21:41 +0330 Subject: [PATCH] color opacity --- src/components/ColorPicker.tsx | 54 +++++++++++++++++-- src/components/EditorTable.tsx | 3 +- .../components/canvas/usePencilDrawing.ts | 12 ++++- .../instructions/PencilInstruction.tsx | 2 + .../sidebar/instructions/TextInstruction.tsx | 50 +++-------------- .../sidebar/settings/CustomShapeSettings.tsx | 3 ++ .../sidebar/settings/GridSettings.tsx | 9 ++++ .../sidebar/settings/LineSettings.tsx | 4 ++ .../sidebar/settings/LinkSettings.tsx | 4 ++ .../sidebar/settings/ShapeSettings.tsx | 7 +++ .../sidebar/settings/TextSettings.tsx | 4 ++ .../editor/components/tools/AbstractShape.tsx | 1 + .../editor/components/tools/ArrowShape.tsx | 1 + .../editor/components/tools/CircleShape.tsx | 1 + .../editor/components/tools/LineShape.tsx | 1 + .../editor/components/tools/LinkShape.tsx | 1 + .../components/tools/RectangleShape.tsx | 1 + .../editor/components/tools/TextShape.tsx | 14 +---- .../editor/components/tools/TriangleShape.tsx | 1 + src/pages/editor/store/editorStore.helpers.ts | 1 + src/pages/editor/store/editorStore.ts | 29 ++++++++++ src/pages/editor/store/editorStore.types.ts | 2 + src/pages/editor/store/pencilDefaultsStore.ts | 2 + src/pages/editor/utils/colorOpacity.ts | 28 ++++++++++ src/pages/viewer/components/BookPage.tsx | 5 +- 25 files changed, 178 insertions(+), 62 deletions(-) create mode 100644 src/pages/editor/utils/colorOpacity.ts diff --git a/src/components/ColorPicker.tsx b/src/components/ColorPicker.tsx index 66cdd12..2415ec0 100644 --- a/src/components/ColorPicker.tsx +++ b/src/components/ColorPicker.tsx @@ -1,6 +1,7 @@ import { type FC, type InputHTMLAttributes, useState, useRef, useEffect } from 'react' import { clx } from '../helpers/utils' import Error from './Error' +import Input from './Input' type Props = { label?: string @@ -9,17 +10,21 @@ type Props = { isNotRequired?: boolean value?: string onChange?: (value: string) => void + opacity?: number + onOpacityChange?: (value: number) => void + opacityLabel?: string } & Omit, 'onChange' | 'value'> const ColorPicker: FC = (props: Props) => { const [color, setColor] = useState(props.value || '#000000') + const [opacityDisplay, setOpacityDisplay] = useState(`${props.opacity ?? 100}`) const colorInputRef = useRef(null) const pickerOpenGuardRef = useRef(false) + const showOpacity = props.onOpacityChange !== undefined const inputClass = clx( 'w-full bg-white h-[34px] text-black block pl-10 pr-10 text-xs rounded-xl border border-border', props.readOnly && 'bg-gray-100 border-0 text-description', - props.className ) const handleColorChange = (event: React.ChangeEvent) => { @@ -53,8 +58,31 @@ const ColorPicker: FC = (props: Props) => { } }, [props.value]) - return ( -
+ useEffect(() => { + if (props.opacity !== undefined) { + setOpacityDisplay(`${props.opacity}`) + } + }, [props.opacity]) + + const handleOpacityChange = (rawValue: string) => { + setOpacityDisplay(rawValue) + const numValue = parseInt(rawValue, 10) + if (!Number.isNaN(numValue) && numValue >= 0 && numValue <= 100) { + props.onOpacityChange?.(numValue) + } + } + + const handleOpacityBlur = (rawValue: string) => { + const numValue = parseInt(rawValue, 10) || 0 + const clampedValue = Math.min(100, Math.max(0, numValue)) + setOpacityDisplay(`${clampedValue}`) + props.onOpacityChange?.(clampedValue) + } + + const colorField = ( +
@@ -115,6 +143,26 @@ const ColorPicker: FC = (props: Props) => { className='absolute opacity-0 pointer-events-none w-0 h-0' />
+
+ ) + + return ( +
+ {colorField} + {showOpacity && ( +
+ handleOpacityChange(e.target.value)} + onBlur={(e) => handleOpacityBlur(e.target.value)} + min={0} + max={100} + /> +
+ )} {props.error_text && ( diff --git a/src/components/EditorTable.tsx b/src/components/EditorTable.tsx index 52f6610..c82dbe8 100644 --- a/src/components/EditorTable.tsx +++ b/src/components/EditorTable.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Group, Rect, Text } from "react-konva"; import Konva from "konva"; import type { EditorObject } from "@/pages/editor/store/editorStore"; +import { getColorWithOpacity } from "@/pages/editor/utils/colorOpacity"; type EditorTableProps = { obj: EditorObject; @@ -80,7 +81,7 @@ const EditorTable = ({ obj, selectedCellId, onCellClick, onCellDblClick, onDragE y={y} width={cellWidth} height={cellHeight} - fill={cell.background} + fill={getColorWithOpacity(cell.background, cell.backgroundOpacity ?? 100)} stroke={isCellSelected ? "#3b82f6" : stroke || "#808080"} strokeWidth={isCellSelected ? 3 : strokeWidth || 1} cornerRadius={isTopLeft ? [8, 0, 0, 0] : isTopRight ? [0, 8, 0, 0] : 0} diff --git a/src/pages/editor/components/canvas/usePencilDrawing.ts b/src/pages/editor/components/canvas/usePencilDrawing.ts index dd30812..c5ed112 100644 --- a/src/pages/editor/components/canvas/usePencilDrawing.ts +++ b/src/pages/editor/components/canvas/usePencilDrawing.ts @@ -67,6 +67,7 @@ const buildPencilObject = ( points: DrawPoint[], stroke: string, strokeWidth: number, + opacity: number, ): EditorObject => { const xs = points.map((p) => p.x); const ys = points.map((p) => p.y); @@ -89,7 +90,7 @@ const buildPencilObject = ( stroke, strokeWidth, tension: DEFAULT_TENSION, - opacity: 100, + opacity, rotation: 0, }; }; @@ -100,6 +101,7 @@ export const usePencilDrawing = (stageRef: React.RefObject) const pointerIdRef = useRef(null); const strokeRef = useRef("#000000"); const strokeWidthRef = useRef(3); + const opacityRef = useRef(100); const [drawingState, setDrawingState] = useState(INITIAL_STATE); @@ -141,7 +143,12 @@ export const usePencilDrawing = (stageRef: React.RefObject) return; } - const obj = buildPencilObject(points, strokeRef.current, strokeWidthRef.current); + const obj = buildPencilObject( + points, + strokeRef.current, + strokeWidthRef.current, + opacityRef.current, + ); useEditorStore.getState().commitObjectHistoryBeforeChange(); addObject(obj); reset(); @@ -165,6 +172,7 @@ export const usePencilDrawing = (stageRef: React.RefObject) (pos: DrawPoint, pointerId: number) => { strokeRef.current = defaults.stroke; strokeWidthRef.current = defaults.strokeWidth; + opacityRef.current = defaults.opacity; isDrawingRef.current = true; pointerIdRef.current = pointerId; pointsRef.current = [pos]; diff --git a/src/pages/editor/components/sidebar/instructions/PencilInstruction.tsx b/src/pages/editor/components/sidebar/instructions/PencilInstruction.tsx index 97d9a8c..b064642 100644 --- a/src/pages/editor/components/sidebar/instructions/PencilInstruction.tsx +++ b/src/pages/editor/components/sidebar/instructions/PencilInstruction.tsx @@ -22,6 +22,8 @@ const PencilInstruction: FC = () => { updateDefaults({ opacity: value })} onChange={(value) => updateDefaults({ stroke: value })} /> { } }; - // State محلی برای نمایش شفافیت - const [opacityDisplay, setOpacityDisplay] = useState(`${formState.opacity}`); - - useEffect(() => { - setOpacityDisplay(`${formState.opacity}`); - }, [formState.opacity]); - return (
@@ -164,41 +157,14 @@ const TextInstruction: FC = () => { />
-
-
- handleChange("fill", value)} - /> -
- { - const value = e.target.value; - setOpacityDisplay(value); - - // اعمال تغییرات به صورت لایو اگر مقدار معتبر است - const numValue = parseInt(value); - if (!isNaN(numValue) && numValue >= 0 && numValue <= 100) { - handleChange("opacity", numValue); - } - }} - onBlur={(e) => { - const numValue = parseInt(e.target.value) || 0; - const clampedValue = Math.min(100, Math.max(0, numValue)); - setOpacityDisplay(`${clampedValue}`); - handleChange("opacity", clampedValue); - }} - min={0} - max={100} - /> -
-
+
+ handleChange("opacity", value)} + onChange={(value) => handleChange("fill", value)} + />
{/*
diff --git a/src/pages/editor/components/sidebar/settings/CustomShapeSettings.tsx b/src/pages/editor/components/sidebar/settings/CustomShapeSettings.tsx index ac38864..df06ed3 100644 --- a/src/pages/editor/components/sidebar/settings/CustomShapeSettings.tsx +++ b/src/pages/editor/components/sidebar/settings/CustomShapeSettings.tsx @@ -22,6 +22,7 @@ const CustomShapeSettings = ({ selectedObject, onUpdate }: CustomShapeSettingsPr }; const stroke = selectedObject.stroke ?? "#1e40af"; const strokeWidth = selectedObject.strokeWidth ?? 2; + const opacity = selectedObject.opacity ?? 100; return (
@@ -29,6 +30,8 @@ const CustomShapeSettings = ({ selectedObject, onUpdate }: CustomShapeSettingsPr label="رنگ پر" value={fill} readOnly={fillType === "gradient"} + opacity={opacity} + onOpacityChange={(value) => onUpdate(selectedObject.id, { opacity: value })} onChange={(value) => onUpdate(selectedObject.id, { fill: value })} /> diff --git a/src/pages/editor/components/sidebar/settings/GridSettings.tsx b/src/pages/editor/components/sidebar/settings/GridSettings.tsx index a3392b1..edb1451 100644 --- a/src/pages/editor/components/sidebar/settings/GridSettings.tsx +++ b/src/pages/editor/components/sidebar/settings/GridSettings.tsx @@ -15,6 +15,7 @@ const GridSettings = ({ selectedObject }: GridSettingsProps) => { removeRow, removeColumn, changeCellBackground, + changeCellBackgroundOpacity, selectedCellId, } = useEditorStore(); @@ -50,6 +51,12 @@ const GridSettings = ({ selectedObject }: GridSettingsProps) => { } }; + const handleCellBackgroundOpacityChange = (opacity: number) => { + if (selectedCellId && tableDataObj) { + changeCellBackgroundOpacity(selectedObject.id, selectedCellId, opacity); + } + }; + const selectedCell = selectedCellId && tableDataObj ? tableDataObj.cells[selectedCellId] : null; return ( @@ -111,6 +118,8 @@ const GridSettings = ({ selectedObject }: GridSettingsProps) => {
diff --git a/src/pages/editor/components/sidebar/settings/LineSettings.tsx b/src/pages/editor/components/sidebar/settings/LineSettings.tsx index 1095bd2..63387dc 100644 --- a/src/pages/editor/components/sidebar/settings/LineSettings.tsx +++ b/src/pages/editor/components/sidebar/settings/LineSettings.tsx @@ -13,6 +13,10 @@ const LineSettings = ({ selectedObject, onUpdate }: LineSettingsProps) => { + onUpdate(selectedObject.id, { opacity: value }) + } onChange={(value) => onUpdate(selectedObject.id, { stroke: value })} /> { + onUpdate(selectedObject.id, { opacity: value }) + } onChange={(value) => onUpdate(selectedObject.id, { fill: value })} /> diff --git a/src/pages/editor/components/sidebar/settings/ShapeSettings.tsx b/src/pages/editor/components/sidebar/settings/ShapeSettings.tsx index c623bec..90516c8 100644 --- a/src/pages/editor/components/sidebar/settings/ShapeSettings.tsx +++ b/src/pages/editor/components/sidebar/settings/ShapeSettings.tsx @@ -10,6 +10,7 @@ type ShapeSettingsProps = { const ShapeSettings = ({ selectedObject, onUpdate }: ShapeSettingsProps) => { const baseWidth = selectedObject.width ?? 100; const baseHeight = selectedObject.height ?? 100; + const baseOpacity = selectedObject.opacity ?? 100; const baseFill = selectedObject.fill ?? "#3b82f6"; const fillType = selectedObject.fillType ?? "solid"; const gradient = selectedObject.gradient ?? { @@ -50,6 +51,12 @@ const ShapeSettings = ({ selectedObject, onUpdate }: ShapeSettingsProps) => { label="رنگ پس‌زمینه" value={baseFill} readOnly={fillType === "gradient"} + opacity={baseOpacity} + onOpacityChange={(value) => + onUpdate(selectedObject.id, { + opacity: value, + }) + } onChange={(value) => onUpdate(selectedObject.id, { fill: value, diff --git a/src/pages/editor/components/sidebar/settings/TextSettings.tsx b/src/pages/editor/components/sidebar/settings/TextSettings.tsx index fb22457..3abee6c 100644 --- a/src/pages/editor/components/sidebar/settings/TextSettings.tsx +++ b/src/pages/editor/components/sidebar/settings/TextSettings.tsx @@ -102,6 +102,10 @@ const TextSettings = ({ selectedObject, onUpdate }: TextSettingsProps) => { + onUpdate(selectedObject.id, { opacity: value }) + } onChange={(value) => onUpdate(selectedObject.id, { fill: value })} /> diff --git a/src/pages/editor/components/tools/AbstractShape.tsx b/src/pages/editor/components/tools/AbstractShape.tsx index bfc3598..8f0bf6d 100644 --- a/src/pages/editor/components/tools/AbstractShape.tsx +++ b/src/pages/editor/components/tools/AbstractShape.tsx @@ -70,6 +70,7 @@ const AbstractShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shape strokeWidth={displayStrokeWidth} dash={isMask && showGuide ? [10, 5] : undefined} rotation={obj.rotation || 0} + opacity={(obj.opacity ?? 100) / 100} {...blurProps} draggable={draggable} onClick={(e) => { diff --git a/src/pages/editor/components/tools/ArrowShape.tsx b/src/pages/editor/components/tools/ArrowShape.tsx index 37942ab..a3915b6 100644 --- a/src/pages/editor/components/tools/ArrowShape.tsx +++ b/src/pages/editor/components/tools/ArrowShape.tsx @@ -42,6 +42,7 @@ const ArrowShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapePro hitStrokeWidth={20} perfectDrawEnabled={false} rotation={obj.rotation || 0} + opacity={(obj.opacity ?? 100) / 100} draggable={draggable} onClick={(e) => { if (shapeRef.current) { diff --git a/src/pages/editor/components/tools/CircleShape.tsx b/src/pages/editor/components/tools/CircleShape.tsx index cca3292..4b37697 100644 --- a/src/pages/editor/components/tools/CircleShape.tsx +++ b/src/pages/editor/components/tools/CircleShape.tsx @@ -58,6 +58,7 @@ const CircleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: ShapePr strokeWidth={displayStrokeWidth} dash={isMask && showGuide ? [10, 5] : undefined} rotation={obj.rotation || 0} + opacity={(obj.opacity ?? 100) / 100} {...blurProps} draggable={draggable} onClick={(e) => { diff --git a/src/pages/editor/components/tools/LineShape.tsx b/src/pages/editor/components/tools/LineShape.tsx index f6c4c91..a31d481 100644 --- a/src/pages/editor/components/tools/LineShape.tsx +++ b/src/pages/editor/components/tools/LineShape.tsx @@ -26,6 +26,7 @@ const LineShape = ({ obj, onSelect, onUpdate, draggable }: ShapeProps) => { hitStrokeWidth={20} perfectDrawEnabled={false} rotation={obj.rotation || 0} + opacity={(obj.opacity ?? 100) / 100} draggable={draggable} onClick={(e) => { if (shapeRef.current) { diff --git a/src/pages/editor/components/tools/LinkShape.tsx b/src/pages/editor/components/tools/LinkShape.tsx index 1f185ff..0270838 100644 --- a/src/pages/editor/components/tools/LinkShape.tsx +++ b/src/pages/editor/components/tools/LinkShape.tsx @@ -35,6 +35,7 @@ const LinkShape = ({ fontStyle={fontWeight} underline={true} rotation={obj.rotation || 0} + opacity={(obj.opacity ?? 100) / 100} draggable={draggable} onClick={(e) => { // در حالت editor، لینک‌ها قابل کلیک نیستند diff --git a/src/pages/editor/components/tools/RectangleShape.tsx b/src/pages/editor/components/tools/RectangleShape.tsx index 6894414..90aa1b5 100644 --- a/src/pages/editor/components/tools/RectangleShape.tsx +++ b/src/pages/editor/components/tools/RectangleShape.tsx @@ -68,6 +68,7 @@ const RectangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shap strokeWidth={displayStrokeWidth} dash={isMask && showGuide ? [10, 5] : undefined} rotation={obj.rotation || 0} + opacity={(obj.opacity ?? 100) / 100} {...blurProps} draggable={draggable} onClick={(e) => { diff --git a/src/pages/editor/components/tools/TextShape.tsx b/src/pages/editor/components/tools/TextShape.tsx index 8e11a9f..1c00358 100644 --- a/src/pages/editor/components/tools/TextShape.tsx +++ b/src/pages/editor/components/tools/TextShape.tsx @@ -10,6 +10,7 @@ import { resolveTextMaxWidth, usesWrappedLayout, } from "@/pages/editor/utils/textStyle"; +import { getColorWithOpacity } from "@/pages/editor/utils/colorOpacity"; const getFontWeight = (fontWeight?: string): string => { if (!fontWeight) return "normal"; @@ -38,19 +39,6 @@ const getKonvaFontStyle = (fontWeight?: string): string => { return "normal"; }; -const getColorWithOpacity = (fill?: string, opacity?: number): string => { - if (!fill) return "#000000"; - if (opacity === undefined || opacity === 100) return fill; - - const hex = fill.replace("#", ""); - const r = parseInt(hex.substring(0, 2), 16); - const g = parseInt(hex.substring(2, 4), 16); - const b = parseInt(hex.substring(4, 6), 16); - const alpha = opacity / 100; - - return `rgba(${r}, ${g}, ${b}, ${alpha})`; -}; - const TextShape = ({ obj, onSelect, diff --git a/src/pages/editor/components/tools/TriangleShape.tsx b/src/pages/editor/components/tools/TriangleShape.tsx index 6ac629c..dd19118 100644 --- a/src/pages/editor/components/tools/TriangleShape.tsx +++ b/src/pages/editor/components/tools/TriangleShape.tsx @@ -66,6 +66,7 @@ const TriangleShape = ({ obj, isSelected, onSelect, onUpdate, draggable }: Shape strokeWidth={displayStrokeWidth} dash={isMask && showGuide ? [10, 5] : undefined} rotation={obj.rotation || 0} + opacity={(obj.opacity ?? 100) / 100} {...blurProps} draggable={draggable} onClick={(e) => { diff --git a/src/pages/editor/store/editorStore.helpers.ts b/src/pages/editor/store/editorStore.helpers.ts index 9669378..fd7bcad 100644 --- a/src/pages/editor/store/editorStore.helpers.ts +++ b/src/pages/editor/store/editorStore.helpers.ts @@ -44,6 +44,7 @@ export const createInitialCells = ( id: cellId, text: "", background: "#f5f5f5", + backgroundOpacity: 100, width: cellWidth, height: cellHeight, }; diff --git a/src/pages/editor/store/editorStore.ts b/src/pages/editor/store/editorStore.ts index e8a2e4a..bad9b00 100644 --- a/src/pages/editor/store/editorStore.ts +++ b/src/pages/editor/store/editorStore.ts @@ -127,6 +127,11 @@ type EditorStoreType = { cellId: string, color: string, ) => void; + changeCellBackgroundOpacity: ( + tableId: string, + cellId: string, + opacity: number, + ) => void; applyTableResize: ( tableId: string, newWidth: number, @@ -640,6 +645,7 @@ export const useEditorStore = create((set, get) => { id: cellId, text: "", background: "#f5f5f5", + backgroundOpacity: 100, width: tableData.cellWidth, height: tableData.cellHeight, }; @@ -670,6 +676,7 @@ export const useEditorStore = create((set, get) => { id: cellId, text: "", background: "#f5f5f5", + backgroundOpacity: 100, width: tableData.cellWidth, height: tableData.cellHeight, }; @@ -776,6 +783,28 @@ export const useEditorStore = create((set, get) => { }, }); }, + changeCellBackgroundOpacity: (tableId, cellId, opacity) => { + get().commitObjectHistoryBeforeChange(); + const state = get(); + const obj = state.objects.find((o) => o.id === tableId); + if (!obj || !obj.tableData) return; + + const { tableData } = obj; + const updatedCells = { + ...tableData.cells, + [cellId]: { + ...tableData.cells[cellId], + backgroundOpacity: Math.min(100, Math.max(0, opacity)), + }, + }; + + state.updateObject(tableId, { + tableData: { + ...tableData, + cells: updatedCells, + }, + }); + }, applyTableResize: (tableId, newWidth, newHeight) => { const state = get(); const obj = state.objects.find((o) => o.id === tableId); diff --git a/src/pages/editor/store/editorStore.types.ts b/src/pages/editor/store/editorStore.types.ts index ea9a041..7eb46ac 100644 --- a/src/pages/editor/store/editorStore.types.ts +++ b/src/pages/editor/store/editorStore.types.ts @@ -33,6 +33,8 @@ export type TableCell = { id: string; text: string; background: string; + /** شفافیت پس‌زمینه سلول (۰–۱۰۰) */ + backgroundOpacity?: number; width: number; height: number; }; diff --git a/src/pages/editor/store/pencilDefaultsStore.ts b/src/pages/editor/store/pencilDefaultsStore.ts index 9099ddb..2101c35 100644 --- a/src/pages/editor/store/pencilDefaultsStore.ts +++ b/src/pages/editor/store/pencilDefaultsStore.ts @@ -3,6 +3,7 @@ import { create } from "zustand"; export type PencilDefaults = { stroke: string; strokeWidth: number; + opacity: number; }; type PencilDefaultsStoreType = { @@ -14,6 +15,7 @@ export const usePencilDefaultsStore = create((set) => ( defaults: { stroke: "#000000", strokeWidth: 3, + opacity: 100, }, updateDefaults: (updates) => set((state) => ({ diff --git a/src/pages/editor/utils/colorOpacity.ts b/src/pages/editor/utils/colorOpacity.ts new file mode 100644 index 0000000..af79880 --- /dev/null +++ b/src/pages/editor/utils/colorOpacity.ts @@ -0,0 +1,28 @@ +/** Converts a hex color + 0–100 opacity to rgba (editor/viewer convention). */ +export const getColorWithOpacity = (fill?: string, opacity?: number): string => { + if (!fill) return "#000000"; + if (opacity === undefined || opacity >= 100) return fill; + + const hex = fill.replace("#", ""); + if (!/^[0-9A-Fa-f]{3,8}$/.test(hex)) return fill; + + const paddedHex = + hex.length === 3 + ? hex + .split("") + .map((c) => c + c) + .join("") + : hex.length === 4 + ? hex + .split("") + .map((c) => c + c) + .join("") + : hex.padEnd(6, "0").slice(0, 6); + + const r = parseInt(paddedHex.substring(0, 2), 16); + const g = parseInt(paddedHex.substring(2, 4), 16); + const b = parseInt(paddedHex.substring(4, 6), 16); + const alpha = opacity / 100; + + return `rgba(${r}, ${g}, ${b}, ${alpha})`; +}; diff --git a/src/pages/viewer/components/BookPage.tsx b/src/pages/viewer/components/BookPage.tsx index 33f9815..34a8121 100644 --- a/src/pages/viewer/components/BookPage.tsx +++ b/src/pages/viewer/components/BookPage.tsx @@ -950,7 +950,10 @@ const BookPage = memo(forwardRef( key={cellId} style={{ border: stroke ? `${(strokeWidth || 1) * scale}px solid ${stroke}` : `${1 * scale}px solid #ccc`, - backgroundColor: cell?.background || '#ffffff', + backgroundColor: getColorWithOpacity( + cell?.background || '#ffffff', + cell?.backgroundOpacity, + ), display: 'flex', alignItems: 'center', justifyContent: 'center',