From 9e33579eada580a9b5f5a009686f0ef32293bf7d Mon Sep 17 00:00:00 2001 From: ssi02014 Date: Wed, 5 Apr 2023 15:34:47 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20github=20actions=20ci/cd=20?= =?UTF-8?q?=EA=B5=AC=EC=B6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/npm-publish.yml | 38 +++++++++++++++++++++++++++++++ package.json | 2 +- src/components/TG.styled.ts | 1 - 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/npm-publish.yml diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml new file mode 100644 index 0000000..bb43518 --- /dev/null +++ b/.github/workflows/npm-publish.yml @@ -0,0 +1,38 @@ +name: Publish Package to npmjs +on: + push: + branches: + - master +jobs: + npm-publish: + runs-on: ubuntu-latest + steps: + - name: Checkout 🔔 + uses: actions/checkout@v3 + - name: Node Setup 🔔 + uses: actions/setup-node@v3 + with: + node-version: 18 + registry-url: 'https://registry.npmjs.org' + - name: Install & Build 🔨 + run: | + yarn + yarn build + - name: Publish 🚀 + run: yarn publish + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + storybook-publish: + runs-on: ubuntu-latest + steps: + - name: Checkout 🔔 + uses: actions/checkout@v3 + - name: Install & Build 🔨 + run: | + yarn + yarn build:storybook + - name: Deploy 🚀 + uses: JamesIves/github-pages-deploy-action@v4 + with: + token: ${{ secrets.PUBLISH_TOKEN }} + folder: storybook-static \ No newline at end of file diff --git a/package.json b/package.json index 9911c08..a740f13 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-thumbnail-generator", - "version": "2.7.1", + "version": "2.8.0", "description": "react-thumbnail-generator", "main": "dist/index.js", "module": "dist/index.js", diff --git a/src/components/TG.styled.ts b/src/components/TG.styled.ts index 5aab829..e256d3b 100644 --- a/src/components/TG.styled.ts +++ b/src/components/TG.styled.ts @@ -29,7 +29,6 @@ export const TGBodyWrapper = styled.section<{ display: flex; justify-content: center; min-width: ${({ isFullWidth }) => (isFullWidth ? '100%' : '600px')}; - min-height: 100vh; border-radius: 7px; box-shadow: 1px 1px 10px #cccccc; z-index: 9999; From 0970cf42177c53048a996aa1486753f1d249a28d Mon Sep 17 00:00:00 2001 From: ssi02014 Date: Wed, 5 Apr 2023 16:39:30 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20line=20Height=20+=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Canvas/index.tsx | 5 +- .../{Divider.tsx => Divider/index.tsx} | 0 src/components/Inputs/RangeInput/index.tsx | 1 + .../{TGHeader.tsx => Layout/Header/index.tsx} | 20 +-- src/components/Layout/styled.tsx | 91 +++++++++++ .../{TGPortal.tsx => Portal/index.tsx} | 6 +- .../SelectItem.tsx} | 14 +- .../{TGSelect.tsx => Select/index.tsx} | 20 +-- src/components/Select/styled.tsx | 58 +++++++ src/components/TG.styled.ts | 148 +----------------- src/components/TG.tsx | 108 +++++++------ src/hooks/useDragAndDropText.tsx | 2 +- src/lib/ThumbnailGenerator.tsx | 6 +- src/types/canvas.ts | 1 + src/utils/common.ts | 9 +- 15 files changed, 256 insertions(+), 233 deletions(-) rename src/components/{Divider.tsx => Divider/index.tsx} (100%) rename src/components/{TGHeader.tsx => Layout/Header/index.tsx} (62%) create mode 100644 src/components/Layout/styled.tsx rename src/components/{TGPortal.tsx => Portal/index.tsx} (79%) rename src/components/{TGSelectItem.tsx => Select/SelectItem.tsx} (62%) rename src/components/{TGSelect.tsx => Select/index.tsx} (86%) create mode 100644 src/components/Select/styled.tsx diff --git a/src/components/Canvas/index.tsx b/src/components/Canvas/index.tsx index 2b5174b..37caec9 100644 --- a/src/components/Canvas/index.tsx +++ b/src/components/Canvas/index.tsx @@ -10,6 +10,7 @@ interface CanvasProps { const Canvas = React.forwardRef(({ canvasState }: CanvasProps, ref: any) => { const { value, + lineHeight, canvasWidth, canvasHeight, fontSize, @@ -85,10 +86,10 @@ const Canvas = React.forwardRef(({ canvasState }: CanvasProps, ref: any) => { lines: string[] ) => { const size = +fontSize.replace('px', ''); - const lineHeight = size * 1.15; + const fontLineHeight = size + +lineHeight; lines.forEach((line, idx) => { - const { x, y } = getMultiLinePosition(lines.length, lineHeight, idx); + const { x, y } = getMultiLinePosition(lines.length, fontLineHeight, idx); ctx.save(); ctx.translate(x, y); diff --git a/src/components/Divider.tsx b/src/components/Divider/index.tsx similarity index 100% rename from src/components/Divider.tsx rename to src/components/Divider/index.tsx diff --git a/src/components/Inputs/RangeInput/index.tsx b/src/components/Inputs/RangeInput/index.tsx index e6b95fb..8ebf5d0 100644 --- a/src/components/Inputs/RangeInput/index.tsx +++ b/src/components/Inputs/RangeInput/index.tsx @@ -28,6 +28,7 @@ const RangeInput = ({ void; } -const TGHeader = ({ onToggle }: TGHeaderProps) => { +const Header = ({ onToggle }: HeaderProps) => { const LimitWidthSize = window.innerWidth; return ( - + - + Limit Width: {`${LimitWidthSize}px`} - - + + ); }; -export default TGHeader; +export default Header; diff --git a/src/components/Layout/styled.tsx b/src/components/Layout/styled.tsx new file mode 100644 index 0000000..7820e17 --- /dev/null +++ b/src/components/Layout/styled.tsx @@ -0,0 +1,91 @@ +import { getModalPosition } from '@utils/style'; +import styled from 'styled-components'; + +export const HeaderWrapper = styled.div` + display: flex; + position: sticky; + top: 0; + flex-direction: column; + padding: 10px; + padding-bottom: 0; + background-color: #fff; + + & > div:first-child { + justify-content: space-between; + align-items: center; + display: flex; + } + + a { + color: #111111; + padding: 0; + margin: 0; + font-size: 0.875rem; + font-weight: bold; + text-decoration: none; + + &:hover { + color: #3264b5; + } + } + + button { + cursor: pointer; + transition: transform 0.2s; + &:hover { + transform: rotate(90deg); + } + } +`; + +export const LimitSizeText = styled.div` + font-size: 0.85rem; + margin-top: 5px; + text-align: center; + + span { + font-weight: bold; + } +`; + +export const BodyWrapper = styled.section<{ + modalPosition: 'left' | 'right' | 'center'; + isFullWidth: boolean; +}>` + position: fixed; + display: flex; + justify-content: center; + min-width: ${({ isFullWidth }) => (isFullWidth ? '100%' : '600px')}; + border-radius: 7px; + box-shadow: 1px 1px 10px #cccccc; + z-index: 9999; + background-color: #ffffff; + flex-direction: column; + overflow: hidden; + font-family: Arial; + + * { + box-sizing: border-box; + } + + ${({ modalPosition }) => getModalPosition(modalPosition)} +`; + +export const InnerWrapper = styled.div` + flex-direction: column; + width: 100%; + max-height: calc(100vh - 78px); + overflow-y: scroll; + overflow-x: auto; +`; + +export const ContentWrapper = styled.div` + display: flex; + justify-content: center; + flex-direction: column; + margin-bottom: 20px; + + canvas + textarea { + margin-top: 10px; + } +`; diff --git a/src/components/TGPortal.tsx b/src/components/Portal/index.tsx similarity index 79% rename from src/components/TGPortal.tsx rename to src/components/Portal/index.tsx index 37b0330..b44f7a6 100644 --- a/src/components/TGPortal.tsx +++ b/src/components/Portal/index.tsx @@ -1,12 +1,12 @@ import React, { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; -interface TGPortalProps { +interface PortalProps { id?: string; children: React.ReactNode; } -const TGPortal = ({ id, children }: TGPortalProps) => { +const Portal = ({ id, children }: PortalProps) => { const [portalElement, setPortalElement] = useState(null); useEffect(() => { @@ -17,4 +17,4 @@ const TGPortal = ({ id, children }: TGPortalProps) => { return ReactDOM.createPortal(children, portalElement); }; -export default TGPortal; +export default Portal; diff --git a/src/components/TGSelectItem.tsx b/src/components/Select/SelectItem.tsx similarity index 62% rename from src/components/TGSelectItem.tsx rename to src/components/Select/SelectItem.tsx index 168d7b9..a87f747 100644 --- a/src/components/TGSelectItem.tsx +++ b/src/components/Select/SelectItem.tsx @@ -1,8 +1,8 @@ import React, { useContext } from 'react'; -import { SelectListItem } from './TG.styled'; -import { SelectContext } from './TGSelect'; +import * as S from './styled'; +import { SelectContext } from './index'; -interface TGSelectItemProps { +interface SelectItemProps { children: string; value: string | number; } @@ -12,18 +12,18 @@ interface SelectContextProps { onChange: (value: string | number) => void; } -const TGSelectItem = ({ children, value }: TGSelectItemProps) => { +const SelectItem = ({ children, value }: SelectItemProps) => { const { selectValue, onChange } = useContext( SelectContext ) as SelectContextProps; return ( - onChange(value)}> {children} - + ); }; -export default TGSelectItem; +export default SelectItem; diff --git a/src/components/TGSelect.tsx b/src/components/Select/index.tsx similarity index 86% rename from src/components/TGSelect.tsx rename to src/components/Select/index.tsx index eff43c8..314502b 100644 --- a/src/components/TGSelect.tsx +++ b/src/components/Select/index.tsx @@ -6,8 +6,8 @@ import React, { useEffect, ComponentProps, } from 'react'; -import { SelectWrapper, SelectInput, SelectItemContainer } from './TG.styled'; -import Icon from './Icon'; +import * as S from './styled'; +import Icon from '../Icon'; interface SelectContextProps { color?: string; @@ -28,7 +28,7 @@ export const SelectContext = React.createContext( null ); -const TGSelect = ({ +const Select = ({ children, name, color, @@ -70,9 +70,9 @@ const TGSelect = ({ return ( - + - @@ -84,11 +84,13 @@ const TGSelect = ({ )}

