text editing finitions - text align option

This commit is contained in:
Artur AGH
2023-10-25 14:36:09 +02:00
parent 7ea8779942
commit 74accdcb14
16 changed files with 1324 additions and 1028 deletions

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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>
</>
);
}

View File

@@ -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 "

View File

@@ -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>
);
};

View 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>
);
};

View File

@@ -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>

View 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>
</>
);
}

View 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>
);
};

View 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();
}
};

View File

@@ -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();
}
};

View File

@@ -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 };

View File

@@ -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,
} };

View 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 }

View 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 }

View File

@@ -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: {