diff --git a/package.json b/package.json
index cdc6a35..fc68f6e 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "react-thumbnail-generator",
- "version": "2.3.1",
+ "version": "2.4.0",
"description": "react-thumbnail-generator",
"main": "dist/index.js",
"module": "dist/index.js",
diff --git a/src/components/Accordion/index.tsx b/src/components/Accordion/index.tsx
new file mode 100644
index 0000000..b29a63f
--- /dev/null
+++ b/src/components/Accordion/index.tsx
@@ -0,0 +1,37 @@
+import React, { useState } from 'react';
+import * as S from './styled';
+import { arrowBottom, arrowTop } from '../../assets/icons';
+import TGIcon from '../../components/TGIcon';
+
+interface AccordionProps {
+ title: string;
+ children: React.ReactNode;
+}
+
+const Accordion = ({ title, children }: AccordionProps) => {
+ const [isOpenPanel, setIsOpenPanel] = useState(false);
+
+ const handleToggle = () => {
+ setIsOpenPanel(!isOpenPanel);
+ };
+
+ return (
+
+
+ {title}
+
+ {isOpenPanel ? (
+
+ ) : (
+
+ )}
+
+
+
+ {children}
+
+
+ );
+};
+
+export default Accordion;
diff --git a/src/components/Accordion/styled.tsx b/src/components/Accordion/styled.tsx
new file mode 100644
index 0000000..5b90e5a
--- /dev/null
+++ b/src/components/Accordion/styled.tsx
@@ -0,0 +1,49 @@
+import styled from 'styled-components';
+
+export const AccordionWrapper = styled.div`
+ margin: 0 auto;
+ width: 100%;
+`;
+
+export const AccordionTopContainer = styled.div`
+ cursor: pointer;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ transition: 0.3s;
+ padding: 0 20px;
+ height: 39px;
+
+ & > p {
+ cursor: pointer;
+ background-color: transparent;
+ outline: none;
+ border: none;
+ text-align: start;
+ font-size: 1rem;
+ color: #111;
+ margin: 0;
+ }
+
+ &:hover {
+ & > p {
+ color: #3264b5;
+ }
+ }
+`;
+
+export const AccordionPanelContainer = styled.div`
+ background-color: #fff;
+ overflow: hidden;
+ transition: max-height 0.3s linear;
+ max-height: 0;
+
+ & > p {
+ padding: 20px 40px;
+ font-size: 1.15rem;
+ }
+
+ &.active {
+ max-height: 200px;
+ }
+`;
diff --git a/src/components/TGCanvas.tsx b/src/components/Canvas/index.tsx
similarity index 84%
rename from src/components/TGCanvas.tsx
rename to src/components/Canvas/index.tsx
index e132ece..41193c5 100644
--- a/src/components/TGCanvas.tsx
+++ b/src/components/Canvas/index.tsx
@@ -1,20 +1,17 @@
import React, { useEffect } from 'react';
import { Color } from 'react-color-palette';
-import { CanvasState } from '../types/canvas';
-import { TGCanvasWrapper } from './TG.styled';
+import { CanvasState } from '../../types/canvas';
+import * as S from './styled';
-interface TGCanvasProps {
+interface CanvasProps {
canvasState: CanvasState;
bgColor: Color;
fontColor: Color;
strokeColor: Color;
}
-const TGCanvas = React.forwardRef(
- (
- { canvasState, bgColor, fontColor, strokeColor }: TGCanvasProps,
- ref: any
- ) => {
+const Canvas = React.forwardRef(
+ ({ canvasState, bgColor, fontColor, strokeColor }: CanvasProps, ref: any) => {
const {
value,
canvasWidth,
@@ -84,7 +81,7 @@ const TGCanvas = React.forwardRef(
if (ctx) {
ctx.save();
- if (isBlur) ctx.filter = 'blur(4px)'; // (*)
+ if (isBlur) ctx.filter = 'blur(5px)'; // (*)
if (selectedImage) {
ctx.drawImage(selectedImage, 0, 0);
} else {
@@ -98,13 +95,13 @@ const TGCanvas = React.forwardRef(
}, [bgColor, fontColor, strokeColor, canvasState]);
return (
-
+
-
+
);
}
);
-TGCanvas.displayName = 'Search';
+Canvas.displayName = 'Search';
-export default TGCanvas;
+export default Canvas;
diff --git a/src/components/Canvas/styled.tsx b/src/components/Canvas/styled.tsx
new file mode 100644
index 0000000..5409dbf
--- /dev/null
+++ b/src/components/Canvas/styled.tsx
@@ -0,0 +1,7 @@
+import styled from 'styled-components';
+
+export const CanvasWrapper = styled.div`
+ display: flex;
+ justify-content: center;
+ width: 100%;
+`;
diff --git a/src/components/TGColorPicker.tsx b/src/components/ColorPicker/index.tsx
similarity index 72%
rename from src/components/TGColorPicker.tsx
rename to src/components/ColorPicker/index.tsx
index 7735428..143275f 100644
--- a/src/components/TGColorPicker.tsx
+++ b/src/components/ColorPicker/index.tsx
@@ -1,13 +1,18 @@
import React, { useEffect, useRef, useState } from 'react';
-import { TGColorPickerWrapper, TGIConButton } from './TG.styled';
-import { Color, ColorPicker } from 'react-color-palette';
+import { ColorPickerWrapper } from './styled';
+import { Color, ColorPicker as PaletteColorPicker } from 'react-color-palette';
+import { IconButton } from '../Icon/styled';
-interface TGColorPickerProps {
+interface ColorPickerPickerProps {
children: React.ReactNode;
color: Color;
setColor: (color: Color) => void;
}
-const TGColorPicker = ({ children, color, setColor }: TGColorPickerProps) => {
+const ColorPickerPicker = ({
+ children,
+ color,
+ setColor,
+}: ColorPickerPickerProps) => {
const [isOpenColorPicker, setIsOpenColorPicker] = useState(false);
const colorRef = useRef(null);
@@ -34,16 +39,16 @@ const TGColorPicker = ({ children, color, setColor }: TGColorPickerProps) => {
}, [handleCloseColorPicker]);
return (
-
-
+
{children}
-
+
{isOpenColorPicker && (
- {
/>
)}
-
+
);
};
-export default TGColorPicker;
+export default ColorPickerPicker;
diff --git a/src/components/ColorPicker/styled.tsx b/src/components/ColorPicker/styled.tsx
new file mode 100644
index 0000000..5594d54
--- /dev/null
+++ b/src/components/ColorPicker/styled.tsx
@@ -0,0 +1,143 @@
+import styled from 'styled-components';
+
+export const ColorPickerWrapper = styled.div`
+ position: relative;
+
+ & > div {
+ position: absolute;
+ left: 50%;
+ bottom: 40px;
+ transform: translateX(-50%);
+ }
+
+ .rcp-light {
+ --rcp-background: #ffffff;
+ --rcp-input-text: #111111;
+ --rcp-input-border: rgba(0, 0, 0, 0.1);
+ --rcp-input-label: #717171;
+ }
+ .rcp-dark {
+ --rcp-background: #181818;
+ --rcp-input-text: #f3f3f3;
+ --rcp-input-border: rgba(255, 255, 255, 0.1);
+ --rcp-input-label: #999999;
+ }
+ .rcp {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background-color: var(--rcp-background);
+ border-radius: 10px;
+ }
+ .rcp-body {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 20px;
+ width: 100%;
+ box-sizing: border-box;
+ padding: 20px;
+ }
+ .rcp-saturation {
+ position: relative;
+ width: 100%;
+ background-image: linear-gradient(transparent, black),
+ linear-gradient(to right, white, transparent);
+ border-radius: 10px 10px 0 0;
+ user-select: none;
+ }
+ .rcp-saturation-cursor {
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ border: 2px solid #ffffff;
+ border-radius: 50%;
+ box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.15);
+ box-sizing: border-box;
+ transform: translate(-10px, -10px);
+ }
+ .rcp-hue {
+ position: relative;
+ width: 100%;
+ height: 12px;
+ background-image: linear-gradient(
+ to right,
+ rgb(255, 0, 0),
+ rgb(255, 255, 0),
+ rgb(0, 255, 0),
+ rgb(0, 255, 255),
+ rgb(0, 0, 255),
+ rgb(255, 0, 255),
+ rgb(255, 0, 0)
+ );
+ border-radius: 10px;
+ user-select: none;
+ }
+ .rcp-hue-cursor {
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ border: 2px solid #ffffff;
+ border-radius: 50%;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 0px 0.5px;
+ box-sizing: border-box;
+ transform: translate(-10px, -4px);
+ }
+ .rcp-alpha {
+ position: relative;
+ width: 100%;
+ height: 12px;
+ border-radius: 10px;
+ user-select: none;
+ }
+ .rcp-alpha-cursor {
+ position: absolute;
+ width: 20px;
+ height: 20px;
+ border: 2px solid #ffffff;
+ border-radius: 50%;
+ box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 0px 0.5px;
+ box-sizing: border-box;
+ transform: translate(-10px, -4px);
+ }
+ .rcp-fields {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 10px;
+ width: 100%;
+ }
+ .rcp-fields-element {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 5px;
+ width: 100%;
+ }
+ .hex-element {
+ grid-row: 1;
+ }
+ .hex-element:nth-child(3n) {
+ grid-column: 1 / -1;
+ }
+ .rcp-fields-element-input {
+ width: 100%;
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--rcp-input-text);
+ text-align: center;
+ background: none;
+ border: 2px solid;
+ border-color: var(--rcp-input-border);
+ border-radius: 5px;
+ box-sizing: border-box;
+ outline: none;
+ padding: 10px;
+ }
+ .rcp-fields-element-label {
+ font-size: 14px;
+ font-weight: 600;
+ color: var(--rcp-input-label);
+ text-transform: uppercase;
+ }
+`;
diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx
new file mode 100644
index 0000000..1859a06
--- /dev/null
+++ b/src/components/Icon/index.tsx
@@ -0,0 +1,13 @@
+import React from 'react';
+
+interface IconProps {
+ src: string;
+ width: number;
+ height: number;
+}
+
+const Icon = ({ src, width, height }: IconProps) => {
+ return ;
+};
+
+export default Icon;
diff --git a/src/components/Icon/styled.tsx b/src/components/Icon/styled.tsx
new file mode 100644
index 0000000..1d9da53
--- /dev/null
+++ b/src/components/Icon/styled.tsx
@@ -0,0 +1,26 @@
+import styled from 'styled-components';
+
+export const IconButton = styled.button<{
+ isOpenColorPicker?: boolean;
+ isBorder?: boolean;
+}>`
+ padding: 4px 5px;
+ background: #fff;
+ border-radius: 5px;
+ display: flex;
+ align-items: center;
+ cursor: pointer;
+
+ ${({ isBorder, isOpenColorPicker }) => {
+ if (!isBorder) return `border: none;`;
+ return `
+ border: ${
+ isOpenColorPicker ? '1px solid #0e1b30;' : '1px solid #cccccc;'
+ };
+ `;
+ }}
+
+ &:hover {
+ border: ${({ isBorder }) => (isBorder ? `1px solid #0e1b30` : 'none')};
+ }
+`;
diff --git a/src/components/TG.styled.ts b/src/components/TG.styled.ts
index 2f2978a..3a8aca6 100644
--- a/src/components/TG.styled.ts
+++ b/src/components/TG.styled.ts
@@ -27,7 +27,7 @@ export const TGBodyWrapper = styled.section<{
position: fixed;
display: flex;
justify-content: center;
- min-width: 700px;
+ min-width: 600px;
border-radius: 7px;
box-shadow: 1px 1px 10px #cccccc;
z-index: 9999;
@@ -38,12 +38,6 @@ export const TGBodyWrapper = styled.section<{
${({ modalPosition }) => getModalPosition(modalPosition)}
`;
-export const TGCanvasWrapper = styled.div`
- display: flex;
- justify-content: center;
- width: 100%;
-`;
-
export const TGLimitSizeText = styled.div`
font-size: 0.85rem;
margin-top: 5px;
@@ -78,11 +72,16 @@ export const TGHeaderWrapper = styled.div`
}
a {
- color: #111;
+ color: #111111;
padding: 0;
margin: 0;
font-size: 0.875rem;
font-weight: bold;
+ text-decoration: none;
+
+ &:hover {
+ color: #3264b5;
+ }
}
button {
@@ -142,148 +141,6 @@ export const TGButtonWrapper = styled.div`
}
`;
-export const TGColorPickerWrapper = styled.div`
- position: relative;
-
- & > div {
- position: absolute;
- left: 50%;
- bottom: 40px;
- transform: translateX(-50%);
- }
-
- .rcp-light {
- --rcp-background: #ffffff;
- --rcp-input-text: #111111;
- --rcp-input-border: rgba(0, 0, 0, 0.1);
- --rcp-input-label: #717171;
- }
- .rcp-dark {
- --rcp-background: #181818;
- --rcp-input-text: #f3f3f3;
- --rcp-input-border: rgba(255, 255, 255, 0.1);
- --rcp-input-label: #999999;
- }
- .rcp {
- display: flex;
- flex-direction: column;
- align-items: center;
- background-color: var(--rcp-background);
- border-radius: 10px;
- }
- .rcp-body {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- gap: 20px;
- width: 100%;
- box-sizing: border-box;
- padding: 20px;
- }
- .rcp-saturation {
- position: relative;
- width: 100%;
- background-image: linear-gradient(transparent, black),
- linear-gradient(to right, white, transparent);
- border-radius: 10px 10px 0 0;
- user-select: none;
- }
- .rcp-saturation-cursor {
- position: absolute;
- width: 20px;
- height: 20px;
- border: 2px solid #ffffff;
- border-radius: 50%;
- box-shadow: 0 0 15px 0 rgba(0, 0, 0, 0.15);
- box-sizing: border-box;
- transform: translate(-10px, -10px);
- }
- .rcp-hue {
- position: relative;
- width: 100%;
- height: 12px;
- background-image: linear-gradient(
- to right,
- rgb(255, 0, 0),
- rgb(255, 255, 0),
- rgb(0, 255, 0),
- rgb(0, 255, 255),
- rgb(0, 0, 255),
- rgb(255, 0, 255),
- rgb(255, 0, 0)
- );
- border-radius: 10px;
- user-select: none;
- }
- .rcp-hue-cursor {
- position: absolute;
- width: 20px;
- height: 20px;
- border: 2px solid #ffffff;
- border-radius: 50%;
- box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 0px 0.5px;
- box-sizing: border-box;
- transform: translate(-10px, -4px);
- }
- .rcp-alpha {
- position: relative;
- width: 100%;
- height: 12px;
- border-radius: 10px;
- user-select: none;
- }
- .rcp-alpha-cursor {
- position: absolute;
- width: 20px;
- height: 20px;
- border: 2px solid #ffffff;
- border-radius: 50%;
- box-shadow: rgba(0, 0, 0, 0.2) 0px 0px 0px 0.5px;
- box-sizing: border-box;
- transform: translate(-10px, -4px);
- }
- .rcp-fields {
- display: grid;
- grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
- gap: 10px;
- width: 100%;
- }
- .rcp-fields-element {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 5px;
- width: 100%;
- }
- .hex-element {
- grid-row: 1;
- }
- .hex-element:nth-child(3n) {
- grid-column: 1 / -1;
- }
- .rcp-fields-element-input {
- width: 100%;
- font-size: 14px;
- font-weight: 600;
- color: var(--rcp-input-text);
- text-align: center;
- background: none;
- border: 2px solid;
- border-color: var(--rcp-input-border);
- border-radius: 5px;
- box-sizing: border-box;
- outline: none;
- padding: 10px;
- }
- .rcp-fields-element-label {
- font-size: 14px;
- font-weight: 600;
- color: var(--rcp-input-label);
- text-transform: uppercase;
- }
-`;
-
export const TGControllerWrapper = styled.div`
display: flex;
justify-content: center;
@@ -302,35 +159,11 @@ export const TGControllerWrapper = styled.div`
}
`;
-export const TGIConButton = styled.button<{
- isOpenColorPicker?: boolean;
- isBorder?: boolean;
-}>`
- padding: 4px 5px;
- background: #fff;
- border-radius: 5px;
- display: flex;
- align-items: center;
- cursor: pointer;
-
- ${({ isBorder, isOpenColorPicker }) => {
- if (!isBorder) return `border: none;`;
- return `
- border: ${
- isOpenColorPicker ? '1px solid #0e1b30;' : '1px solid #cccccc;'
- };
- `;
- }}
-
- &:hover {
- border: ${({ isBorder }) => (isBorder ? `1px solid #0e1b30` : 'none')};
- }
-`;
-
// TG Select
export const SelectWrapper = styled.div`
position: relative;
min-width: 150px;
+ max-width: 150px;
label {
font-size: 0.7rem;
diff --git a/src/components/TG.tsx b/src/components/TG.tsx
index d72c708..b3589c4 100644
--- a/src/components/TG.tsx
+++ b/src/components/TG.tsx
@@ -1,11 +1,9 @@
import React, { ChangeEvent, useMemo, useRef, useState } from 'react';
-import TGCanvas from './TGCanvas';
import TGHeader from './TGHeader';
-import TGColorPicker from './TGColorPicker';
import TGSelect from './TGSelect';
import TGSelectItem from './TGSelectItem';
import TGInputText from './TGInputText';
-import TGIcon from './TGIcon';
+import Icon from './Icon';
import TGInputFile from './TGInputFile';
import Divider from './Divider';
import {
@@ -19,6 +17,10 @@ import { fill, font, stroke, blur } from '../assets/icons';
import { Color, useColor } from 'react-color-palette';
import * as S from './TG.styled';
import { downloadCanvas, getValidMessage } from '../utils/common';
+import Accordion from './Accordion';
+import Canvas from './Canvas';
+import ColorPicker from './ColorPicker';
+import { IconButton } from './Icon/styled';
interface TGProps {
additionalFontFamily?: string[];
@@ -37,7 +39,7 @@ const TG = ({
fontSize: '30px',
fontStrokeType: 'None',
fontFamily: 'Arial',
- canvasWidth: '700',
+ canvasWidth: '600',
canvasHeight: '400',
imageType: 'png',
xAxis: '0',
@@ -148,7 +150,7 @@ const TG = ({
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
- {fontFamilyOptions.map((item) => (
-
- {item}
-
- ))}
-
-
- {fontSizes.map((item) => (
-
- {item}
-
- ))}
-
-
- {strokeTypes.map((item) => (
-
- {item}
-
- ))}
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ {fontFamilyOptions.map((item) => (
+
+ {item}
+
+ ))}
+
+
+ {fontSizes.map((item) => (
+
+ {item}
+
+ ))}
+
+
+ {strokeTypes.map((item) => (
+
+ {item}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{imageTypes.map((item) => (
diff --git a/src/components/TGHeader.tsx b/src/components/TGHeader.tsx
index d02d650..36c8de3 100644
--- a/src/components/TGHeader.tsx
+++ b/src/components/TGHeader.tsx
@@ -1,7 +1,8 @@
import React from 'react';
-import { TGHeaderWrapper, TGIConButton, TGLimitSizeText } from './TG.styled';
+import { TGHeaderWrapper, TGLimitSizeText } from './TG.styled';
import TGIcon from './TGIcon';
import { close } from '../assets/icons';
+import { IconButton } from './Icon/styled';
interface TGHeaderProps {
onToggle: () => void;
@@ -18,9 +19,9 @@ const TGHeader = ({ onToggle }: TGHeaderProps) => {
rel="noreferrer">
Simple Thumbnail Generator
-
+
-
+
Limit Width: {`${LimitWidthSize}px`}
diff --git a/src/components/TGInputText.tsx b/src/components/TGInputText.tsx
index 16711a2..ce85a02 100644
--- a/src/components/TGInputText.tsx
+++ b/src/components/TGInputText.tsx
@@ -9,7 +9,7 @@ const TGInputText = ({
name,
label,
value,
- width = 100,
+ width = 124,
maxLength = 5,
disabled = false,
onChange,