Skip to content

Commit

Permalink
text sets and fonts
Browse files Browse the repository at this point in the history
  • Loading branch information
RexanWONG committed Sep 15, 2024
1 parent 4b776bb commit 248470c
Show file tree
Hide file tree
Showing 9 changed files with 798 additions and 59 deletions.
502 changes: 502 additions & 0 deletions app/fonts.css

Large diffs are not rendered by default.

113 changes: 56 additions & 57 deletions components/editor/editor.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
'use client'

import React, { useEffect, useState } from 'react'
import Image from 'next/image'
import { Design } from '@/types'
import { removeBackground } from "@imgly/background-removal";
import SliderField from './slider-field';
import { removeBackground } from "@imgly/background-removal";
import { Accordion } from "@/components/ui/accordion"
import { Button } from '../ui/button';
import TextCustomizer from './text-customizer';

interface EditorProps {
design: Design[]
Expand All @@ -13,13 +13,17 @@ interface EditorProps {
const Editor: React.FC<EditorProps> = ({ design }) => {
const [isImageSetupDone, setIsImageSetupDone] = useState<boolean>(false)
const [removedBgImageUrl, setRemovedBgImageUrl] = useState<string | null>(null)
const [textAttributes, setTextAttributes] = useState({
top: 50,
left: 50,
color: 'white',
fontSize: 200,
fontWeight: 800,
})
const [textSets, setTextSets] = useState([
{
id: 1,
text: 'edit',
top: 0,
left: 0,
color: 'white',
fontSize: 200,
fontWeight: 800,
}
]);

const setupImage = async () => {
try {
Expand All @@ -39,8 +43,27 @@ const Editor: React.FC<EditorProps> = ({ design }) => {
setupImage()
}, [design]);

const handleAttributeChange = (attribute: string, value: any) => {
setTextAttributes(prev => ({ ...prev, [attribute]: value }))
const handleAttributeChange = (id: number, attribute: string, value: any) => {
setTextSets(prev => prev.map(set =>
set.id === id ? { ...set, [attribute]: value } : set
));
}

const addNewTextSet = () => {
const newId = Math.max(...textSets.map(set => set.id), 0) + 1;
setTextSets(prev => [...prev, {
id: newId,
text: 'edit',
top: 0,
left: 0,
color: 'white',
fontSize: 200,
fontWeight: 800,
}]);
}

const removeTextSet = (id: number) => {
setTextSets(prev => prev.filter(set => set.id !== id));
}

return (
Expand All @@ -54,25 +77,26 @@ const Editor: React.FC<EditorProps> = ({ design }) => {
objectPosition="center"
className={`${!isImageSetupDone ? 'animate-pulse' : ''}`}
/>
{isImageSetupDone && (
{isImageSetupDone && textSets.map(textSet => (
<div
key={textSet.id}
contentEditable
suppressContentEditableWarning
style={{
position: 'absolute',
top: `${50 - textAttributes.top}%`,
left: `${textAttributes.left + 50}%`,
top: `${50 - textSet.top}%`,
left: `${textSet.left + 50}%`,
transform: 'translate(-50%, -50%)',
color: textAttributes.color,
color: textSet.color,
textAlign: 'center',
fontSize: `${textAttributes.fontSize}px`,
fontWeight: textAttributes.fontWeight,
fontSize: `${textSet.fontSize}px`,
fontWeight: textSet.fontWeight,
textShadow: '2px 2px 4px rgba(0, 0, 0, 0.8)',
}}
>
POV
{textSet.text}
</div>
)}
))}
{removedBgImageUrl && (
<Image
src={removedBgImageUrl}
Expand All @@ -85,42 +109,17 @@ const Editor: React.FC<EditorProps> = ({ design }) => {
)}
</div>
<div className='flex flex-col w-full'>
<SliderField
attribute="fontSize"
label="Font Size"
min={10}
max={800}
step={1}
currentValue={textAttributes.fontSize}
handleAttributeChange={handleAttributeChange}
/>
<SliderField
attribute="fontWeight"
label="Font Weight"
min={100}
max={900}
step={1}
currentValue={textAttributes.fontWeight}
handleAttributeChange={handleAttributeChange}
/>
<SliderField
attribute="left"
label="X Position"
min={-200}
max={200}
step={1}
currentValue={textAttributes.left}
handleAttributeChange={handleAttributeChange}
/>
<SliderField
attribute="top"
label="Y Position"
min={-100}
max={100}
step={1}
currentValue={textAttributes.top}
handleAttributeChange={handleAttributeChange}
/>
<Button onClick={addNewTextSet}>Add New Text Set</Button>
<Accordion type="single" collapsible className="w-full mt-2">
{textSets.map(textSet => (
<TextCustomizer
key={textSet.id}
textSet={textSet}
handleAttributeChange={handleAttributeChange}
removeTextSet={removeTextSet}
/>
))}
</Accordion>
</div>
</div>
)
Expand Down
41 changes: 41 additions & 0 deletions components/editor/input-field.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use client'

import React from 'react';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';

interface InputFieldProps {
attribute: string;
label: string;
currentValue: string;
handleAttributeChange: (attribute: string, value: string) => void;
}

const InputField: React.FC<InputFieldProps> = ({
attribute,
label,
currentValue,
handleAttributeChange
}) => {
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
handleAttributeChange(attribute, value);
};

return (
<>
<div className="flex flex-col items-start">
{/* <Label htmlFor={attribute}>{label}</Label> */}
<Input
type="text"
placeholder='text'
value={currentValue}
onChange={handleInputChange}
className='mt-2'
/>
</div>
</>
);
};

export default InputField;
78 changes: 78 additions & 0 deletions components/editor/text-customizer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react';
import InputField from './input-field';
import SliderField from './slider-field';
import { Button } from '../ui/button';
import {
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"

interface TextCustomizerProps {
textSet: {
id: number;
text: string;
top: number;
left: number;
color: string;
fontSize: number;
fontWeight: number;
};
handleAttributeChange: (id: number, attribute: string, value: any) => void;
removeTextSet: (id: number) => void;
}

const TextCustomizer: React.FC<TextCustomizerProps> = ({ textSet, handleAttributeChange, removeTextSet }) => {
return (
<AccordionItem value={`item-${textSet.id}`}>
<AccordionTrigger>{textSet.text}</AccordionTrigger>
<AccordionContent className='p-1'>
<InputField
attribute="text"
label="Text"
currentValue={textSet.text}
handleAttributeChange={(attribute, value) => handleAttributeChange(textSet.id, attribute, value)}
/>
<SliderField
attribute="fontSize"
label="Font Size"
min={10}
max={800}
step={1}
currentValue={textSet.fontSize}
handleAttributeChange={(attribute, value) => handleAttributeChange(textSet.id, attribute, value)}
/>
<SliderField
attribute="fontWeight"
label="Font Weight"
min={100}
max={900}
step={100}
currentValue={textSet.fontWeight}
handleAttributeChange={(attribute, value) => handleAttributeChange(textSet.id, attribute, value)}
/>
<SliderField
attribute="left"
label="X Position"
min={-200}
max={200}
step={1}
currentValue={textSet.left}
handleAttributeChange={(attribute, value) => handleAttributeChange(textSet.id, attribute, value)}
/>
<SliderField
attribute="top"
label="Y Position"
min={-100}
max={100}
step={1}
currentValue={textSet.top}
handleAttributeChange={(attribute, value) => handleAttributeChange(textSet.id, attribute, value)}
/>
<Button onClick={() => removeTextSet(textSet.id)} variant="destructive" className='mt-8'>Remove Text Set</Button>
</AccordionContent>
</AccordionItem>
);
};

export default TextCustomizer;
58 changes: 58 additions & 0 deletions components/ui/accordion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"use client"

import * as React from "react"
import * as AccordionPrimitive from "@radix-ui/react-accordion"
import { ChevronDown } from "lucide-react"

import { cn } from "@/lib/utils"

const Accordion = AccordionPrimitive.Root

const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
))
AccordionItem.displayName = "AccordionItem"

const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
className
)}
{...props}
>
{children}
<ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
))
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName

const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
))

AccordionContent.displayName = AccordionPrimitive.Content.displayName

export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
2 changes: 1 addition & 1 deletion components/upload-image-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ const UploadImageButton: React.FC<UploadImageButtonProps> = ({ designId }) => {
Uploading image
</Button>
) : (
<Button variant={'secondary'} onClick={handleUploadImage}>
<Button onClick={handleUploadImage}>
Upload image
</Button>
)}
Expand Down
Loading

0 comments on commit 248470c

Please sign in to comment.