mirror of
https://github.com/r2r90/canvas-label.git
synced 2025-12-17 21:19:39 +00:00
text editing finitions - text align option
This commit is contained in:
@@ -14,10 +14,18 @@
|
|||||||
"@headlessui/react": "^1.7.17",
|
"@headlessui/react": "^1.7.17",
|
||||||
"@next-auth/prisma-adapter": "^1.0.7",
|
"@next-auth/prisma-adapter": "^1.0.7",
|
||||||
"@prisma/client": "^5.1.1",
|
"@prisma/client": "^5.1.1",
|
||||||
|
"@radix-ui/react-avatar": "^1.0.4",
|
||||||
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
|
"@radix-ui/react-hover-card": "^1.0.7",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
"@radix-ui/react-slider": "^1.1.2",
|
"@radix-ui/react-slider": "^1.1.2",
|
||||||
"@radix-ui/react-toggle": "^1.0.3",
|
"@radix-ui/react-toggle": "^1.0.3",
|
||||||
|
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||||
"@radix-ui/themes": "^2.0.0",
|
"@radix-ui/themes": "^2.0.0",
|
||||||
"@reduxjs/toolkit": "^1.9.6",
|
"@reduxjs/toolkit": "^1.9.6",
|
||||||
"@remotion/google-fonts": "^4.0.51",
|
"@remotion/google-fonts": "^4.0.51",
|
||||||
|
|||||||
1557
pnpm-lock.yaml
generated
1557
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,39 +0,0 @@
|
|||||||
import { HoverCard, HoverCardTrigger } from "./ui/hover-card";
|
|
||||||
import { Label } from "./ui/label";
|
|
||||||
import { Slider } from "./ui/slider";
|
|
||||||
|
|
||||||
interface FontSizeSelector {
|
|
||||||
value: number;
|
|
||||||
fontSizeHandler: (e: number | undefined) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FontSizeSelector({ value, fontSizeHandler }: FontSizeSelector) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<HoverCard openDelay={200}>
|
|
||||||
<HoverCardTrigger asChild>
|
|
||||||
<div className="grid gap-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<Label htmlFor="fontSize">Font Size</Label>
|
|
||||||
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-muted-foreground hover:border-border">
|
|
||||||
{value}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<Slider
|
|
||||||
id="fontSize"
|
|
||||||
min={1}
|
|
||||||
max={100}
|
|
||||||
defaultValue={[value ?? 16]}
|
|
||||||
step={1}
|
|
||||||
onValueChange={(e) => {
|
|
||||||
fontSizeHandler(e[0]);
|
|
||||||
}}
|
|
||||||
className="[&_[role=slider]]:h-4 [&_[role=slider]]:w-4"
|
|
||||||
aria-label="fontSize"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</HoverCardTrigger>
|
|
||||||
</HoverCard>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import { Input } from "../ui/input";
|
|||||||
import { useAppDispatch } from "@/hooks";
|
import { useAppDispatch } from "@/hooks";
|
||||||
import { ChangeEvent, useState } from "react";
|
import { ChangeEvent, useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
@@ -18,12 +17,10 @@ export function Sidebar() {
|
|||||||
setInputText(e.target.value);
|
setInputText(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTextAdd = () => dispatch(addText({ initialValue: inputText }));
|
||||||
|
|
||||||
const handleTextAdd = () => dispatch(addText({initialValue: inputText}));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex flex-col ">
|
<div className="flex h-full max-w-[20rem] flex-col">
|
||||||
<Input
|
<Input
|
||||||
type="file"
|
type="file"
|
||||||
className="m-[2rem] w-auto "
|
className="m-[2rem] w-auto "
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
52
src/components/texte-editing-tools/text-align-selector.tsx
Normal file
52
src/components/texte-editing-tools/text-align-selector.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import {
|
||||||
|
AiOutlineAlignCenter,
|
||||||
|
AiOutlineAlignLeft,
|
||||||
|
AiOutlineAlignRight,
|
||||||
|
} from "react-icons/ai";
|
||||||
|
import type { TextConfig } from "konva/lib/shapes/Text";
|
||||||
|
import { updateText } from "@/store/app.slice";
|
||||||
|
import { useAppDispatch } from "@/hooks";
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
currentText: TextConfig;
|
||||||
|
selectedItemId: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FontAlign = ({ currentText, selectedItemId }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const handleTextAlignChange = (position: string) => {
|
||||||
|
if (!selectedItemId) return;
|
||||||
|
dispatch(
|
||||||
|
updateText({
|
||||||
|
id: selectedItemId,
|
||||||
|
align: position,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Tabs
|
||||||
|
value={currentText.align}
|
||||||
|
className="flex-1"
|
||||||
|
onValueChange={(value: string) => {
|
||||||
|
if (value) handleTextAlignChange(value);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<TabsList className="grid grid-cols-3">
|
||||||
|
<TabsTrigger value="left">
|
||||||
|
<AiOutlineAlignLeft />
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="center">
|
||||||
|
<AiOutlineAlignCenter />
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="right">
|
||||||
|
<AiOutlineAlignRight />
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
</div>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import fonts from "../assets/fonts.json";
|
import fonts from "../../assets/fonts.json";
|
||||||
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -21,16 +21,29 @@ import {
|
|||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
|
|
||||||
import { useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
import { updateText } from "@/store/app.slice";
|
||||||
|
import { useAppDispatch } from "@/hooks";
|
||||||
|
import type { TextConfig } from "konva/lib/shapes/Text";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
handleTextFontFamilyChange: (fontFamiy: string | undefined) => void;
|
currentText: TextConfig;
|
||||||
selectedFont: string;
|
selectedItemId: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function FontFamilyPicker({ handleTextFontFamilyChange, selectedFont }: Props) {
|
export function FontFamilyPicker({ currentText, selectedItemId }: Props) {
|
||||||
const [open, setOpen] = React.useState(false);
|
const dispatch = useAppDispatch();
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
|
const handleTextFontFamilyChange = (e: string | undefined) => {
|
||||||
|
if (!selectedItemId) return;
|
||||||
|
dispatch(
|
||||||
|
updateText({
|
||||||
|
id: selectedItemId,
|
||||||
|
fontFamily: e,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fonts.items.map((font): void => {
|
fonts.items.map((font): void => {
|
||||||
@@ -45,6 +58,8 @@ export function FontFamilyPicker({ handleTextFontFamilyChange, selectedFont }: P
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const selectedFont = currentText.fontFamily ?? "Roboto";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
55
src/components/texte-editing-tools/text-size-selector.tsx
Normal file
55
src/components/texte-editing-tools/text-size-selector.tsx
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import { HoverCard, HoverCardTrigger } from "@/components/ui/hover-card";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Slider } from "@/components/ui/slider";
|
||||||
|
import { updateText } from "@/store/app.slice";
|
||||||
|
import { useAppDispatch } from "@/hooks";
|
||||||
|
import type { TextConfig } from "konva/lib/shapes/Text";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
currentText: TextConfig;
|
||||||
|
selectedItemId: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FontSizeSelector({ currentText, selectedItemId }: Props) {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const value = currentText.fontSize ?? 16;
|
||||||
|
|
||||||
|
const handleTextFontSizeChange = (e: number | undefined) => {
|
||||||
|
if (!selectedItemId) return;
|
||||||
|
dispatch(
|
||||||
|
updateText({
|
||||||
|
id: selectedItemId,
|
||||||
|
fontSize: e,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HoverCard openDelay={200}>
|
||||||
|
<HoverCardTrigger asChild>
|
||||||
|
<div className="grid gap-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<Label htmlFor="fontSize">Font Size</Label>
|
||||||
|
<span className="w-12 rounded-md border border-transparent px-2 py-0.5 text-right text-sm text-muted-foreground hover:border-border">
|
||||||
|
{value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Slider
|
||||||
|
id="fontSize"
|
||||||
|
min={1}
|
||||||
|
max={100}
|
||||||
|
defaultValue={[currentText.fontSize ?? 16]}
|
||||||
|
step={1}
|
||||||
|
onValueChange={(e) => {
|
||||||
|
handleTextFontSizeChange(e[0]);
|
||||||
|
}}
|
||||||
|
className="[&_[role=slider]]:h-4 [&_[role=slider]]:w-4"
|
||||||
|
aria-label="fontSize"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</HoverCardTrigger>
|
||||||
|
</HoverCard>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
78
src/components/texte-editing-tools/text-spacing-settings.tsx
Normal file
78
src/components/texte-editing-tools/text-spacing-settings.tsx
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { GiSettingsKnobs } from "react-icons/gi";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { Label } from "../ui/label";
|
||||||
|
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
|
||||||
|
import { Slider } from "../ui/slider";
|
||||||
|
import type { TextConfig } from "konva/lib/shapes/Text";
|
||||||
|
import { useAppDispatch } from "@/hooks";
|
||||||
|
import { updateText } from "@/store/app.slice";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
currentText: TextConfig;
|
||||||
|
selectedItemId: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SpacingSettings = ({ currentText, selectedItemId }: Props) => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleTextLetterSpacing = (e: number | undefined) => {
|
||||||
|
if (!selectedItemId) return;
|
||||||
|
dispatch(
|
||||||
|
updateText({
|
||||||
|
id: selectedItemId,
|
||||||
|
letterSpacing: e,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLineHeight = (e: number | undefined) => {
|
||||||
|
if (!selectedItemId) return;
|
||||||
|
dispatch(
|
||||||
|
updateText({
|
||||||
|
id: selectedItemId,
|
||||||
|
lineHeight: e,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
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">Letter Spacing</Label>
|
||||||
|
<Slider
|
||||||
|
id="fontSize"
|
||||||
|
defaultValue={[currentText.letterSpacing ?? 0]}
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={0.1}
|
||||||
|
className="[&_[role=slider]]:h-4 [&_[role=slider]]:w-4"
|
||||||
|
aria-label="letterSpacing"
|
||||||
|
onValueChange={(e) => handleTextLetterSpacing(e[0])}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-3 items-center gap-4">
|
||||||
|
<Label htmlFor="maxWidth">Line Height</Label>
|
||||||
|
<Slider
|
||||||
|
id="fontSize"
|
||||||
|
defaultValue={[currentText.lineHeight ?? 1]}
|
||||||
|
min={1}
|
||||||
|
max={10}
|
||||||
|
step={1}
|
||||||
|
className="[&_[role=slider]]:h-4 [&_[role=slider]]:w-4"
|
||||||
|
aria-label="lineHeight"
|
||||||
|
onValueChange={(e) => handleLineHeight(e[0])}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
117
src/components/texte-editing-tools/text-style-selector.tsx
Normal file
117
src/components/texte-editing-tools/text-style-selector.tsx
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
import { Toggle } from "../ui/toggle";
|
||||||
|
import { updateText } from "@/store/app.slice";
|
||||||
|
import type { TextConfig } from "konva/lib/shapes/Text";
|
||||||
|
import {
|
||||||
|
FontBoldIcon,
|
||||||
|
FontItalicIcon,
|
||||||
|
StrikethroughIcon,
|
||||||
|
UnderlineIcon,
|
||||||
|
} from "@radix-ui/react-icons";
|
||||||
|
import { Separator } from "../ui/separator";
|
||||||
|
import { useAppDispatch } from "@/hooks";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
currentText: TextConfig;
|
||||||
|
selectedItemId: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function FontStyle({ currentText, selectedItemId }: Props) {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleFontStyleToggle = (button: "bold" | "italic") => () => {
|
||||||
|
if (!selectedItemId) return;
|
||||||
|
dispatch(
|
||||||
|
updateText({
|
||||||
|
id: selectedItemId,
|
||||||
|
fontStyle: getFontStyle(currentText.fontStyle, button),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTextDecorationToggle =
|
||||||
|
(button: "underline" | "line-through") => () => {
|
||||||
|
if (!selectedItemId) return;
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
updateText({
|
||||||
|
id: selectedItemId,
|
||||||
|
textDecoration: getFontStyle(currentText.textDecoration, button),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Toggle
|
||||||
|
aria-label="Toggle italic"
|
||||||
|
onClick={handleFontStyleToggle("bold")}
|
||||||
|
value={currentText.fontStyle}
|
||||||
|
>
|
||||||
|
<FontBoldIcon className="mr-2 h-4 w-4" />
|
||||||
|
Bold
|
||||||
|
</Toggle>
|
||||||
|
|
||||||
|
<Toggle
|
||||||
|
aria-label="Toggle italic"
|
||||||
|
onClick={handleFontStyleToggle("italic")}
|
||||||
|
value={currentText.fontStyle}
|
||||||
|
>
|
||||||
|
<FontItalicIcon className="mr-2 h-4 w-4" />
|
||||||
|
Italic
|
||||||
|
</Toggle>
|
||||||
|
|
||||||
|
<Separator
|
||||||
|
orientation="vertical"
|
||||||
|
className="mx-2 my-auto h-[2rem] items-center "
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Toggle
|
||||||
|
aria-label="Toggle italic"
|
||||||
|
onClick={handleTextDecorationToggle("underline")}
|
||||||
|
value={currentText.textDecoration}
|
||||||
|
>
|
||||||
|
<UnderlineIcon className="mr-2 h-4 w-4" />
|
||||||
|
Souligné
|
||||||
|
</Toggle>
|
||||||
|
<Toggle
|
||||||
|
aria-label="Toggle italic"
|
||||||
|
onClick={handleTextDecorationToggle("line-through")}
|
||||||
|
value={currentText.textDecoration}
|
||||||
|
>
|
||||||
|
<StrikethroughIcon className="mr-2 h-4 w-4" />
|
||||||
|
Barré
|
||||||
|
</Toggle>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFontStyle = (
|
||||||
|
currentStyle: string | undefined,
|
||||||
|
buttonClicked: "bold" | "italic" | "underline" | "line-through",
|
||||||
|
) => {
|
||||||
|
if (buttonClicked === "bold") {
|
||||||
|
if (currentStyle?.includes("bold")) {
|
||||||
|
return currentStyle?.replace("bold", "").trim();
|
||||||
|
}
|
||||||
|
return ((currentStyle ?? "") + " bold").trim();
|
||||||
|
}
|
||||||
|
if (buttonClicked === "italic") {
|
||||||
|
if (currentStyle?.includes("italic")) {
|
||||||
|
return currentStyle?.replace("italic", "").trim();
|
||||||
|
}
|
||||||
|
return ((currentStyle ?? "") + " italic").trim();
|
||||||
|
}
|
||||||
|
if (buttonClicked === "underline") {
|
||||||
|
if (currentStyle?.includes("underline")) {
|
||||||
|
return currentStyle?.replace("underline", "").trim();
|
||||||
|
}
|
||||||
|
return ((currentStyle ?? "") + " underline").trim();
|
||||||
|
}
|
||||||
|
if (buttonClicked === "line-through") {
|
||||||
|
if (currentStyle?.includes("line-through")) {
|
||||||
|
return currentStyle?.replace("line-through", "").trim();
|
||||||
|
}
|
||||||
|
return ((currentStyle ?? "") + " line-through").trim();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,62 +1,32 @@
|
|||||||
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 { FontFamilyPicker } from "./texte-editing-tools/text-family-picker";
|
||||||
import { FontFamilyPicker } from "./font-family-picker";
|
import { FontSizeSelector } from "./texte-editing-tools/text-size-selector";
|
||||||
import { FontSizeSelector } from "./font-size-selector";
|
import type { ChangeEvent } from "react";
|
||||||
import { useState, type ChangeEvent } from "react";
|
import { FontStyle } from "./texte-editing-tools/text-style-selector";
|
||||||
import { SpacingSettings } from "./spacing-settings";
|
import { FontAlign } from "./texte-editing-tools/text-align-selector";
|
||||||
|
import { Separator } from "./ui/separator";
|
||||||
|
import { SpacingSettings } from "./texte-editing-tools/text-spacing-settings";
|
||||||
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
|
import {
|
||||||
|
HoverCard,
|
||||||
|
HoverCardContent,
|
||||||
|
HoverCardTrigger,
|
||||||
|
} from "@/components/ui/hover-card";
|
||||||
|
import {
|
||||||
|
AiOutlineAlignCenter,
|
||||||
|
AiOutlineAlignLeft,
|
||||||
|
AiOutlineAlignRight,
|
||||||
|
} from "react-icons/ai";
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
const currentText = texts.find((t) => t.id === selectedItemId);
|
const currentText = texts.find((t) => t.id === selectedItemId);
|
||||||
|
|
||||||
if (!currentText) return null;
|
if (!currentText) return null;
|
||||||
|
|
||||||
const handleFontStyleToggle = (button: "bold" | "italic") => () => {
|
|
||||||
if (!selectedItemId) return;
|
|
||||||
dispatch(
|
|
||||||
updateText({
|
|
||||||
id: selectedItemId,
|
|
||||||
fontStyle: getFontStyle(currentText.fontStyle, button),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTextDecorationToggle =
|
|
||||||
(button: "underline" | "line-through") => () => {
|
|
||||||
if (!selectedItemId) return;
|
|
||||||
|
|
||||||
dispatch(
|
|
||||||
updateText({
|
|
||||||
id: selectedItemId,
|
|
||||||
textDecoration: getFontStyle(currentText.textDecoration, button),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTextFontSizeChange = (e: number | undefined) => {
|
|
||||||
if (!selectedItemId) return;
|
|
||||||
dispatch(
|
|
||||||
updateText({
|
|
||||||
id: selectedItemId,
|
|
||||||
fontSize: e,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTextFontFamilyChange = (e: string | undefined) => {
|
|
||||||
if (!selectedItemId) return;
|
|
||||||
dispatch(
|
|
||||||
updateText({
|
|
||||||
id: selectedItemId,
|
|
||||||
fontFamily: e,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
|
||||||
const handleTextColorChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleTextColorChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
if (!selectedItemId) return;
|
if (!selectedItemId) return;
|
||||||
dispatch(
|
dispatch(
|
||||||
@@ -67,95 +37,33 @@ export const Toolbar = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
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]">
|
||||||
<Toggle
|
<FontFamilyPicker
|
||||||
className="data-[state=on]:font-bold"
|
currentText={currentText}
|
||||||
onClick={handleFontStyleToggle("bold")}
|
selectedItemId={selectedItemId}
|
||||||
>
|
|
||||||
B
|
|
||||||
</Toggle>
|
|
||||||
<Toggle
|
|
||||||
className="data-[state=on]:italic"
|
|
||||||
onClick={handleFontStyleToggle("italic")}
|
|
||||||
>
|
|
||||||
I
|
|
||||||
</Toggle>
|
|
||||||
<Toggle
|
|
||||||
className="underline data-[state=on]:no-underline"
|
|
||||||
onClick={handleTextDecorationToggle("underline")}
|
|
||||||
>
|
|
||||||
U
|
|
||||||
</Toggle>
|
|
||||||
<Toggle
|
|
||||||
className="line-through data-[state=on]:no-underline"
|
|
||||||
onClick={handleTextDecorationToggle("line-through")}
|
|
||||||
>
|
|
||||||
S
|
|
||||||
</Toggle>
|
|
||||||
|
|
||||||
<FontSizeSelector
|
|
||||||
value={currentText.fontSize ?? 16}
|
|
||||||
fontSizeHandler={handleTextFontSizeChange}
|
|
||||||
/>
|
/>
|
||||||
|
<Separator orientation="vertical" />
|
||||||
|
<FontStyle currentText={currentText} selectedItemId={selectedItemId} />
|
||||||
|
<Separator orientation="vertical" />
|
||||||
|
<FontAlign currentText={currentText} selectedItemId={selectedItemId} />
|
||||||
|
<Separator orientation="vertical" />
|
||||||
|
<FontSizeSelector
|
||||||
|
currentText={currentText}
|
||||||
|
selectedItemId={selectedItemId}
|
||||||
|
/>
|
||||||
|
<Separator orientation="vertical" />
|
||||||
<input
|
<input
|
||||||
|
className="my-auto "
|
||||||
type="color"
|
type="color"
|
||||||
onChange={handleTextColorChange}
|
onChange={handleTextColorChange}
|
||||||
value={currentText.fill}
|
value={currentText.fill}
|
||||||
/>
|
/>
|
||||||
<FontFamilyPicker
|
<Separator orientation="vertical" />
|
||||||
handleTextFontFamilyChange={handleTextFontFamilyChange}
|
<SpacingSettings
|
||||||
selectedFont={currentText.fontFamily}
|
currentText={currentText}
|
||||||
|
selectedItemId={selectedItemId}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Toggle
|
|
||||||
className="data-[state=on]:font-bold"
|
|
||||||
onClick={handleAlignmentChange("left")}
|
|
||||||
></Toggle>
|
|
||||||
|
|
||||||
<SpacingSettings />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFontStyle = (
|
|
||||||
currentStyle: string | undefined,
|
|
||||||
buttonClicked: "bold" | "italic" | "underline" | "line-through",
|
|
||||||
) => {
|
|
||||||
if (buttonClicked === "bold") {
|
|
||||||
if (currentStyle?.includes("bold")) {
|
|
||||||
return currentStyle?.replace("bold", "").trim();
|
|
||||||
}
|
|
||||||
return ((currentStyle ?? "") + " bold").trim();
|
|
||||||
}
|
|
||||||
if (buttonClicked === "italic") {
|
|
||||||
if (currentStyle?.includes("italic")) {
|
|
||||||
return currentStyle?.replace("italic", "").trim();
|
|
||||||
}
|
|
||||||
return ((currentStyle ?? "") + " italic").trim();
|
|
||||||
}
|
|
||||||
if (buttonClicked === "underline") {
|
|
||||||
if (currentStyle?.includes("underline")) {
|
|
||||||
return currentStyle?.replace("underline", "").trim();
|
|
||||||
}
|
|
||||||
return ((currentStyle ?? "") + " underline").trim();
|
|
||||||
}
|
|
||||||
if (buttonClicked === "line-through") {
|
|
||||||
if (currentStyle?.includes("line-through")) {
|
|
||||||
return currentStyle?.replace("line-through", "").trim();
|
|
||||||
}
|
|
||||||
return ((currentStyle ?? "") + " line-through").trim();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { Slot } from '@radix-ui/react-icons'
|
import { cva, type VariantProps } from "class-variance-authority";
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Slot } from "@radix-ui/themes";
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
"inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||||
@@ -30,27 +30,27 @@ const buttonVariants = cva(
|
|||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
|
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
VariantProps<typeof buttonVariants> {
|
VariantProps<typeof buttonVariants> {
|
||||||
asChild?: boolean
|
asChild?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
const Comp = asChild ? Slot : "button"
|
const Comp = asChild ? Slot : "button";
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
},
|
||||||
)
|
);
|
||||||
Button.displayName = "Button"
|
Button.displayName = "Button";
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
export { Button, buttonVariants };
|
||||||
|
|||||||
@@ -1,25 +1,25 @@
|
|||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
||||||
import { Check, ChevronRight, Circle } from "lucide-react"
|
import { Check, ChevronRight, Circle } from "lucide-react";
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
const DropdownMenu = DropdownMenuPrimitive.Root;
|
||||||
|
|
||||||
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
|
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
|
||||||
|
|
||||||
const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
const DropdownMenuGroup = DropdownMenuPrimitive.Group;
|
||||||
|
|
||||||
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
|
||||||
|
|
||||||
const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
const DropdownMenuSub = DropdownMenuPrimitive.Sub;
|
||||||
|
|
||||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
|
||||||
|
|
||||||
const DropdownMenuSubTrigger = React.forwardRef<
|
const DropdownMenuSubTrigger = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
}
|
}
|
||||||
>(({ className, inset, children, ...props }, ref) => (
|
>(({ className, inset, children, ...props }, ref) => (
|
||||||
<DropdownMenuPrimitive.SubTrigger
|
<DropdownMenuPrimitive.SubTrigger
|
||||||
@@ -27,16 +27,16 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
|||||||
className={cn(
|
className={cn(
|
||||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
|
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
|
||||||
inset && "pl-8",
|
inset && "pl-8",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
<ChevronRight className="ml-auto h-4 w-4" />
|
<ChevronRight className="ml-auto h-4 w-4" />
|
||||||
</DropdownMenuPrimitive.SubTrigger>
|
</DropdownMenuPrimitive.SubTrigger>
|
||||||
))
|
));
|
||||||
DropdownMenuSubTrigger.displayName =
|
DropdownMenuSubTrigger.displayName =
|
||||||
DropdownMenuPrimitive.SubTrigger.displayName
|
DropdownMenuPrimitive.SubTrigger.displayName;
|
||||||
|
|
||||||
const DropdownMenuSubContent = React.forwardRef<
|
const DropdownMenuSubContent = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||||
@@ -46,13 +46,13 @@ const DropdownMenuSubContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
DropdownMenuSubContent.displayName =
|
DropdownMenuSubContent.displayName =
|
||||||
DropdownMenuPrimitive.SubContent.displayName
|
DropdownMenuPrimitive.SubContent.displayName;
|
||||||
|
|
||||||
const DropdownMenuContent = React.forwardRef<
|
const DropdownMenuContent = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||||
@@ -64,18 +64,18 @@ const DropdownMenuContent = React.forwardRef<
|
|||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
</DropdownMenuPrimitive.Portal>
|
</DropdownMenuPrimitive.Portal>
|
||||||
))
|
));
|
||||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
|
||||||
|
|
||||||
const DropdownMenuItem = React.forwardRef<
|
const DropdownMenuItem = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
}
|
}
|
||||||
>(({ className, inset, ...props }, ref) => (
|
>(({ className, inset, ...props }, ref) => (
|
||||||
<DropdownMenuPrimitive.Item
|
<DropdownMenuPrimitive.Item
|
||||||
@@ -83,12 +83,12 @@ const DropdownMenuItem = React.forwardRef<
|
|||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
inset && "pl-8",
|
inset && "pl-8",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
|
||||||
|
|
||||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||||
@@ -98,7 +98,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
checked={checked}
|
checked={checked}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -110,9 +110,9 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
|||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.CheckboxItem>
|
</DropdownMenuPrimitive.CheckboxItem>
|
||||||
))
|
));
|
||||||
DropdownMenuCheckboxItem.displayName =
|
DropdownMenuCheckboxItem.displayName =
|
||||||
DropdownMenuPrimitive.CheckboxItem.displayName
|
DropdownMenuPrimitive.CheckboxItem.displayName;
|
||||||
|
|
||||||
const DropdownMenuRadioItem = React.forwardRef<
|
const DropdownMenuRadioItem = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||||
@@ -122,7 +122,7 @@ const DropdownMenuRadioItem = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -133,13 +133,13 @@ const DropdownMenuRadioItem = React.forwardRef<
|
|||||||
</span>
|
</span>
|
||||||
{children}
|
{children}
|
||||||
</DropdownMenuPrimitive.RadioItem>
|
</DropdownMenuPrimitive.RadioItem>
|
||||||
))
|
));
|
||||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
|
||||||
|
|
||||||
const DropdownMenuLabel = React.forwardRef<
|
const DropdownMenuLabel = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||||
inset?: boolean
|
inset?: boolean;
|
||||||
}
|
}
|
||||||
>(({ className, inset, ...props }, ref) => (
|
>(({ className, inset, ...props }, ref) => (
|
||||||
<DropdownMenuPrimitive.Label
|
<DropdownMenuPrimitive.Label
|
||||||
@@ -147,12 +147,12 @@ const DropdownMenuLabel = React.forwardRef<
|
|||||||
className={cn(
|
className={cn(
|
||||||
"px-2 py-1.5 text-sm font-semibold",
|
"px-2 py-1.5 text-sm font-semibold",
|
||||||
inset && "pl-8",
|
inset && "pl-8",
|
||||||
className
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
|
||||||
|
|
||||||
const DropdownMenuSeparator = React.forwardRef<
|
const DropdownMenuSeparator = React.forwardRef<
|
||||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||||
@@ -163,8 +163,8 @@ const DropdownMenuSeparator = React.forwardRef<
|
|||||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
));
|
||||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
|
||||||
|
|
||||||
const DropdownMenuShortcut = ({
|
const DropdownMenuShortcut = ({
|
||||||
className,
|
className,
|
||||||
@@ -175,9 +175,9 @@ const DropdownMenuShortcut = ({
|
|||||||
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -195,4 +195,4 @@ export {
|
|||||||
DropdownMenuSubContent,
|
DropdownMenuSubContent,
|
||||||
DropdownMenuSubTrigger,
|
DropdownMenuSubTrigger,
|
||||||
DropdownMenuRadioGroup,
|
DropdownMenuRadioGroup,
|
||||||
}
|
};
|
||||||
|
|||||||
29
src/components/ui/separator.tsx
Normal file
29
src/components/ui/separator.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Separator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{ className, orientation = "horizontal", decorative = true, ...props },
|
||||||
|
ref
|
||||||
|
) => (
|
||||||
|
<SeparatorPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
decorative={decorative}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"shrink-0 bg-border",
|
||||||
|
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Separator.displayName = SeparatorPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Separator }
|
||||||
53
src/components/ui/tabs.tsx
Normal file
53
src/components/ui/tabs.tsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Tabs = TabsPrimitive.Root
|
||||||
|
|
||||||
|
const TabsList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsList.displayName = TabsPrimitive.List.displayName
|
||||||
|
|
||||||
|
const TabsTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const TabsContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||||
@@ -14,9 +14,9 @@ const toggleVariants = cva(
|
|||||||
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
"border border-input bg-transparent hover:bg-accent hover:text-accent-foreground",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-10 px-3",
|
default: "h-8 px-2",
|
||||||
sm: "h-9 px-2.5",
|
sm: "h-7 px-1.5",
|
||||||
lg: "h-11 px-5",
|
lg: "h-8 px-3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|||||||
Reference in New Issue
Block a user