mirror of
https://github.com/r2r90/canvas-label.git
synced 2026-01-27 05:32:08 +00:00
styling test version
This commit is contained in:
@@ -3,7 +3,9 @@ import React from "react";
|
||||
import { Sidebar } from "@/components/layout/sidebar/sidebar";
|
||||
import Header from "@/components/layout/header/Header";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Tabs } from "@/components/ui/tabs";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PlusCircledIcon } from "@radix-ui/react-icons";
|
||||
|
||||
type Props = {
|
||||
children: ReactNode;
|
||||
@@ -11,14 +13,17 @@ type Props = {
|
||||
|
||||
export const Layout = ({ children }: Props) => {
|
||||
return (
|
||||
<div className="h-full overflow-hidden rounded-[0.5rem] border bg-background shadow">
|
||||
<div className="h-full overflow-hidden rounded-[0.5rem] border bg-background shadow ">
|
||||
<Header />
|
||||
<Separator />
|
||||
|
||||
<div className="container py-6 ">
|
||||
<div className="grid h-full items-stretch gap-6 md:min-h-[600px] md:grid-cols-[1fr_350px] lg:min-h-[700px] ">
|
||||
<div
|
||||
className="container py-6
|
||||
"
|
||||
>
|
||||
<div className="grid h-full items-stretch gap-6 md:grid-cols-[1fr_350px] lg:min-h-[680px] ">
|
||||
<div className="rounded-md border md:order-1">{children}</div>
|
||||
<div className="hidden flex-col space-y-4 sm:flex md:order-2">
|
||||
<div className="hidden flex-col space-y-4 rounded-md border sm:flex md:order-2 lg:min-h-[680px]">
|
||||
<Sidebar />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -101,40 +101,7 @@ export const ImageGallery = () => {
|
||||
<LuLayoutTemplate />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent side="right" className="mt-4">
|
||||
<Card className="p-3">
|
||||
<CardHeader className="mb-2 p-2 text-center">
|
||||
<CardTitle>Template</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Images"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
|
||||
<div
|
||||
className=" grid h-64 w-full grid-cols-2 gap-4 overflow-auto"
|
||||
ref={scrollAreaRef}
|
||||
>
|
||||
{photos?.map((p, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="rounded-md border-2 border-muted bg-popover p-2 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
|
||||
>
|
||||
<Image
|
||||
key={p.i}
|
||||
src={p.urls.small}
|
||||
alt={"image"}
|
||||
height={100}
|
||||
width={100}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
</PopoverContent>
|
||||
<PopoverContent side="right" className="mt-4"></PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
|
||||
146
src/components/layout/sidebar/image-gallery/image-gallery.tsx
Normal file
146
src/components/layout/sidebar/image-gallery/image-gallery.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
type ElementRef,
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Card, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import axios from "axios";
|
||||
import * as process from "process";
|
||||
import Image from "next/image";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { addImage } from "@/store/app.slice";
|
||||
|
||||
type Props = {
|
||||
open: boolean;
|
||||
};
|
||||
|
||||
export const ImageGallery = ({ open }: Props) => {
|
||||
const [query, setQuery] = useState<string>("");
|
||||
const [page, setPage] = useState<number>(1);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const [photos, setPhotos] = useState<any[]>([]);
|
||||
|
||||
const scrollAreaRef = useRef<ElementRef<"div">>(null);
|
||||
|
||||
const mainUrl = "https://api.unsplash.com/photos";
|
||||
const searchUrl = "https://api.unsplash.com/search/photos";
|
||||
const clientID = `?client_id=${process.env.NEXT_PUBLIC_ACCESS_KEY}`;
|
||||
|
||||
useEffect(() => {
|
||||
fetchImages();
|
||||
}, [query, page]);
|
||||
|
||||
const fetchImages = async () => {
|
||||
setIsLoading(true);
|
||||
let url;
|
||||
const urlPage = `&page=${page}`;
|
||||
const urlQuery = `&query=${query}`;
|
||||
|
||||
if (query) {
|
||||
url = `${searchUrl}${clientID}${urlPage}${urlQuery}`;
|
||||
console.log(url);
|
||||
} else {
|
||||
url = `${mainUrl}${clientID}${urlPage}`;
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } = await axios.get(url);
|
||||
|
||||
setPhotos((oldPhotos) => {
|
||||
if (query && page === 1) {
|
||||
return data.results;
|
||||
} else if (query) {
|
||||
return [...oldPhotos, ...data.results];
|
||||
} else {
|
||||
console.log(data);
|
||||
return [...oldPhotos, ...data];
|
||||
}
|
||||
});
|
||||
setIsLoading(false);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const ref = scrollAreaRef.current;
|
||||
const handler = () => {
|
||||
if (scrollAreaRef.current) {
|
||||
console.log(
|
||||
scrollAreaRef.current.clientHeight + scrollAreaRef.current.scrollTop,
|
||||
scrollAreaRef.current.scrollHeight - 2,
|
||||
);
|
||||
}
|
||||
if (
|
||||
isLoading ||
|
||||
!scrollAreaRef.current ||
|
||||
scrollAreaRef.current.clientHeight + scrollAreaRef.current.scrollTop <
|
||||
scrollAreaRef.current.scrollHeight - 2
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPage((oldPage) => {
|
||||
return oldPage + 1;
|
||||
});
|
||||
};
|
||||
const handleImageUploaded = () => {
|
||||
if (!file) return;
|
||||
const img = document.createElement("img");
|
||||
const imageUrl = URL.createObjectURL(file);
|
||||
img.onload = () => {
|
||||
dispatch(addImage({ imageUrl, width: img.width, height: img.height }));
|
||||
img.remove();
|
||||
};
|
||||
img.src = imageUrl;
|
||||
img.style.cssText = "display: none;";
|
||||
document.body.appendChild(img);
|
||||
};
|
||||
scrollAreaRef.current?.addEventListener("scroll", handler);
|
||||
return () => ref?.removeEventListener("scroll", handler);
|
||||
}, [isLoading, open]);
|
||||
|
||||
const handleImageAdd = (e) => {
|
||||
console.log(e.target);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="min-h-full p-3">
|
||||
<CardHeader className="mb-2 p-2 text-center">
|
||||
<CardTitle>Template</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search Images"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
|
||||
<div
|
||||
className="mt-3 max-h-64 w-full grid-cols-2 justify-center overflow-auto"
|
||||
ref={scrollAreaRef}
|
||||
>
|
||||
{photos?.map((p, i) => (
|
||||
<div
|
||||
key={i}
|
||||
onClick={(e) => handleImageAdd(e)}
|
||||
className="rounded-md border-2 border-muted bg-popover p-2 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
|
||||
>
|
||||
<Image
|
||||
key={p.i}
|
||||
src={p.urls.small}
|
||||
alt={"image"}
|
||||
height={100}
|
||||
width={100}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
36
src/components/layout/sidebar/image-input/image-input.tsx
Normal file
36
src/components/layout/sidebar/image-input/image-input.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import React, { type ChangeEvent } from "react";
|
||||
import { addImage } from "@/store/app.slice";
|
||||
import { useAppDispatch } from "@/hooks";
|
||||
import {
|
||||
Card,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
||||
export default function ImageInput() {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleImageUploaded = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
const img = document.createElement("img");
|
||||
const imageUrl = URL.createObjectURL(file);
|
||||
img.onload = () => {
|
||||
dispatch(addImage({ imageUrl, width: img.width, height: img.height }));
|
||||
img.remove();
|
||||
};
|
||||
img.src = imageUrl;
|
||||
img.style.cssText = "display: none;";
|
||||
document.body.appendChild(img);
|
||||
};
|
||||
return (
|
||||
<Card className="p-2">
|
||||
<Input type="file" onChange={handleImageUploaded} />
|
||||
<CardDescription>
|
||||
Téléchargez votre image et ajoutez-la à votre étiquette.
|
||||
</CardDescription>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { RxStack } from "react-icons/rx";
|
||||
import { useAppDispatch, useAppSelector } from "@/hooks";
|
||||
import { selectItem, setStageItems } from "@/store/app.slice";
|
||||
import { closestCenter, DndContext, type DragEndEvent } from "@dnd-kit/core";
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import LayerItem from "@/components/layer-item";
|
||||
|
||||
export const Layers = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const currentStep = useAppSelector((state) => state.app.currentStep);
|
||||
const items = useAppSelector(
|
||||
(state) => state.app.history[currentStep]?.items,
|
||||
);
|
||||
if (!items) return;
|
||||
const handleDragEnd = (e: DragEndEvent) => {
|
||||
const { active, over } = e;
|
||||
const oldIndex = items.findIndex((item) => item.id === active.id);
|
||||
const newIndex = items.findIndex((item) => item.id === over?.id);
|
||||
const result = arrayMove(items, oldIndex, newIndex);
|
||||
dispatch(setStageItems(result));
|
||||
dispatch(selectItem(active.id));
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline" className="text-xl">
|
||||
<RxStack />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent side="right">
|
||||
<Card className="p-2">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-center">Layers</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid items-center gap-2 p-2 ">
|
||||
<DndContext
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext
|
||||
items={items}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<LayerItem item={item} key={item.id} />
|
||||
))}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
};
|
||||
48
src/components/layout/sidebar/layers/layers.tsx
Normal file
48
src/components/layout/sidebar/layers/layers.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { useAppDispatch, useAppSelector } from "@/hooks";
|
||||
import { selectItem, setStageItems } from "@/store/app.slice";
|
||||
import { closestCenter, DndContext, type DragEndEvent } from "@dnd-kit/core";
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
verticalListSortingStrategy,
|
||||
} from "@dnd-kit/sortable";
|
||||
import LayerItem from "@/components/layer-item";
|
||||
|
||||
export const Layers = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const currentStep = useAppSelector((state) => state.app.currentStep);
|
||||
const items = useAppSelector(
|
||||
(state) => state.app.history[currentStep]?.items,
|
||||
);
|
||||
if (!items) return;
|
||||
const handleDragEnd = (e: DragEndEvent) => {
|
||||
const { active, over } = e;
|
||||
const oldIndex = items.findIndex((item) => item.id === active.id);
|
||||
const newIndex = items.findIndex((item) => item.id === over?.id);
|
||||
const result = arrayMove(items, oldIndex, newIndex);
|
||||
dispatch(setStageItems(result));
|
||||
dispatch(selectItem(active.id));
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className="p-2">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-center">Layers</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="grid items-center gap-2 p-2 ">
|
||||
<DndContext
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext items={items} strategy={verticalListSortingStrategy}>
|
||||
{items.map((item) => (
|
||||
<LayerItem item={item} key={item.id} />
|
||||
))}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -1,21 +1,64 @@
|
||||
import { TextInput } from "@/components/layout/sidebar/text-input";
|
||||
import ImageInput from "@/components/layout/sidebar/image-input";
|
||||
import SizeSelect from "@/components/layout/sidebar/size-select";
|
||||
import { BackgroundSelect } from "@/components/layout/sidebar/background-select";
|
||||
import { Layers } from "@/components/layout/sidebar/layers";
|
||||
import { SelectTemplate } from "@/components/layout/sidebar/select-template";
|
||||
import { ImageGallery } from "@/components/layout/sidebar/image-gallery";
|
||||
import TextInput from "@/components/layout/sidebar/text-input/text-input";
|
||||
import ImageInput from "@/components/layout/sidebar/image-input/image-input";
|
||||
import { Layers } from "@/components/layout/sidebar/layers/layers";
|
||||
import { ImageGallery } from "@/components/layout/sidebar/image-gallery/image-gallery";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import React, { useState } from "react";
|
||||
import { LuLayoutTemplate } from "react-icons/lu";
|
||||
import {
|
||||
GearIcon,
|
||||
ImageIcon,
|
||||
LayersIcon,
|
||||
TextIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import { StageSize } from "@/components/layout/sidebar/stage-settings/stage-size";
|
||||
import { StageBackground } from "@/components/layout/sidebar/stage-settings/stage-background";
|
||||
|
||||
export function Sidebar() {
|
||||
const [openGallery, setOpenGallery] = useState<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-20 flex-col items-center gap-2 p-2 pt-4">
|
||||
<SizeSelect />
|
||||
<TextInput />
|
||||
<ImageInput />
|
||||
<SelectTemplate />
|
||||
<BackgroundSelect />
|
||||
<ImageGallery />
|
||||
<Layers />
|
||||
<div className="items-left flex w-full flex-col p-2 pt-4">
|
||||
<Tabs>
|
||||
<TabsList className="grid grid-cols-5">
|
||||
<TabsTrigger value="stage">
|
||||
<GearIcon />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="image">
|
||||
<ImageIcon />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="text">
|
||||
<TextIcon />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
onClick={() => setOpenGallery(true)}
|
||||
open={openGallery}
|
||||
onOpenChange={setOpenGallery}
|
||||
value="gallery"
|
||||
>
|
||||
<LuLayoutTemplate />
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="layers">
|
||||
<LayersIcon />
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="stage">
|
||||
<StageSize />
|
||||
<StageBackground />
|
||||
</TabsContent>
|
||||
<TabsContent value="image">
|
||||
<ImageInput />
|
||||
</TabsContent>
|
||||
<TabsContent value="text">
|
||||
<TextInput />
|
||||
</TabsContent>
|
||||
<TabsContent value="gallery">
|
||||
<ImageGallery open={openGallery} />
|
||||
</TabsContent>
|
||||
<TabsContent value="layers">
|
||||
<Layers />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import React, { useState } from "react";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { useAppDispatch, useAppSelector } from "@/hooks";
|
||||
import { selectBackground } from "@/store/app.slice";
|
||||
|
||||
export const StageBackground = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const bgColor = useAppSelector((state) => state.app.backgroundColor);
|
||||
|
||||
const handleBackgroundSelect = (color: string) => {
|
||||
dispatch(selectBackground(color));
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Label
|
||||
htmlFor="apple"
|
||||
className="flex flex-col items-center justify-between rounded-md border-2 border-muted bg-popover p-4 hover:bg-accent hover:text-accent-foreground peer-data-[state=checked]:border-primary [&:has([data-state=checked])]:border-primary"
|
||||
>
|
||||
<input
|
||||
className="m-2 "
|
||||
type="color"
|
||||
value={bgColor ?? undefined}
|
||||
onChange={(e) => handleBackgroundSelect(e.target.value)}
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
42
src/components/layout/sidebar/stage-settings/stage-size.tsx
Normal file
42
src/components/layout/sidebar/stage-settings/stage-size.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import React, { useState } from "react";
|
||||
import { Card, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useAppDispatch } from "@/hooks";
|
||||
import { updateStage } from "@/store/app.slice";
|
||||
|
||||
export const StageSize = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleStageSizeSelect = (
|
||||
width: number | undefined,
|
||||
height: number | undefined,
|
||||
) => {
|
||||
dispatch(updateStage({ width, height }));
|
||||
};
|
||||
return (
|
||||
<Card className="p-3">
|
||||
<CardHeader>
|
||||
<CardTitle>{`Sélectionnez la taille d'étiquette`}</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
<Button onClick={() => handleStageSizeSelect(269.32, 165.87)}>
|
||||
Petit Format
|
||||
</Button>
|
||||
<Button onClick={() => handleStageSizeSelect(323.15, 227)}>
|
||||
Moyen Format
|
||||
</Button>
|
||||
<Button onClick={() => handleStageSizeSelect(410, 289)}>
|
||||
Grand Format
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/*<CardFooter className="mt-8 justify-between">*/}
|
||||
{/* <Button variant="ghost" onClick={() => setOpen(false)}>*/}
|
||||
{/* Cancel*/}
|
||||
{/* </Button>*/}
|
||||
{/* <Button onClick={handleSizeSelect}>Submit</Button>*/}
|
||||
{/*</CardFooter>*/}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -8,7 +8,13 @@ import { Textarea } from "@/components/ui/textarea";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { addText } from "@/store/app.slice";
|
||||
import { useAppDispatch } from "@/hooks";
|
||||
import { Card, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import {
|
||||
Card,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { RxText } from "react-icons/rx";
|
||||
|
||||
export function TextInput() {
|
||||
@@ -51,6 +57,9 @@ export function TextInput() {
|
||||
</Button>
|
||||
<Button onClick={handleTextAdd}>Submit</Button>
|
||||
</CardFooter>
|
||||
<CardDescription>
|
||||
Téléchargez votre image et ajoutez-la à votre étiquette.
|
||||
</CardDescription>
|
||||
</Card>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
45
src/components/layout/sidebar/text-input/text-input.tsx
Normal file
45
src/components/layout/sidebar/text-input/text-input.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { type ChangeEvent, useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
CardDescription,
|
||||
CardFooter,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { addText } from "@/store/app.slice";
|
||||
import { useAppDispatch } from "@/hooks";
|
||||
|
||||
export default function TextInput() {
|
||||
const dispatch = useAppDispatch();
|
||||
const [inputText, setInputText] = useState("");
|
||||
const handleTextAdd = () => {
|
||||
dispatch(addText({ initialValue: inputText }));
|
||||
};
|
||||
const handleInputChange = (e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
setInputText(e.target.value);
|
||||
};
|
||||
return (
|
||||
<Card className="p-3">
|
||||
<CardHeader className="pt-2">
|
||||
<CardTitle className="text-center">Text</CardTitle>
|
||||
</CardHeader>
|
||||
<Textarea
|
||||
id="description"
|
||||
placeholder="Entrez votre text ..."
|
||||
onChange={handleInputChange}
|
||||
/>
|
||||
|
||||
<CardFooter className="mt-8 justify-between">
|
||||
<Button variant="ghost" onClick={() => setOpen(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={handleTextAdd}>Submit</Button>
|
||||
</CardFooter>
|
||||
<CardDescription>
|
||||
Téléchargez votre image et ajoutez-la à votre étiquette.
|
||||
</CardDescription>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
import * as React from "react";
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Tabs = TabsPrimitive.Root
|
||||
const Tabs = TabsPrimitive.Root;
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
@@ -12,13 +12,13 @@ const TabsList = React.forwardRef<
|
||||
<TabsPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
||||
className
|
||||
"inline-flex h-10 w-[20rem] items-center justify-center rounded-md bg-muted p-1 text-muted-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsList.displayName = TabsPrimitive.List.displayName
|
||||
));
|
||||
TabsList.displayName = TabsPrimitive.List.displayName;
|
||||
|
||||
const TabsTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Trigger>,
|
||||
@@ -28,12 +28,12 @@ const TabsTrigger = React.forwardRef<
|
||||
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
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
|
||||
));
|
||||
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
|
||||
|
||||
const TabsContent = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.Content>,
|
||||
@@ -43,11 +43,11 @@ const TabsContent = React.forwardRef<
|
||||
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
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName
|
||||
));
|
||||
TabsContent.displayName = TabsPrimitive.Content.displayName;
|
||||
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent }
|
||||
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
||||
|
||||
Reference in New Issue
Block a user