Add tags to commssion types and custom types. Add button for example images to cards
This commit is contained in:
@ -14,7 +14,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import type { CommissionExtra, CommissionOption } from "@/generated/prisma/client";
|
||||
import type { CommissionExtra, CommissionOption, Tag } from "@/generated/prisma/client";
|
||||
import {
|
||||
commissionCustomCardSchema,
|
||||
type CommissionCustomCardValues,
|
||||
@ -26,6 +26,7 @@ import { toast } from "sonner";
|
||||
import { CommissionExtraField } from "../types/form/CommissionExtraField";
|
||||
import { CommissionOptionField } from "../types/form/CommissionOptionField";
|
||||
import { CustomCardImagePicker } from "./CustomCardImagePicker";
|
||||
import MultipleSelector from "@/components/ui/multiselect";
|
||||
|
||||
type CustomCardOption = {
|
||||
optionId: string;
|
||||
@ -48,6 +49,7 @@ type CustomCardWithItems = {
|
||||
referenceImageUrl: string | null;
|
||||
isVisible: boolean;
|
||||
isSpecialOffer: boolean;
|
||||
tags: Tag[];
|
||||
options: CustomCardOption[];
|
||||
extras: CustomCardExtra[];
|
||||
};
|
||||
@ -57,6 +59,7 @@ type Props = {
|
||||
allOptions: CommissionOption[];
|
||||
allExtras: CommissionExtra[];
|
||||
images: CommissionCustomCardImageItem[];
|
||||
allTags: Tag[];
|
||||
};
|
||||
|
||||
export default function EditCustomCardForm({
|
||||
@ -64,6 +67,7 @@ export default function EditCustomCardForm({
|
||||
allOptions,
|
||||
allExtras,
|
||||
images,
|
||||
allTags,
|
||||
}: Props) {
|
||||
const router = useRouter();
|
||||
const form = useForm<CommissionCustomCardValues>({
|
||||
@ -74,6 +78,7 @@ export default function EditCustomCardForm({
|
||||
isVisible: card.isVisible,
|
||||
isSpecialOffer: card.isSpecialOffer,
|
||||
referenceImageUrl: card.referenceImageUrl ?? null,
|
||||
tagIds: card.tags.map((t) => t.id),
|
||||
options: card.options.map((o) => ({
|
||||
optionId: o.optionId,
|
||||
price: o.price ?? undefined,
|
||||
@ -171,6 +176,37 @@ export default function EditCustomCardForm({
|
||||
render={() => <CustomCardImagePicker form={form} initialImages={images} />}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="tagIds"
|
||||
render={({ field }) => {
|
||||
const selectedIds = field.value ?? [];
|
||||
const selectedOptions = allTags
|
||||
.filter((t) => selectedIds.includes(t.id))
|
||||
.map((t) => ({ label: t.name, value: t.id }));
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Tags</FormLabel>
|
||||
<FormControl>
|
||||
<MultipleSelector
|
||||
options={allTags.map((t) => ({ label: t.name, value: t.id }))}
|
||||
placeholder="Select tags for this custom card"
|
||||
hidePlaceholderWhenSelected
|
||||
selectFirstItem
|
||||
value={selectedOptions}
|
||||
onChange={(options) => field.onChange(options.map((o) => o.value))}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Used to link this custom card to tagged artworks.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<CommissionOptionField options={allOptions} />
|
||||
<CommissionExtraField extras={allExtras} />
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import type { CommissionExtra, CommissionOption } from "@/generated/prisma/client";
|
||||
import type { CommissionExtra, CommissionOption, Tag } from "@/generated/prisma/client";
|
||||
import {
|
||||
commissionCustomCardSchema,
|
||||
type CommissionCustomCardValues,
|
||||
@ -26,14 +26,16 @@ import { toast } from "sonner";
|
||||
import { CommissionExtraField } from "../types/form/CommissionExtraField";
|
||||
import { CommissionOptionField } from "../types/form/CommissionOptionField";
|
||||
import { CustomCardImagePicker } from "./CustomCardImagePicker";
|
||||
import MultipleSelector from "@/components/ui/multiselect";
|
||||
|
||||
type Props = {
|
||||
options: CommissionOption[];
|
||||
extras: CommissionExtra[];
|
||||
images: CommissionCustomCardImageItem[];
|
||||
tags: Tag[];
|
||||
};
|
||||
|
||||
export default function NewCustomCardForm({ options, extras, images }: Props) {
|
||||
export default function NewCustomCardForm({ options, extras, images, tags }: Props) {
|
||||
const router = useRouter();
|
||||
const form = useForm<CommissionCustomCardValues>({
|
||||
resolver: zodResolver(commissionCustomCardSchema),
|
||||
@ -43,6 +45,7 @@ export default function NewCustomCardForm({ options, extras, images }: Props) {
|
||||
isVisible: true,
|
||||
isSpecialOffer: false,
|
||||
referenceImageUrl: null,
|
||||
tagIds: [],
|
||||
options: [],
|
||||
extras: [],
|
||||
},
|
||||
@ -131,6 +134,37 @@ export default function NewCustomCardForm({ options, extras, images }: Props) {
|
||||
render={() => <CustomCardImagePicker form={form} initialImages={images} />}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="tagIds"
|
||||
render={({ field }) => {
|
||||
const selectedIds = field.value ?? [];
|
||||
const selectedOptions = tags
|
||||
.filter((t) => selectedIds.includes(t.id))
|
||||
.map((t) => ({ label: t.name, value: t.id }));
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Tags</FormLabel>
|
||||
<FormControl>
|
||||
<MultipleSelector
|
||||
options={tags.map((t) => ({ label: t.name, value: t.id }))}
|
||||
placeholder="Select tags for this custom card"
|
||||
hidePlaceholderWhenSelected
|
||||
selectFirstItem
|
||||
value={selectedOptions}
|
||||
onChange={(options) => field.onChange(options.map((o) => o.value))}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Used to link this custom card to tagged artworks.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<CommissionOptionField options={options} />
|
||||
<CommissionExtraField extras={extras} />
|
||||
|
||||
|
||||
@ -1,10 +1,28 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import { updateCommissionType } from "@/actions/commissions/types/updateType";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { CommissionCustomInput, CommissionExtra, CommissionOption, CommissionType, CommissionTypeCustomInput, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma/client";
|
||||
import MultipleSelector from "@/components/ui/multiselect";
|
||||
import type {
|
||||
CommissionCustomInput,
|
||||
CommissionExtra,
|
||||
CommissionOption,
|
||||
CommissionType,
|
||||
CommissionTypeCustomInput,
|
||||
CommissionTypeExtra,
|
||||
CommissionTypeOption,
|
||||
Tag,
|
||||
} from "@/generated/prisma/client";
|
||||
import { commissionTypeSchema } from "@/schemas/commissionType";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useRouter } from "next/navigation";
|
||||
@ -15,25 +33,35 @@ import { CommissionExtraField } from "./form/CommissionExtraField";
|
||||
import { CommissionOptionField } from "./form/CommissionOptionField";
|
||||
|
||||
type CommissionTypeWithConnections = CommissionType & {
|
||||
options: (CommissionTypeOption & { option: CommissionOption })[]
|
||||
extras: (CommissionTypeExtra & { extra: CommissionExtra })[]
|
||||
customInputs: (CommissionTypeCustomInput & { customInput: CommissionCustomInput })[]
|
||||
}
|
||||
options: (CommissionTypeOption & { option: CommissionOption })[];
|
||||
extras: (CommissionTypeExtra & { extra: CommissionExtra })[];
|
||||
customInputs: (CommissionTypeCustomInput & {
|
||||
customInput: CommissionCustomInput;
|
||||
})[];
|
||||
tags: Tag[];
|
||||
};
|
||||
|
||||
type Props = {
|
||||
type: CommissionTypeWithConnections
|
||||
allOptions: CommissionOption[],
|
||||
allExtras: CommissionExtra[],
|
||||
type: CommissionTypeWithConnections;
|
||||
allOptions: CommissionOption[];
|
||||
allExtras: CommissionExtra[];
|
||||
allTags: Tag[];
|
||||
// allCustomInputs: CommissionCustomInput[]
|
||||
}
|
||||
};
|
||||
|
||||
export default function EditTypeForm({ type, allOptions, allExtras }: Props) {
|
||||
export default function EditTypeForm({
|
||||
type,
|
||||
allOptions,
|
||||
allExtras,
|
||||
allTags,
|
||||
}: Props) {
|
||||
const router = useRouter();
|
||||
const form = useForm<z.infer<typeof commissionTypeSchema>>({
|
||||
resolver: zodResolver(commissionTypeSchema),
|
||||
defaultValues: {
|
||||
name: type.name,
|
||||
description: type.description ?? "",
|
||||
tagIds: type.tags.map((t) => t.id),
|
||||
options: type.options.map((o) => ({
|
||||
optionId: o.optionId,
|
||||
price: o.price ?? undefined,
|
||||
@ -54,16 +82,16 @@ export default function EditTypeForm({ type, allOptions, allExtras }: Props) {
|
||||
required: f.required,
|
||||
})),
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
async function onSubmit(values: z.infer<typeof commissionTypeSchema>) {
|
||||
try {
|
||||
await updateCommissionType(type.id, values)
|
||||
toast.success("Commission type updated.")
|
||||
router.push("/commissions/types")
|
||||
await updateCommissionType(type.id, values);
|
||||
toast.success("Commission type updated.");
|
||||
router.push("/commissions/types");
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
toast("Failed to create commission type.")
|
||||
console.error(err);
|
||||
toast("Failed to create commission type.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +108,9 @@ export default function EditTypeForm({ type, allOptions, allExtras }: Props) {
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>The name of the commission type.</FormDescription>
|
||||
<FormDescription>
|
||||
The name of the commission type.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@ -99,6 +129,41 @@ export default function EditTypeForm({ type, allOptions, allExtras }: Props) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="tagIds"
|
||||
render={({ field }) => {
|
||||
const selectedIds = field.value ?? [];
|
||||
const selectedOptions = allTags
|
||||
.filter((t) => selectedIds.includes(t.id))
|
||||
.map((t) => ({ label: t.name, value: t.id }));
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Tags</FormLabel>
|
||||
<FormControl>
|
||||
<MultipleSelector
|
||||
options={allTags.map((t) => ({
|
||||
label: t.name,
|
||||
value: t.id,
|
||||
}))}
|
||||
placeholder="Select tags for this commission type"
|
||||
hidePlaceholderWhenSelected
|
||||
selectFirstItem
|
||||
value={selectedOptions}
|
||||
onChange={(options) =>
|
||||
field.onChange(options.map((o) => o.value))
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Used to link this commission type to tagged artworks.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<CommissionOptionField options={allOptions} />
|
||||
<CommissionExtraField extras={allExtras} />
|
||||
@ -106,10 +171,16 @@ export default function EditTypeForm({ type, allOptions, allExtras }: Props) {
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<Button type="submit">Submit</Button>
|
||||
<Button type="reset" variant="secondary" onClick={() => router.back()}>Cancel</Button>
|
||||
<Button
|
||||
type="reset"
|
||||
variant="secondary"
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,15 @@ import { updateCommissionTypeSortOrder } from "@/actions/commissions/types/updat
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { CommissionCustomInput, CommissionExtra, CommissionOption, CommissionType, CommissionTypeCustomInput, CommissionTypeExtra, CommissionTypeOption } from "@/generated/prisma/client";
|
||||
import {
|
||||
CommissionCustomInput,
|
||||
CommissionExtra,
|
||||
CommissionOption,
|
||||
CommissionType,
|
||||
CommissionTypeCustomInput,
|
||||
CommissionTypeExtra,
|
||||
CommissionTypeOption,
|
||||
} from "@/generated/prisma/client";
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
@ -187,4 +195,4 @@ export default function ListTypes({ types }: { types: CommissionTypeWithItems[]
|
||||
</Dialog>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,47 +1,68 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import { createCommissionType } from "@/actions/commissions/types/newType";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { CommissionCustomInput, CommissionExtra, CommissionOption } from "@/generated/prisma/client";
|
||||
import MultipleSelector from "@/components/ui/multiselect";
|
||||
import type {
|
||||
CommissionCustomInput,
|
||||
CommissionExtra,
|
||||
CommissionOption,
|
||||
Tag,
|
||||
} from "@/generated/prisma/client";
|
||||
import { commissionTypeSchema } from "@/schemas/commissionType";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import * as z from "zod/v4";
|
||||
import type * as z from "zod/v4";
|
||||
import { CommissionCustomInputField } from "./form/CommissionCustomInputField";
|
||||
import { CommissionExtraField } from "./form/CommissionExtraField";
|
||||
import { CommissionOptionField } from "./form/CommissionOptionField";
|
||||
|
||||
type Props = {
|
||||
options: CommissionOption[],
|
||||
extras: CommissionExtra[],
|
||||
customInputs: CommissionCustomInput[]
|
||||
}
|
||||
options: CommissionOption[];
|
||||
extras: CommissionExtra[];
|
||||
customInputs: CommissionCustomInput[];
|
||||
tags: Tag[];
|
||||
};
|
||||
|
||||
export default function NewTypeForm({ options, extras, customInputs }: Props) {
|
||||
export default function NewTypeForm({
|
||||
options,
|
||||
extras,
|
||||
customInputs,
|
||||
tags,
|
||||
}: Props) {
|
||||
const router = useRouter();
|
||||
const form = useForm<z.infer<typeof commissionTypeSchema>>({
|
||||
resolver: zodResolver(commissionTypeSchema),
|
||||
defaultValues: {
|
||||
name: "",
|
||||
description: "",
|
||||
tagIds: [],
|
||||
options: [],
|
||||
extras: [],
|
||||
},
|
||||
})
|
||||
});
|
||||
|
||||
async function onSubmit(values: z.infer<typeof commissionTypeSchema>) {
|
||||
try {
|
||||
const created = await createCommissionType(values)
|
||||
console.log("CommissionType created:", created)
|
||||
toast("Commission type created.")
|
||||
router.push("/commissions/types")
|
||||
const created = await createCommissionType(values);
|
||||
console.log("CommissionType created:", created);
|
||||
toast("Commission type created.");
|
||||
router.push("/commissions/types");
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
toast("Failed to create commission type.")
|
||||
console.error(err);
|
||||
toast("Failed to create commission type.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +79,9 @@ export default function NewTypeForm({ options, extras, customInputs }: Props) {
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>The name of the commission type.</FormDescription>
|
||||
<FormDescription>
|
||||
The name of the commission type.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
@ -77,6 +100,41 @@ export default function NewTypeForm({ options, extras, customInputs }: Props) {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="tagIds"
|
||||
render={({ field }) => {
|
||||
const selectedIds = field.value ?? [];
|
||||
const selectedOptions = tags
|
||||
.filter((t) => selectedIds.includes(t.id))
|
||||
.map((t) => ({ label: t.name, value: t.id }));
|
||||
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Tags</FormLabel>
|
||||
<FormControl>
|
||||
<MultipleSelector
|
||||
options={tags.map((t) => ({
|
||||
label: t.name,
|
||||
value: t.id,
|
||||
}))}
|
||||
placeholder="Select tags for this commission type"
|
||||
hidePlaceholderWhenSelected
|
||||
selectFirstItem
|
||||
value={selectedOptions}
|
||||
onChange={(options) =>
|
||||
field.onChange(options.map((o) => o.value))
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
Used to link this commission type to tagged artworks.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
|
||||
<CommissionOptionField options={options} />
|
||||
<CommissionExtraField extras={extras} />
|
||||
@ -84,10 +142,16 @@ export default function NewTypeForm({ options, extras, customInputs }: Props) {
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<Button type="submit">Submit</Button>
|
||||
<Button type="reset" variant="secondary" onClick={() => router.back()}>Cancel</Button>
|
||||
<Button
|
||||
type="reset"
|
||||
variant="secondary"
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,18 +1,32 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import { updateTag } from "@/actions/tags/updateTag";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { ArtCategory, Tag, TagAlias } from "@/generated/prisma/client";
|
||||
import { TagFormInput, tagSchema } from "@/schemas/artworks/tagSchema";
|
||||
import type { ArtCategory, Tag, TagAlias } from "@/generated/prisma/client";
|
||||
import { type TagFormInput, tagSchema } from "@/schemas/artworks/tagSchema";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import MultipleSelector from "../ui/multiselect";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "../ui/select";
|
||||
import { Switch } from "../ui/switch";
|
||||
import AliasEditor from "./AliasEditor";
|
||||
|
||||
@ -42,19 +56,19 @@ export default function EditTagForm({
|
||||
isParent: tag.isParent ?? false,
|
||||
showOnAnimalPage: tag.showOnAnimalPage ?? false,
|
||||
isVisible: tag.isVisible ?? true,
|
||||
aliases: tag.aliases?.map(a => a.alias) ?? []
|
||||
}
|
||||
})
|
||||
aliases: tag.aliases?.map((a) => a.alias) ?? [],
|
||||
},
|
||||
});
|
||||
|
||||
async function onSubmit(values: TagFormInput) {
|
||||
try {
|
||||
const updated = await updateTag(tag.id, values)
|
||||
console.log("Tag updated:", updated)
|
||||
toast("Tag updated.")
|
||||
router.push("/tags")
|
||||
const updated = await updateTag(tag.id, values);
|
||||
console.log("Tag updated:", updated);
|
||||
toast("Tag updated.");
|
||||
router.push("/tags");
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
toast("Failed to update tag.")
|
||||
console.error(err);
|
||||
toast("Failed to update tag.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,6 +78,10 @@ export default function EditTagForm({
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Tags can be used across artworks, commission types, and future miniatures. Category links
|
||||
are optional and control category-specific behavior.
|
||||
</p>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
{/* String */}
|
||||
@ -87,7 +105,10 @@ export default function EditTagForm({
|
||||
<FormItem>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea {...field} placeholder="A descriptive text (optional)" />
|
||||
<Textarea
|
||||
{...field}
|
||||
placeholder="A descriptive text (optional)"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -98,14 +119,14 @@ export default function EditTagForm({
|
||||
name="categoryIds"
|
||||
render={({ field }) => {
|
||||
const selectedOptions = categories
|
||||
.filter(cat => field.value?.includes(cat.id))
|
||||
.map(cat => ({ label: cat.name, value: cat.id }));
|
||||
.filter((cat) => field.value?.includes(cat.id))
|
||||
.map((cat) => ({ label: cat.name, value: cat.id }));
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Categories</FormLabel>
|
||||
<FormControl>
|
||||
<MultipleSelector
|
||||
defaultOptions={categories.map(cat => ({
|
||||
defaultOptions={categories.map((cat) => ({
|
||||
label: cat.name,
|
||||
value: cat.id,
|
||||
}))}
|
||||
@ -114,14 +135,14 @@ export default function EditTagForm({
|
||||
selectFirstItem
|
||||
value={selectedOptions}
|
||||
onChange={(options) => {
|
||||
const ids = options.map(option => option.value);
|
||||
const ids = options.map((option) => option.value);
|
||||
field.onChange(ids);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormField
|
||||
@ -159,7 +180,10 @@ export default function EditTagForm({
|
||||
<FormItem>
|
||||
<FormLabel>Aliases</FormLabel>
|
||||
<FormControl>
|
||||
<AliasEditor value={field.value ?? []} onChange={field.onChange} />
|
||||
<AliasEditor
|
||||
value={field.value ?? []}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -176,7 +200,10 @@ export default function EditTagForm({
|
||||
<FormDescription></FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
@ -193,7 +220,10 @@ export default function EditTagForm({
|
||||
<FormDescription></FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
@ -210,7 +240,10 @@ export default function EditTagForm({
|
||||
<FormDescription></FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
@ -218,10 +251,16 @@ export default function EditTagForm({
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Button type="submit">Submit</Button>
|
||||
<Button type="reset" variant="secondary" onClick={() => router.back()}>Cancel</Button>
|
||||
<Button
|
||||
type="reset"
|
||||
variant="secondary"
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div >
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,23 +1,42 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import { createTag } from "@/actions/tags/createTag";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { ArtCategory, Tag } from "@/generated/prisma/client";
|
||||
import { TagFormInput, tagSchema } from "@/schemas/artworks/tagSchema";
|
||||
import type { ArtCategory, Tag } from "@/generated/prisma/client";
|
||||
import { type TagFormInput, tagSchema } from "@/schemas/artworks/tagSchema";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { toast } from "sonner";
|
||||
import MultipleSelector from "../ui/multiselect";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../ui/select";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "../ui/select";
|
||||
import { Switch } from "../ui/switch";
|
||||
import AliasEditor from "./AliasEditor";
|
||||
|
||||
|
||||
export default function NewTagForm({ categories, allTags }: { categories: ArtCategory[], allTags: Tag[] }) {
|
||||
export default function NewTagForm({
|
||||
categories,
|
||||
allTags,
|
||||
}: {
|
||||
categories: ArtCategory[];
|
||||
allTags: Tag[];
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const form = useForm<TagFormInput>({
|
||||
resolver: zodResolver(tagSchema),
|
||||
@ -30,18 +49,18 @@ export default function NewTagForm({ categories, allTags }: { categories: ArtCat
|
||||
showOnAnimalPage: false,
|
||||
isVisible: true,
|
||||
aliases: [],
|
||||
}
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
async function onSubmit(values: TagFormInput) {
|
||||
try {
|
||||
const created = await createTag(values)
|
||||
console.log("Tag created:", created)
|
||||
toast("Tag created.")
|
||||
router.push("/tags")
|
||||
const created = await createTag(values);
|
||||
console.log("Tag created:", created);
|
||||
toast("Tag created.");
|
||||
router.push("/tags");
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
toast("Failed to create tag.")
|
||||
console.error(err);
|
||||
toast("Failed to create tag.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +70,11 @@ export default function NewTagForm({ categories, allTags }: { categories: ArtCat
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-8">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Tags can be used across artworks, commission types, and future
|
||||
miniatures. Category links are optional and control category-specific
|
||||
behavior.
|
||||
</p>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
{/* String */}
|
||||
@ -74,7 +98,10 @@ export default function NewTagForm({ categories, allTags }: { categories: ArtCat
|
||||
<FormItem>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea {...field} placeholder="A descriptive text (optional)" />
|
||||
<Textarea
|
||||
{...field}
|
||||
placeholder="A descriptive text (optional)"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -85,14 +112,14 @@ export default function NewTagForm({ categories, allTags }: { categories: ArtCat
|
||||
name="categoryIds"
|
||||
render={({ field }) => {
|
||||
const selectedOptions = categories
|
||||
.filter(cat => field.value?.includes(cat.id))
|
||||
.map(cat => ({ label: cat.name, value: cat.id }));
|
||||
.filter((cat) => field.value?.includes(cat.id))
|
||||
.map((cat) => ({ label: cat.name, value: cat.id }));
|
||||
return (
|
||||
<FormItem>
|
||||
<FormLabel>Categories</FormLabel>
|
||||
<FormControl>
|
||||
<MultipleSelector
|
||||
defaultOptions={categories.map(cat => ({
|
||||
defaultOptions={categories.map((cat) => ({
|
||||
label: cat.name,
|
||||
value: cat.id,
|
||||
}))}
|
||||
@ -101,14 +128,14 @@ export default function NewTagForm({ categories, allTags }: { categories: ArtCat
|
||||
selectFirstItem
|
||||
value={selectedOptions}
|
||||
onChange={(options) => {
|
||||
const ids = options.map(option => option.value);
|
||||
const ids = options.map((option) => option.value);
|
||||
field.onChange(ids);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormField
|
||||
@ -146,7 +173,10 @@ export default function NewTagForm({ categories, allTags }: { categories: ArtCat
|
||||
<FormItem>
|
||||
<FormLabel>Aliases</FormLabel>
|
||||
<FormControl>
|
||||
<AliasEditor value={field.value ?? []} onChange={field.onChange} />
|
||||
<AliasEditor
|
||||
value={field.value ?? []}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@ -163,7 +193,10 @@ export default function NewTagForm({ categories, allTags }: { categories: ArtCat
|
||||
<FormDescription></FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
@ -180,7 +213,10 @@ export default function NewTagForm({ categories, allTags }: { categories: ArtCat
|
||||
<FormDescription></FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
@ -197,7 +233,10 @@ export default function NewTagForm({ categories, allTags }: { categories: ArtCat
|
||||
<FormDescription></FormDescription>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Switch checked={field.value} onCheckedChange={field.onChange} />
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
@ -205,10 +244,16 @@ export default function NewTagForm({ categories, allTags }: { categories: ArtCat
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<Button type="submit">Submit</Button>
|
||||
<Button type="reset" variant="secondary" onClick={() => router.back()}>Cancel</Button>
|
||||
<Button
|
||||
type="reset"
|
||||
variant="secondary"
|
||||
onClick={() => router.back()}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
</div >
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -480,8 +480,7 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
|
||||
} // When onSearch is provided, we don't want to filter the options. You can still override it.
|
||||
filter={commandFilter()}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
<div
|
||||
className={cn(
|
||||
'min-h-10 rounded-md border border-input text-base ring-offset-background focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 md:text-sm',
|
||||
{
|
||||
@ -490,11 +489,6 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
|
||||
},
|
||||
className,
|
||||
)}
|
||||
onClick={() => {
|
||||
if (disabled) return;
|
||||
inputRef?.current?.focus();
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
<div className="relative flex flex-wrap gap-1">
|
||||
{selected.map((option) => {
|
||||
@ -581,7 +575,7 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
|
||||
<X />
|
||||
</button>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
<div className="relative">
|
||||
{open && (
|
||||
<CommandList
|
||||
@ -597,7 +591,10 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
|
||||
}}
|
||||
>
|
||||
{isLoading ? (
|
||||
<>{loadingIndicator}</>
|
||||
// biome-ignore lint: lint/complexity/noUselessFragments
|
||||
<>
|
||||
{loadingIndicator}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{EmptyItem()}
|
||||
@ -605,47 +602,45 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
|
||||
{!selectFirstItem && <CommandItem value="-" className="hidden" />}
|
||||
{orderedGroupEntries.map(([key, dropdowns]) => (
|
||||
<CommandGroup key={key} heading={key} className="h-full overflow-auto">
|
||||
<>
|
||||
{dropdowns.map((option) => {
|
||||
const alreadySelected = selected.some((s) => s.value === option.value);
|
||||
const disabledItem = option.disable || (showSelectedInDropdown && alreadySelected);
|
||||
{dropdowns.map((option) => {
|
||||
const alreadySelected = selected.some((s) => s.value === option.value);
|
||||
const disabledItem = option.disable || (showSelectedInDropdown && alreadySelected);
|
||||
|
||||
return (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
value={`${option.label}::${option.value}`}
|
||||
disabled={disabledItem}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onSelect={() => {
|
||||
if (disabledItem) return;
|
||||
return (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
value={`${option.label}::${option.value}`}
|
||||
disabled={disabledItem}
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onSelect={() => {
|
||||
if (disabledItem) return;
|
||||
|
||||
if (selected.length >= maxSelected) {
|
||||
onMaxSelected?.(selected.length);
|
||||
return;
|
||||
}
|
||||
if (selected.length >= maxSelected) {
|
||||
onMaxSelected?.(selected.length);
|
||||
return;
|
||||
}
|
||||
|
||||
setInputValue('');
|
||||
setInputValue('');
|
||||
|
||||
// Guard against duplicates (safety)
|
||||
if (selected.some((s) => s.value === option.value)) return;
|
||||
// Guard against duplicates (safety)
|
||||
if (selected.some((s) => s.value === option.value)) return;
|
||||
|
||||
const newOptions = [...selected, option];
|
||||
setSelected(newOptions);
|
||||
onChange?.(newOptions);
|
||||
}}
|
||||
className={cn(
|
||||
'cursor-pointer',
|
||||
disabledItem && 'cursor-default text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{option.label}
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
const newOptions = [...selected, option];
|
||||
setSelected(newOptions);
|
||||
onChange?.(newOptions);
|
||||
}}
|
||||
className={cn(
|
||||
'cursor-pointer',
|
||||
disabledItem && 'cursor-default text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{option.label}
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</CommandGroup>
|
||||
))}
|
||||
</>
|
||||
|
||||
@ -1,27 +1,27 @@
|
||||
"use client"
|
||||
"use client";
|
||||
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
||||
import * as React from "react"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react";
|
||||
import type * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Select({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
||||
return <SelectPrimitive.Root data-slot="select" {...props} />;
|
||||
}
|
||||
|
||||
function SelectGroup({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />
|
||||
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
|
||||
}
|
||||
|
||||
function SelectValue({
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
|
||||
}
|
||||
|
||||
function SelectTrigger({
|
||||
@ -30,7 +30,7 @@ function SelectTrigger({
|
||||
children,
|
||||
...props
|
||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||
size?: "sm" | "default"
|
||||
size?: "sm" | "default";
|
||||
}) {
|
||||
return (
|
||||
<SelectPrimitive.Trigger
|
||||
@ -38,7 +38,7 @@ function SelectTrigger({
|
||||
data-size={size}
|
||||
className={cn(
|
||||
"border-input data-placeholder:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@ -47,7 +47,7 @@ function SelectTrigger({
|
||||
<ChevronDownIcon className="size-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectContent({
|
||||
@ -65,7 +65,7 @@ function SelectContent({
|
||||
"bg-popover text-popover-foreground 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 relative z-50 max-h-(--radix-select-content-available-height) min-w-32 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
position={position}
|
||||
align={align}
|
||||
@ -76,7 +76,7 @@ function SelectContent({
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width) scroll-my-1"
|
||||
"h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width) scroll-my-1",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
@ -84,7 +84,7 @@ function SelectContent({
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectLabel({
|
||||
@ -97,7 +97,7 @@ function SelectLabel({
|
||||
className={cn("text-muted-foreground px-2 py-1.5 text-xs", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectItem({
|
||||
@ -110,7 +110,7 @@ function SelectItem({
|
||||
data-slot="select-item"
|
||||
className={cn(
|
||||
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
@ -124,7 +124,7 @@ function SelectItem({
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectSeparator({
|
||||
@ -137,7 +137,7 @@ function SelectSeparator({
|
||||
className={cn("bg-border pointer-events-none -mx-1 my-1 h-px", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectScrollUpButton({
|
||||
@ -149,13 +149,13 @@ function SelectScrollUpButton({
|
||||
data-slot="select-scroll-up-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function SelectScrollDownButton({
|
||||
@ -167,13 +167,13 @@ function SelectScrollDownButton({
|
||||
data-slot="select-scroll-down-button"
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon className="size-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
@ -187,5 +187,5 @@ export {
|
||||
SelectSeparator,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user