mirror of
https://github.com/r2r90/canvas-label.git
synced 2025-12-17 05:29:27 +00:00
text edit finition
This commit is contained in:
@@ -2,10 +2,7 @@
|
|||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import fonts from "../assets/fonts.json";
|
import fonts from "../assets/fonts.json";
|
||||||
import {
|
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
||||||
CaretSortIcon,
|
|
||||||
CheckIcon,
|
|
||||||
} from "@radix-ui/react-icons";
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
@@ -26,15 +23,14 @@ import {
|
|||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
|
||||||
type PopoverTriggerProps = React.ComponentPropsWithoutRef<
|
type Props = {
|
||||||
typeof PopoverTrigger
|
handleTextFontFamilyChange: (fontFamiy: string | undefined) => void;
|
||||||
>;
|
selectedFont: string;
|
||||||
|
};
|
||||||
|
|
||||||
type Props = PopoverTriggerProps;
|
export function FontFamilyPicker({ handleTextFontFamilyChange, selectedFont }: Props) {
|
||||||
|
|
||||||
export function FontFamilyPicker({}: Props) {
|
|
||||||
const [open, setOpen] = React.useState(false);
|
const [open, setOpen] = React.useState(false);
|
||||||
const [selectedFont, setSelectedFont] = React.useState<string>("Roboto");
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fonts.items.map((font): void => {
|
fonts.items.map((font): void => {
|
||||||
@@ -73,8 +69,8 @@ export function FontFamilyPicker({}: Props) {
|
|||||||
<CommandItem
|
<CommandItem
|
||||||
key={font.family}
|
key={font.family}
|
||||||
onSelect={() => {
|
onSelect={() => {
|
||||||
setSelectedFont(font.family);
|
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
|
handleTextFontFamilyChange(font.family);
|
||||||
}}
|
}}
|
||||||
className="text-sm"
|
className="text-sm"
|
||||||
style={{ fontFamily: font.family }}
|
style={{ fontFamily: font.family }}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export function FontSizeSelector({ value, fontSizeHandler }: FontSizeSelector) {
|
|||||||
<Slider
|
<Slider
|
||||||
id="fontSize"
|
id="fontSize"
|
||||||
min={1}
|
min={1}
|
||||||
max={48}
|
max={100}
|
||||||
defaultValue={[value ?? 16]}
|
defaultValue={[value ?? 16]}
|
||||||
step={1}
|
step={1}
|
||||||
onValueChange={(e) => {
|
onValueChange={(e) => {
|
||||||
@@ -33,13 +33,6 @@ export function FontSizeSelector({ value, fontSizeHandler }: FontSizeSelector) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
{/* <HoverCardContent
|
|
||||||
align="start"
|
|
||||||
className="w-[260px] text-sm"
|
|
||||||
side="left"
|
|
||||||
>
|
|
||||||
Select font
|
|
||||||
</HoverCardContent> */}
|
|
||||||
</HoverCard>
|
</HoverCard>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
50
src/components/spacing-settings.tsx
Normal file
50
src/components/spacing-settings.tsx
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
import { GiSettingsKnobs } from "react-icons/gi";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { Input } from "./ui/input";
|
||||||
|
import { Label } from "./ui/label";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
|
||||||
|
import { Slider } from "./ui/slider";
|
||||||
|
|
||||||
|
export const SpacingSettings = () => {
|
||||||
|
return (
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button variant="outline">
|
||||||
|
<GiSettingsKnobs />
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-80">
|
||||||
|
<div className="grid gap-4">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<div className="grid grid-cols-3 items-center gap-4">
|
||||||
|
<Label htmlFor="width">Width</Label>
|
||||||
|
<Slider
|
||||||
|
id="fontSize"
|
||||||
|
min={1}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
|
||||||
|
className="[&_[role=slider]]:h-4 [&_[role=slider]]:w-4"
|
||||||
|
aria-label="fontSize"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-3 items-center gap-4">
|
||||||
|
<Label htmlFor="maxWidth">Letter Spacing</Label>
|
||||||
|
<Slider
|
||||||
|
id="fontSize"
|
||||||
|
min={1}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
|
||||||
|
className="[&_[role=slider]]:h-4 [&_[role=slider]]:w-4"
|
||||||
|
aria-label="fontSize"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import { useAppDispatch, useAppSelector } from "@/hooks";
|
import { useAppDispatch, useAppSelector } from "@/hooks";
|
||||||
import { updateText } from "@/store/app.slice";
|
import { updateText } from "@/store/app.slice";
|
||||||
import { Toggle } from "@/components/ui/toggle";
|
import { Toggle } from "@/components/ui/toggle";
|
||||||
import { Select } from "@/components/ui/select";
|
|
||||||
import { FontFamilyPicker } from "./font-family-picker";
|
import { FontFamilyPicker } from "./font-family-picker";
|
||||||
import { FontSizeSelector } from "./font-size-selector";
|
import { FontSizeSelector } from "./font-size-selector";
|
||||||
|
import { useState, type ChangeEvent } from "react";
|
||||||
|
import { SpacingSettings } from "./spacing-settings";
|
||||||
|
|
||||||
export const Toolbar = () => {
|
export const Toolbar = () => {
|
||||||
|
const [alignment, setAlignment] = useState("left");
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const selectedItemId = useAppSelector((state) => state.app.selectedItemId);
|
const selectedItemId = useAppSelector((state) => state.app.selectedItemId);
|
||||||
const texts = useAppSelector((state) => state.app.texts);
|
const texts = useAppSelector((state) => state.app.texts);
|
||||||
@@ -15,9 +17,10 @@ export const Toolbar = () => {
|
|||||||
if (!currentText) return null;
|
if (!currentText) return null;
|
||||||
|
|
||||||
const handleFontStyleToggle = (button: "bold" | "italic") => () => {
|
const handleFontStyleToggle = (button: "bold" | "italic") => () => {
|
||||||
|
if (!selectedItemId) return;
|
||||||
dispatch(
|
dispatch(
|
||||||
updateText({
|
updateText({
|
||||||
...currentText,
|
id: selectedItemId,
|
||||||
fontStyle: getFontStyle(currentText.fontStyle, button),
|
fontStyle: getFontStyle(currentText.fontStyle, button),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -25,23 +28,57 @@ export const Toolbar = () => {
|
|||||||
|
|
||||||
const handleTextDecorationToggle =
|
const handleTextDecorationToggle =
|
||||||
(button: "underline" | "line-through") => () => {
|
(button: "underline" | "line-through") => () => {
|
||||||
|
if (!selectedItemId) return;
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
updateText({
|
updateText({
|
||||||
...currentText,
|
id: selectedItemId,
|
||||||
textDecoration: getFontStyle(currentText.textDecoration, button),
|
textDecoration: getFontStyle(currentText.textDecoration, button),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTextFontSizeChange = (e: number | undefined) => {
|
const handleTextFontSizeChange = (e: number | undefined) => {
|
||||||
|
if (!selectedItemId) return;
|
||||||
dispatch(
|
dispatch(
|
||||||
updateText({
|
updateText({
|
||||||
...currentText,
|
id: selectedItemId,
|
||||||
fontSize: e,
|
fontSize: e,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTextFontFamilyChange = (e: string | undefined) => {
|
||||||
|
if (!selectedItemId) return;
|
||||||
|
dispatch(
|
||||||
|
updateText({
|
||||||
|
id: selectedItemId,
|
||||||
|
fontFamily: e,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const handleTextColorChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (!selectedItemId) return;
|
||||||
|
dispatch(
|
||||||
|
updateText({
|
||||||
|
id: selectedItemId,
|
||||||
|
fill: e.target.value,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTextAlignChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (!selectedItemId) return;
|
||||||
|
dispatch(
|
||||||
|
updateText({
|
||||||
|
id: selectedItemId,
|
||||||
|
align: e.target.value,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAlignmentChange = (newAlignment: string | undefined) => {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex h-[5rem] w-full gap-6 border bg-white p-[1rem] transition">
|
<div className="flex h-[5rem] w-full gap-6 border bg-white p-[1rem] transition">
|
||||||
<Toggle
|
<Toggle
|
||||||
@@ -73,8 +110,22 @@ export const Toolbar = () => {
|
|||||||
value={currentText.fontSize ?? 16}
|
value={currentText.fontSize ?? 16}
|
||||||
fontSizeHandler={handleTextFontSizeChange}
|
fontSizeHandler={handleTextFontSizeChange}
|
||||||
/>
|
/>
|
||||||
<input type="color" />
|
<input
|
||||||
<FontFamilyPicker />
|
type="color"
|
||||||
|
onChange={handleTextColorChange}
|
||||||
|
value={currentText.fill}
|
||||||
|
/>
|
||||||
|
<FontFamilyPicker
|
||||||
|
handleTextFontFamilyChange={handleTextFontFamilyChange}
|
||||||
|
selectedFont={currentText.fontFamily}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Toggle
|
||||||
|
className="data-[state=on]:font-bold"
|
||||||
|
onClick={handleAlignmentChange("left")}
|
||||||
|
></Toggle>
|
||||||
|
|
||||||
|
<SpacingSettings />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ type TransformableTextConfig = Omit<TextConfig, "text"> & {
|
|||||||
text?: TextConfig["text"];
|
text?: TextConfig["text"];
|
||||||
id: string;
|
id: string;
|
||||||
direction?: string;
|
direction?: string;
|
||||||
fontFamily?: string;
|
fontFamily: string;
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
fontStyle?: string;
|
fontStyle?: string;
|
||||||
fontVariant?: string;
|
fontVariant?: string;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import type { TransformableImageProps } from "@/components/transformable-image";
|
import type { TransformableImageProps } from "@/components/transformable-image";
|
||||||
import type { TransformableTextProps } from "@/components/transformable-text";
|
import type { TransformableTextProps } from "@/components/transformable-text";
|
||||||
import type { PayloadAction } from "@reduxjs/toolkit";
|
import type { PayloadAction } from "@reduxjs/toolkit";
|
||||||
import type { KonvaEventObject } from "konva/lib/Node";
|
|
||||||
import type { ChangeEvent } from "react";
|
import type { ChangeEvent } from "react";
|
||||||
|
|
||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createSlice } from "@reduxjs/toolkit";
|
||||||
import { v1 } from "uuid";
|
import { v1 } from "uuid";
|
||||||
import { TextConfig } from "konva/lib/shapes/Text";
|
import type { TextConfig } from "konva/lib/shapes/Text";
|
||||||
|
import { RootState } from "./store";
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
selectedItemId: null as string | null,
|
selectedItemId: null as string | null,
|
||||||
@@ -14,9 +14,12 @@ const initialState = {
|
|||||||
texts: [] as TransformableTextProps["textProps"][],
|
texts: [] as TransformableTextProps["textProps"][],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type InitialState = typeof initialState;
|
||||||
|
|
||||||
const defaultTextConfig = {
|
const defaultTextConfig = {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
align: "center",
|
align: "center",
|
||||||
|
fontFamily: "Roboto",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const appSlice = createSlice({
|
export const appSlice = createSlice({
|
||||||
@@ -51,40 +54,22 @@ export const appSlice = createSlice({
|
|||||||
|
|
||||||
updateText: (
|
updateText: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<Omit<TextConfig, "id"> & { id: string }>,
|
action: PayloadAction<{ id: string } & Partial<TextConfig>>,
|
||||||
) => {
|
) => {
|
||||||
const textToUpdate = state.texts.findIndex(
|
const textToUpdateIndex = state.texts.findIndex(
|
||||||
(t) => t.id === action.payload.id,
|
(t) => t.id === action.payload.id,
|
||||||
);
|
);
|
||||||
state.texts[textToUpdate] = action.payload;
|
const textToUpdate = state.texts[textToUpdateIndex];
|
||||||
},
|
|
||||||
updateTextFontSize: (
|
|
||||||
state,
|
|
||||||
action: PayloadAction<Omit<TextConfig, "id"> & { id: string }>,
|
|
||||||
) => {
|
|
||||||
const textToUpdate = state.texts.findIndex(
|
|
||||||
(t) => t.id === action.payload.id,
|
|
||||||
);
|
|
||||||
state.texts[textToUpdate] = action.payload;
|
|
||||||
},
|
|
||||||
|
|
||||||
// updateTextFontFamily: (
|
if (!textToUpdate) return;
|
||||||
// state,
|
|
||||||
// action: PayloadAction<Omit<TextConfig, "id"> & { id: string }>,
|
state.texts[textToUpdateIndex] = {
|
||||||
// ) => {
|
...textToUpdate,
|
||||||
// const textToUpdate = state.texts.findIndex(
|
...action.payload,
|
||||||
// (t) => t.id === action.payload.id,
|
};
|
||||||
// );
|
},
|
||||||
// state.texts[textToUpdate] = action.payload;
|
|
||||||
// },
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const {
|
export const { addImage, addText, selectItem, deselectItem, updateText } =
|
||||||
addImage,
|
appSlice.actions;
|
||||||
addText,
|
|
||||||
selectItem,
|
|
||||||
deselectItem,
|
|
||||||
updateText,
|
|
||||||
updateTextFontSize,
|
|
||||||
} = appSlice.actions;
|
|
||||||
|
|||||||
9
todo.md
9
todo.md
@@ -1,2 +1,7 @@
|
|||||||
[ ] image toolbar
|
Property 'PopoverTriggerProps' is missing in type '{ handleTextFontFamilyChange: (e: string | undefined) => void; }' but required in type 'Props'.ts(2741)
|
||||||
[ ] text toolbar
|
font-family-picker.tsx(31, 3): 'PopoverTriggerProps' is declared here.
|
||||||
|
(alias) function FontFamilyPicker({ handleTextFontFamilyChange }: Props): React.JSX.Element
|
||||||
|
import FontFamilyPicker
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user