-
- {isOpenSelect && {children}} -
+ + {isOpenSelect && ( + {children} + )} +
); }; -export default TGSelect; +export default Select; diff --git a/src/components/Select/styled.tsx b/src/components/Select/styled.tsx new file mode 100644 index 0000000..f34cfaf --- /dev/null +++ b/src/components/Select/styled.tsx @@ -0,0 +1,58 @@ +import styled from 'styled-components'; + +// TG Select +export const SelectWrapper = styled.div` + position: relative; + min-width: 150px; + max-width: 150px; + + label { + font-size: 0.7rem; + color: #969696; + } +`; + +export const SelectInput = styled.div<{ isOpenSelect: boolean }>` + display: flex; + justify-content: space-between; + align-items: center; + cursor: pointer; + border: ${({ isOpenSelect }) => + isOpenSelect ? `1px solid #0e1b30` : `1px solid #cccccc`}; + border-radius: 5px; + padding: 6px 12px; + + p { + margin: 0; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.9rem; + } + + &:hover { + border: 1px solid #0e1b30; + } +`; + +export const SelectItemContainer = styled.ul` + position: absolute; + bottom: 20px; + width: 100%; + background-color: #fff; + box-shadow: 0 0 3px 0.5px #afafaf; + overflow-y: scroll; + list-style: none; + padding: 0; + max-height: 200px; +`; + +export const SelectListItem = styled.li` + cursor: pointer; + padding: 10px 15px; + font-size: 0.9rem; + + &:hover { + background-color: #ededed; + } +`; diff --git a/src/components/TG.styled.ts b/src/components/TG.styled.ts index e256d3b..0d7d05e 100644 --- a/src/components/TG.styled.ts +++ b/src/components/TG.styled.ts @@ -1,5 +1,5 @@ import styled from 'styled-components'; -import { getIconPosition, getModalPosition } from '@utils/style'; +import { getIconPosition } from '@utils/style'; export const TGOpenButton = styled.button<{ iconPosition: [number, number, number, number]; @@ -21,95 +21,6 @@ export const TGOpenButton = styled.button<{ ${({ iconPosition }) => getIconPosition(iconPosition)} `; -export const TGBodyWrapper = styled.section<{ - modalPosition: 'left' | 'right' | 'center'; - isFullWidth: boolean; -}>` - position: fixed; - display: flex; - justify-content: center; - min-width: ${({ isFullWidth }) => (isFullWidth ? '100%' : '600px')}; - border-radius: 7px; - box-shadow: 1px 1px 10px #cccccc; - z-index: 9999; - background-color: #ffffff; - flex-direction: column; - overflow: hidden; - font-family: Arial; - - * { - box-sizing: border-box; - } - - ${({ modalPosition }) => getModalPosition(modalPosition)} -`; - -export const TGLimitSizeText = styled.div` - font-size: 0.85rem; - margin-top: 5px; - text-align: center; - - span { - font-weight: bold; - } -`; - -export const TGInnerWrapper = styled.div` - flex-direction: column; - width: 100%; - max-height: calc(100vh - 78px); - overflow-y: scroll; - overflow-x: auto; -`; - -export const TGHeaderWrapper = styled.div` - display: flex; - position: sticky; - top: 0; - flex-direction: column; - padding: 10px; - padding-bottom: 0; - background-color: #fff; - - & > div:first-child { - justify-content: space-between; - align-items: center; - display: flex; - } - - a { - color: #111111; - padding: 0; - margin: 0; - font-size: 0.875rem; - font-weight: bold; - text-decoration: none; - - &:hover { - color: #3264b5; - } - } - - button { - cursor: pointer; - transition: transform 0.2s; - &:hover { - transform: rotate(90deg); - } - } -`; - -export const TGContentWrapper = styled.div` - display: flex; - justify-content: center; - flex-direction: column; - margin-bottom: 20px; - - canvas + textarea { - margin-top: 10px; - } -`; - export const TGTextarea = styled.textarea` width: 350px; height: auto; @@ -163,60 +74,3 @@ export const TGControllerWrapper = styled.div` margin-top: 20px; } `; - -// TG Select -export const SelectWrapper = styled.div` - position: relative; - min-width: 150px; - max-width: 150px; - - label { - font-size: 0.7rem; - color: #969696; - } -`; - -export const SelectInput = styled.div<{ isOpenSelect: boolean }>` - display: flex; - justify-content: space-between; - align-items: center; - cursor: pointer; - border: ${({ isOpenSelect }) => - isOpenSelect ? `1px solid #0e1b30` : `1px solid #cccccc`}; - border-radius: 5px; - padding: 6px 12px; - - p { - margin: 0; - display: flex; - align-items: center; - justify-content: center; - font-size: 0.9rem; - } - - &:hover { - border: 1px solid #0e1b30; - } -`; - -export const SelectItemContainer = styled.ul` - position: absolute; - bottom: 20px; - width: 100%; - background-color: #fff; - box-shadow: 0 0 3px 0.5px #afafaf; - overflow-y: scroll; - list-style: none; - padding: 0; - max-height: 200px; -`; - -export const SelectListItem = styled.li` - cursor: pointer; - padding: 10px 15px; - font-size: 0.9rem; - - &:hover { - background-color: #ededed; - } -`; diff --git a/src/components/TG.tsx b/src/components/TG.tsx index 704103c..0ce7023 100644 --- a/src/components/TG.tsx +++ b/src/components/TG.tsx @@ -1,7 +1,6 @@ import React, { ChangeEvent, useMemo, useRef, useState } from 'react'; -import TGHeader from './TGHeader'; -import TGSelect from './TGSelect'; -import TGSelectItem from './TGSelectItem'; +import Select from './Select'; +import SelectItem from './Select/SelectItem'; import TextInput from './Inputs/TextInput'; import Icon from './Icon'; import FileInput from './Inputs/FileInput'; @@ -10,6 +9,7 @@ import Accordion from './Accordion'; import Canvas from './Canvas'; import ColorPicker from './ColorPicker'; import InputRange from './Inputs/RangeInput'; +import Header from './Layout/Header'; import { fontFamilies, fontSizes, @@ -19,8 +19,9 @@ import { import { CanvasState } from '../types/canvas'; import { fill, font, stroke, blur } from '@assets/icons'; import { Color, useColor } from 'react-color-palette'; +import { BodyWrapper, ContentWrapper, InnerWrapper } from './Layout/styled'; import * as S from './TG.styled'; -import { downloadCanvas, getValidMessage } from '@utils/common'; +import { downloadCanvas, getValidMessage, ValidType } from '@utils/common'; import { IconButton } from './Icon/styled'; interface TGProps { @@ -46,6 +47,7 @@ const TG = ({ canvasHeight: '400', imageType: 'png', angle: '0', + lineHeight: '0', isBlur: false, selectedImage: null, isBlockEvent: false, @@ -57,6 +59,24 @@ const TG = ({ const canvasRef = useRef(null); + const canvasStateWithColors = useMemo(() => { + return { + ...canvasState, + bgColor, + fontColor, + strokeColor, + }; + }, [canvasState, bgColor, fontColor, strokeColor]); + + const fontFamilyOptions = useMemo(() => { + return [...additionalFontFamily, ...fontFamilies]; + }, [additionalFontFamily]); + + const defaultLineHeight = useMemo( + () => +canvasState.fontSize.replace('px', ''), + [canvasState.fontSize] + ); + const toggleIsBlockEvent = () => { setCanvasState({ ...canvasState, @@ -150,44 +170,32 @@ const TG = ({ downloadCanvas(canvasRef, canvasState.imageType); }; - const handleChangeAngle = (e: ChangeEvent) => { + const handleChangeRange = (e: ChangeEvent) => { const regex = /[^0-9]/g; const { name, value } = e.target; - + const min = name === 'angle' ? -360 : defaultLineHeight * -5; + const max = name === 'angle' ? 360 : defaultLineHeight * 3; const replacedCallback = getReplaceCallback(name); const replacedValue = value.replace(regex, replacedCallback); const validMessage = getValidMessage( - +value < -360 || +value > 360, - 'fontAngle' + +value < min || +value > max, + name as ValidType ); if (validMessage) return alert(validMessage); setCanvasState({ ...canvasState, - angle: replacedValue, + [name]: replacedValue, }); }; - const canvasStateWithColors = useMemo(() => { - return { - ...canvasState, - bgColor, - fontColor, - strokeColor, - }; - }, [canvasState, bgColor, fontColor, strokeColor]); - - const fontFamilyOptions = useMemo(() => { - return [...additionalFontFamily, ...fontFamilies]; - }, [additionalFontFamily]); - return ( - - - - + +
+ + @@ -235,44 +243,52 @@ const TG = ({ min={-360} max={360} value={canvasState.angle} - onChange={handleChangeAngle} + onChange={handleChangeRange} + /> + - {fontFamilyOptions.map((item) => ( - + {item} - + ))} - - + {strokeTypes.map((item) => ( - + {item} - + ))} - + @@ -302,25 +318,25 @@ const TG = ({ - {imageTypes.map((item) => ( - + {item} - + ))} - + - - - + + + ); }; diff --git a/src/hooks/useDragAndDropText.tsx b/src/hooks/useDragAndDropText.tsx index a07a4d6..1a6f9fe 100644 --- a/src/hooks/useDragAndDropText.tsx +++ b/src/hooks/useDragAndDropText.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; const useDragAndDropText = (canvasWidth: number, canvasHeight: number) => { const [dragAndDropTextData, setDragAndDropTextData] = useState({ diff --git a/src/lib/ThumbnailGenerator.tsx b/src/lib/ThumbnailGenerator.tsx index 4dc5ab1..5eafdf7 100644 --- a/src/lib/ThumbnailGenerator.tsx +++ b/src/lib/ThumbnailGenerator.tsx @@ -3,7 +3,7 @@ import TG from '@components/TG'; import { TGOpenButton } from '@components/TG.styled'; import { toggleButton } from '@assets/icons'; import { Position, getIconSize } from '@utils/style'; -import TGPortal from '@components/TGPortal'; +import Portal from '@components/Portal'; import Icon from '@components/Icon'; interface ThumbnailGeneratorProps { @@ -35,7 +35,7 @@ const ThumbnailGenerator = ({ return ( <> - + {isOpen ? ( )} - + ); }; diff --git a/src/types/canvas.ts b/src/types/canvas.ts index 843776f..3e67beb 100644 --- a/src/types/canvas.ts +++ b/src/types/canvas.ts @@ -12,6 +12,7 @@ export interface CanvasState { canvasWidth: string; canvasHeight: string; imageType: ImageTypes; + lineHeight: string; angle: string; isBlur: boolean; selectedImage: HTMLImageElement | null; diff --git a/src/utils/common.ts b/src/utils/common.ts index b6406cb..e59fb9e 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -1,14 +1,13 @@ import React from 'react'; import { ImageTypes } from '../types/canvas'; -export const getValidMessage = ( - condition: boolean, - type: 'imageSize' | 'canvasSize' | 'fontAngle' -) => { +export type ValidType = 'imageSize' | 'canvasSize' | 'angle' | 'lineHeight'; +export const getValidMessage = (condition: boolean, type: ValidType) => { const message = { imageSize: `Please register a picture smaller than "Limit Width"`, canvasSize: `Please set the canvas width smaller than "Limit Width"`, - fontAngle: 'Please set a value in the range of -360 to 360', + angle: 'Please set a value in the range of -360 to 360', + lineHeight: 'Please set a value in the range of 0 to 360', } as { [key: string]: string }; if (condition) return message[type]; From c312aa1ce670fd5c97c31d8af6615353862f94d0 Mon Sep 17 00:00:00 2001 From: ssi02014 Date: Wed, 5 Apr 2023 16:40:41 +0900 Subject: [PATCH 3/3] docs: README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 53777e1..b762532 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,7 @@ - Resizing the canvas - Filling the background with colors or pictures - Choosing a blur effect -- Selecting font family, size, stroke, color, and angle +- Selecting font family, size, stroke, color, angle, lineHeight - Dragging and dropping text on the canvas - Adding custom web font families - Selecting the modal button and its location