summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/properties-panel
diff options
context:
space:
mode:
authorMaze Winther <mazewinther@gmail.com>2025-08-27 00:52:39 +0200
committerMaze Winther <mazewinther@gmail.com>2025-08-27 00:52:39 +0200
commitadc8cb37fcec21afac848b1e5dd724394c344050 (patch)
tree9157bc1973840b00a96912d42f6741352abedbc4 /apps/web/src/components/editor/properties-panel
parentb459b46802a760a4a81ce91fdf24390e07375fb0 (diff)
downloadopencut-adc8cb37fcec21afac848b1e5dd724394c344050.tar.gz
another huge refactor
Diffstat (limited to 'apps/web/src/components/editor/properties-panel')
-rw-r--r--apps/web/src/components/editor/properties-panel/property-item.tsx4
-rw-r--r--apps/web/src/components/editor/properties-panel/text-properties.tsx412
2 files changed, 229 insertions, 187 deletions
diff --git a/apps/web/src/components/editor/properties-panel/property-item.tsx b/apps/web/src/components/editor/properties-panel/property-item.tsx
index dc6b6d74..f7d5dbda 100644
--- a/apps/web/src/components/editor/properties-panel/property-item.tsx
+++ b/apps/web/src/components/editor/properties-panel/property-item.tsx
@@ -57,6 +57,7 @@ interface PropertyGroupProps {
children: React.ReactNode;
defaultExpanded?: boolean;
className?: string;
+ titleClassName?: string;
}
export function PropertyGroup({
@@ -64,6 +65,7 @@ export function PropertyGroup({
children,
defaultExpanded = true,
className,
+ titleClassName,
}: PropertyGroupProps) {
const [isExpanded, setIsExpanded] = useState(defaultExpanded);
@@ -73,7 +75,7 @@ export function PropertyGroup({
className="flex items-center gap-1.5 cursor-pointer"
onClick={() => setIsExpanded(!isExpanded)}
>
- <PropertyItemLabel className="cursor-pointer">
+ <PropertyItemLabel className={cn("cursor-pointer", titleClassName)}>
{title}
</PropertyItemLabel>
<ChevronDown className={cn("size-3", !isExpanded && "-rotate-90")} />
diff --git a/apps/web/src/components/editor/properties-panel/text-properties.tsx b/apps/web/src/components/editor/properties-panel/text-properties.tsx
index 744c2be5..139c7005 100644
--- a/apps/web/src/components/editor/properties-panel/text-properties.tsx
+++ b/apps/web/src/components/editor/properties-panel/text-properties.tsx
@@ -6,8 +6,14 @@ import { useTimelineStore } from "@/stores/timeline-store";
import { Slider } from "@/components/ui/slider";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
-import { Switch } from "@/components/ui/switch"; // Add Switch import
+import { Switch } from "@/components/ui/switch";
import { useState, useRef } from "react";
+import { PanelBaseView } from "@/components/editor/panel-base-view";
+import {
+ TEXT_PROPERTIES_TABS,
+ isTextPropertiesTab,
+ useTextPropertiesStore,
+} from "@/stores/text-properties-store";
import {
PropertyItem,
PropertyItemLabel,
@@ -22,6 +28,7 @@ export function TextProperties({
trackId: string;
}) {
const { updateTextElement } = useTimelineStore();
+ const { activeTab, setActiveTab } = useTextPropertiesStore();
// Local state for input values to allow temporary empty/invalid states
const [fontSizeInput, setFontSizeInput] = useState(
@@ -105,199 +112,232 @@ export function TextProperties({
};
return (
- <div className="space-y-6 p-5">
- <Textarea
- placeholder="Name"
- defaultValue={element.content}
- className="min-h-18 resize-none bg-background/50"
- onChange={(e) =>
- updateTextElement(trackId, element.id, { content: e.target.value })
- }
- />
- <PropertyItem direction="row">
- <PropertyItemLabel>Font</PropertyItemLabel>
- <PropertyItemValue>
- <FontPicker
- defaultValue={element.fontFamily}
- onValueChange={(value: FontFamily) =>
- updateTextElement(trackId, element.id, { fontFamily: value })
- }
- />
- </PropertyItemValue>
- </PropertyItem>
- <PropertyItem direction="column">
- <PropertyItem direction="row">
- <PropertyItemLabel>Style</PropertyItemLabel>
- <PropertyItemValue>
- <div className="flex items-center gap-2">
- <Button
- variant={element.fontWeight === "bold" ? "default" : "outline"}
- size="sm"
- onClick={() =>
- updateTextElement(trackId, element.id, {
- fontWeight:
- element.fontWeight === "bold" ? "normal" : "bold",
- })
- }
- className="h-8 px-3 font-bold"
- >
- B
- </Button>
- <Button
- variant={element.fontStyle === "italic" ? "default" : "outline"}
- size="sm"
- onClick={() =>
- updateTextElement(trackId, element.id, {
- fontStyle:
- element.fontStyle === "italic" ? "normal" : "italic",
- })
- }
- className="h-8 px-3 italic"
- >
- I
- </Button>
- <Button
- variant={
- element.textDecoration === "underline" ? "default" : "outline"
- }
- size="sm"
- onClick={() =>
+ <PanelBaseView
+ defaultTab="transform"
+ value={activeTab}
+ onValueChange={(v) => {
+ if (isTextPropertiesTab(v)) setActiveTab(v);
+ }}
+ tabs={TEXT_PROPERTIES_TABS.map((t) => ({
+ value: t.value,
+ label: t.label,
+ content:
+ t.value === "transform" ? (
+ <div className="space-y-6"></div>
+ ) : (
+ <div className="space-y-6">
+ <Textarea
+ placeholder="Name"
+ defaultValue={element.content}
+ className="min-h-18 resize-none bg-background/50"
+ onChange={(e) =>
updateTextElement(trackId, element.id, {
- textDecoration:
- element.textDecoration === "underline"
- ? "none"
- : "underline",
+ content: e.target.value,
})
}
- className="h-8 px-3 underline"
- >
- U
- </Button>
- <Button
- variant={
- element.textDecoration === "line-through"
- ? "default"
- : "outline"
- }
- size="sm"
- onClick={() =>
- updateTextElement(trackId, element.id, {
- textDecoration:
- element.textDecoration === "line-through"
- ? "none"
- : "line-through",
- })
- }
- className="h-8 px-3 line-through"
- >
- S
- </Button>
- </div>
- </PropertyItemValue>
- </PropertyItem>
- <PropertyItemLabel>Font size</PropertyItemLabel>
- <PropertyItemValue>
- <div className="flex items-center gap-2">
- <Slider
- value={[element.fontSize]}
- min={8}
- max={300}
- step={1}
- onValueChange={([value]) => {
- updateTextElement(trackId, element.id, { fontSize: value });
- setFontSizeInput(value.toString());
- }}
- className="w-full"
- />
- <Input
- type="number"
- value={fontSizeInput}
- min={8}
- max={300}
- onChange={(e) => handleFontSizeChange(e.target.value)}
- onBlur={handleFontSizeBlur}
- className="w-12 !text-xs h-7 rounded-sm text-center
+ />
+ <PropertyItem direction="row">
+ <PropertyItemLabel>Font</PropertyItemLabel>
+ <PropertyItemValue>
+ <FontPicker
+ defaultValue={element.fontFamily}
+ onValueChange={(value: FontFamily) =>
+ updateTextElement(trackId, element.id, {
+ fontFamily: value,
+ })
+ }
+ />
+ </PropertyItemValue>
+ </PropertyItem>
+ <PropertyItem direction="column">
+ <PropertyItem direction="row">
+ <PropertyItemLabel>Style</PropertyItemLabel>
+ <PropertyItemValue>
+ <div className="flex items-center gap-2">
+ <Button
+ variant={
+ element.fontWeight === "bold" ? "default" : "outline"
+ }
+ size="sm"
+ onClick={() =>
+ updateTextElement(trackId, element.id, {
+ fontWeight:
+ element.fontWeight === "bold" ? "normal" : "bold",
+ })
+ }
+ className="h-8 px-3 font-bold"
+ >
+ B
+ </Button>
+ <Button
+ variant={
+ element.fontStyle === "italic" ? "default" : "outline"
+ }
+ size="sm"
+ onClick={() =>
+ updateTextElement(trackId, element.id, {
+ fontStyle:
+ element.fontStyle === "italic"
+ ? "normal"
+ : "italic",
+ })
+ }
+ className="h-8 px-3 italic"
+ >
+ I
+ </Button>
+ <Button
+ variant={
+ element.textDecoration === "underline"
+ ? "default"
+ : "outline"
+ }
+ size="sm"
+ onClick={() =>
+ updateTextElement(trackId, element.id, {
+ textDecoration:
+ element.textDecoration === "underline"
+ ? "none"
+ : "underline",
+ })
+ }
+ className="h-8 px-3 underline"
+ >
+ U
+ </Button>
+ <Button
+ variant={
+ element.textDecoration === "line-through"
+ ? "default"
+ : "outline"
+ }
+ size="sm"
+ onClick={() =>
+ updateTextElement(trackId, element.id, {
+ textDecoration:
+ element.textDecoration === "line-through"
+ ? "none"
+ : "line-through",
+ })
+ }
+ className="h-8 px-3 line-through"
+ >
+ S
+ </Button>
+ </div>
+ </PropertyItemValue>
+ </PropertyItem>
+ <PropertyItemLabel>Font size</PropertyItemLabel>
+ <PropertyItemValue>
+ <div className="flex items-center gap-2">
+ <Slider
+ value={[element.fontSize]}
+ min={8}
+ max={300}
+ step={1}
+ onValueChange={([value]) => {
+ updateTextElement(trackId, element.id, {
+ fontSize: value,
+ });
+ setFontSizeInput(value.toString());
+ }}
+ className="w-full"
+ />
+ <Input
+ type="number"
+ value={fontSizeInput}
+ min={8}
+ max={300}
+ onChange={(e) => handleFontSizeChange(e.target.value)}
+ onBlur={handleFontSizeBlur}
+ className="w-12 !text-xs h-7 rounded-sm text-center
[appearance:textfield]
[&::-webkit-outer-spin-button]:appearance-none
[&::-webkit-inner-spin-button]:appearance-none"
- />
- </div>
- </PropertyItemValue>
- </PropertyItem>
- <PropertyItem direction="row">
- <PropertyItemLabel>Color</PropertyItemLabel>
- <PropertyItemValue>
- <Input
- type="color"
- value={element.color || "#ffffff"}
- onChange={(e) => {
- const color = e.target.value;
- updateTextElement(trackId, element.id, { color });
- }}
- className="w-full cursor-pointer rounded-full"
- />
- </PropertyItemValue>
- </PropertyItem>
- <PropertyItem direction="column">
- <div className="flex items-center justify-between">
- <PropertyItemLabel>Background</PropertyItemLabel>
- <div className="flex items-center space-x-2">
- <Switch
- id="transparent-bg-toggle"
- checked={element.backgroundColor === "transparent"}
- onCheckedChange={handleTransparentToggle}
- />
- <label htmlFor="transparent-bg-toggle" className="text-sm font-medium">
- Transparent
- </label>
- </div>
- </div>
- <PropertyItemValue>
- <Input
- type="color"
- value={
- element.backgroundColor === "transparent"
- ? lastSelectedColor.current
- : element.backgroundColor || "#000000"
- }
- onChange={(e) => handleColorChange(e.target.value)}
- className="w-full cursor-pointer rounded-full"
- disabled={element.backgroundColor === "transparent"}
- />
- </PropertyItemValue>
- </PropertyItem>
- <PropertyItem direction="column">
- <PropertyItemLabel>Opacity</PropertyItemLabel>
- <PropertyItemValue>
- <div className="flex items-center gap-2">
- <Slider
- value={[element.opacity * 100]}
- min={0}
- max={100}
- step={1}
- onValueChange={([value]) => {
- updateTextElement(trackId, element.id, {
- opacity: value / 100,
- });
- setOpacityInput(value.toString());
- }}
- className="w-full"
- />
- <Input
- type="number"
- value={opacityInput}
- min={0}
- max={100}
- onChange={(e) => handleOpacityChange(e.target.value)}
- onBlur={handleOpacityBlur}
- className="w-12 !text-xs h-7 rounded-sm text-center
+ />
+ </div>
+ </PropertyItemValue>
+ </PropertyItem>
+ <PropertyItem direction="row">
+ <PropertyItemLabel>Color</PropertyItemLabel>
+ <PropertyItemValue>
+ <Input
+ type="color"
+ value={element.color || "#ffffff"}
+ onChange={(e) => {
+ const color = e.target.value;
+ updateTextElement(trackId, element.id, { color });
+ }}
+ className="w-full cursor-pointer rounded-full"
+ />
+ </PropertyItemValue>
+ </PropertyItem>
+ <PropertyItem direction="column">
+ <PropertyItemLabel>Opacity</PropertyItemLabel>
+ <PropertyItemValue>
+ <div className="flex items-center gap-2">
+ <Slider
+ value={[element.opacity * 100]}
+ min={0}
+ max={100}
+ step={1}
+ onValueChange={([value]) => {
+ updateTextElement(trackId, element.id, {
+ opacity: value / 100,
+ });
+ setOpacityInput(value.toString());
+ }}
+ className="w-full"
+ />
+ <Input
+ type="number"
+ value={opacityInput}
+ min={0}
+ max={100}
+ onChange={(e) => handleOpacityChange(e.target.value)}
+ onBlur={handleOpacityBlur}
+ className="w-12 !text-xs h-7 rounded-sm text-center
[appearance:textfield]
[&::-webkit-outer-spin-button]:appearance-none
[&::-webkit-inner-spin-button]:appearance-none"
- />
- </div>
- </PropertyItemValue>
- </PropertyItem>
- </div>
+ />
+ </div>
+ </PropertyItemValue>
+ </PropertyItem>
+ <PropertyItem direction="column">
+ <div className="flex items-center justify-between">
+ <PropertyItemLabel>Background</PropertyItemLabel>
+ <div className="flex items-center space-x-2">
+ <Switch
+ id="transparent-bg-toggle"
+ checked={element.backgroundColor === "transparent"}
+ onCheckedChange={handleTransparentToggle}
+ />
+ <label
+ htmlFor="transparent-bg-toggle"
+ className="text-sm font-medium"
+ >
+ Transparent
+ </label>
+ </div>
+ </div>
+ <PropertyItemValue>
+ <Input
+ type="color"
+ value={
+ element.backgroundColor === "transparent"
+ ? lastSelectedColor.current
+ : element.backgroundColor || "#000000"
+ }
+ onChange={(e) => handleColorChange(e.target.value)}
+ className="w-full cursor-pointer rounded-full"
+ disabled={element.backgroundColor === "transparent"}
+ />
+ </PropertyItemValue>
+ </PropertyItem>
+ </div>
+ ),
+ }))}
+ />
);
}