feat: add millon js, add models picker dialog, update builder

This commit is contained in:
BennyKok
2024-01-07 17:22:28 +08:00
parent 3c4bce630e
commit 01a9c1a1d6
33 changed files with 1693 additions and 190 deletions
+34 -31
View File
@@ -7,6 +7,7 @@ import {
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { ScrollArea } from "@/components/ui/scroll-area";
import { TableCell, TableRow } from "@/components/ui/table";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { getInputsFromWorkflow } from "@/lib/getInputsFromWorkflow";
@@ -72,13 +73,13 @@ export function DeploymentDisplay({
<DialogTrigger asChild className="appearance-none hover:cursor-pointer">
<TableRow>
<TableCell className="capitalize">{deployment.environment}</TableCell>
<TableCell className="font-medium">
<TableCell className="font-medium truncate">
{deployment.version?.version}
</TableCell>
<TableCell className="font-medium">
<TableCell className="font-medium truncate">
{deployment.machine?.name}
</TableCell>
<TableCell className="text-right">
<TableCell className="text-right truncate">
{getRelativeTime(deployment.updated_at)}
</TableCell>
</TableRow>
@@ -90,34 +91,36 @@ export function DeploymentDisplay({
</DialogTitle>
<DialogDescription>Code for your deployment client</DialogDescription>
</DialogHeader>
<Tabs defaultValue="js" className="w-full">
<TabsList className="grid w-fit grid-cols-2">
<TabsTrigger value="js">js</TabsTrigger>
<TabsTrigger value="curl">curl</TabsTrigger>
</TabsList>
<TabsContent className="flex flex-col gap-2" value="js">
Trigger the workflow
<CodeBlock
lang="js"
code={formatCode(jsTemplate, deployment, domain, workflowInput)}
/>
Check the status of the run, and retrieve the outputs
<CodeBlock
lang="js"
code={formatCode(jsTemplate_checkStatus, deployment, domain)}
/>
</TabsContent>
<TabsContent className="flex flex-col gap-2" value="curl">
<CodeBlock
lang="bash"
code={formatCode(curlTemplate, deployment, domain)}
/>
<CodeBlock
lang="bash"
code={formatCode(curlTemplate_checkStatus, deployment, domain)}
/>
</TabsContent>
</Tabs>
<ScrollArea className="max-h-[600px]">
<Tabs defaultValue="js" className="w-full">
<TabsList className="grid w-fit grid-cols-2">
<TabsTrigger value="js">js</TabsTrigger>
<TabsTrigger value="curl">curl</TabsTrigger>
</TabsList>
<TabsContent className="flex flex-col gap-2" value="js">
Trigger the workflow
<CodeBlock
lang="js"
code={formatCode(jsTemplate, deployment, domain, workflowInput)}
/>
Check the status of the run, and retrieve the outputs
<CodeBlock
lang="js"
code={formatCode(jsTemplate_checkStatus, deployment, domain)}
/>
</TabsContent>
<TabsContent className="flex flex-col gap-2" value="curl">
<CodeBlock
lang="bash"
code={formatCode(curlTemplate, deployment, domain)}
/>
<CodeBlock
lang="bash"
code={formatCode(curlTemplate_checkStatus, deployment, domain)}
/>
</TabsContent>
</Tabs>
</ScrollArea>
</DialogContent>
</Dialog>
);
+2
View File
@@ -43,6 +43,7 @@ export function InsertModal<
<DialogTitle>{props.title}</DialogTitle>
<DialogDescription>{props.description}</DialogDescription>
</DialogHeader>
{/* <ScrollArea> */}
<AutoForm
fieldConfig={props.fieldConfig}
formSchema={props.formSchema}
@@ -60,6 +61,7 @@ export function InsertModal<
</AutoFormSubmit>
</div>
</AutoForm>
{/* </ScrollArea> */}
</DialogContent>
</Dialog>
);
+4 -1
View File
@@ -14,12 +14,13 @@ export function MachineBuildLog({
endpoint: string;
}) {
const [logs, setLogs] = useState<LogsType>([]);
const [finished, setFinished] = useState(false);
const wsEndpoint = endpoint.replace(/^http/, "ws");
const { lastMessage, readyState } = useWebSocket(
`${wsEndpoint}/ws/${machine_id}`,
{
shouldReconnect: () => true,
shouldReconnect: () => !finished,
reconnectAttempts: 20,
reconnectInterval: 1000,
}
@@ -36,6 +37,8 @@ export function MachineBuildLog({
if (message?.event === "LOGS") {
setLogs((logs) => [...(logs ?? []), message.data]);
} else if (message?.event === "FINISHED") {
setFinished(true);
}
}, [lastMessage]);
+62 -19
View File
@@ -33,6 +33,7 @@ import {
deleteMachine,
disableMachine,
enableMachine,
updateCustomMachine,
updateMachine,
} from "@/server/curdMachine";
import type {
@@ -95,7 +96,7 @@ export const columns: ColumnDef<Machine>[] = [
cell: ({ row }) => {
return (
// <a className="hover:underline" href={`/${row.original.id}`}>
<div className="flex flex-row gap-2 items-center">
<div className="flex flex-row gap-2 items-center truncate">
<a href={`/machines/${row.original.id}`} className="hover:underline">
{row.getValue("name")}
</a>
@@ -115,7 +116,9 @@ export const columns: ColumnDef<Machine>[] = [
header: () => <div className="text-left">Endpoint</div>,
cell: ({ row }) => {
return (
<div className="text-left font-medium">{row.original.endpoint}</div>
<div className="text-left font-medium truncate max-w-[400px]">
{row.original.endpoint}
</div>
);
},
},
@@ -123,7 +126,11 @@ export const columns: ColumnDef<Machine>[] = [
accessorKey: "type",
header: () => <div className="text-left">Type</div>,
cell: ({ row }) => {
return <div className="text-left font-medium">{row.original.type}</div>;
return (
<div className="text-left font-medium truncate">
{row.original.type}
</div>
);
},
},
{
@@ -133,7 +140,7 @@ export const columns: ColumnDef<Machine>[] = [
header: ({ column }) => {
return (
<button
className="w-full flex items-center justify-end hover:underline"
className="w-full flex items-center justify-end hover:underline truncate"
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
>
Update Date
@@ -195,22 +202,50 @@ export const columns: ColumnDef<Machine>[] = [
Edit
</DropdownMenuItem>
</DropdownMenuContent>
<UpdateModal
data={machine}
open={open}
setOpen={setOpen}
title="Edit"
description="Edit machines"
serverAction={updateMachine}
formSchema={addMachineSchema}
fieldConfig={{
auth_token: {
inputProps: {
type: "password",
{machine.type === "comfy-deploy-serverless" ? (
<UpdateModal
data={machine}
open={open}
setOpen={setOpen}
title="Edit"
description="Edit machines"
serverAction={updateCustomMachine}
formSchema={addCustomMachineSchema}
fieldConfig={{
type: {
fieldType: "fallback",
inputProps: {
disabled: true,
showLabel: false,
type: "hidden",
},
},
},
}}
/>
snapshot: {
fieldType: "snapshot",
},
models: {
fieldType: "models",
},
}}
/>
) : (
<UpdateModal
data={machine}
open={open}
setOpen={setOpen}
title="Edit"
description="Edit machines"
serverAction={updateMachine}
formSchema={addMachineSchema}
fieldConfig={{
auth_token: {
inputProps: {
type: "password",
},
},
}}
/>
)}
</DropdownMenu>
);
},
@@ -273,8 +308,16 @@ export function MachineList({ data }: { data: Machine[] }) {
fieldType: "fallback",
inputProps: {
disabled: true,
showLabel: false,
type: "hidden",
},
},
snapshot: {
fieldType: "snapshot",
},
models: {
fieldType: "models",
},
}}
/>
</div>
+5 -2
View File
@@ -191,9 +191,11 @@ export function RunWorkflowButton({
</DialogTrigger>
<DialogContent className="max-w-xl">
<DialogHeader>
<DialogTitle>Run inputs</DialogTitle>
<DialogTitle>Confirm run</DialogTitle>
<DialogDescription>
Run your workflow with custom inputs
{schema
? "Run your workflow with custom inputs"
: "Confirm to run your workflow"}
</DialogDescription>
</DialogHeader>
{/* <div className="max-h-96 overflow-y-scroll"> */}
@@ -203,6 +205,7 @@ export function RunWorkflowButton({
values={values}
onValuesChange={setValues}
onSubmit={runWorkflow}
className="px-1"
>
<div className="flex justify-end">
<AutoFormSubmit>
@@ -0,0 +1,157 @@
"use client";
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
import { Check, ChevronsUpDown } from "lucide-react";
import * as React from "react";
import { useRef } from "react";
import { z } from "zod";
const Model = z.object({
name: z.string(),
type: z.string(),
base: z.string(),
save_path: z.string(),
description: z.string(),
reference: z.string(),
filename: z.string(),
url: z.string(),
});
const ModelList = z.array(Model);
export const ModelListWrapper = z.object({
models: ModelList,
});
export function ModelPickerView({
field,
}: Pick<AutoFormInputComponentProps, "field">) {
const value = (field.value as z.infer<typeof ModelList>) ?? [];
const [open, setOpen] = React.useState(false);
const [modelList, setModelList] =
React.useState<z.infer<typeof ModelListWrapper>>();
// const [selectedModels, setSelectedModels] = React.useState<
// z.infer<typeof ModelList>
// >(field.value ?? []);
React.useEffect(() => {
const controller = new AbortController();
fetch(
"https://raw.githubusercontent.com/ltdrdata/ComfyUI-Manager/main/model-list.json",
{
signal: controller.signal,
}
)
.then((x) => x.json())
.then((a) => {
setModelList(ModelListWrapper.parse(a));
});
return () => {
controller.abort();
};
}, []);
function toggleModel(model: z.infer<typeof Model>) {
const prevSelectedModels = value;
if (
prevSelectedModels.some(
(selectedModel) =>
selectedModel.url + selectedModel.name === model.url + model.name
)
) {
field.onChange(
prevSelectedModels.filter(
(selectedModel) =>
selectedModel.url + selectedModel.name !== model.url + model.name
)
);
} else {
field.onChange([...prevSelectedModels, model]);
}
}
// React.useEffect(() => {
// field.onChange(selectedModels);
// }, [selectedModels]);
const containerRef = useRef<HTMLDivElement>(null);
return (
<div className="" ref={containerRef}>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between flex"
>
Select models... ({value.length} selected)
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[375px] p-0" side="top">
<Command>
<CommandInput placeholder="Search framework..." className="h-9" />
<CommandEmpty>No framework found.</CommandEmpty>
<CommandList className="pointer-events-auto">
<CommandGroup>
{modelList?.models.map((model) => (
<CommandItem
key={model.url + model.name}
value={model.url}
onSelect={() => {
toggleModel(model);
// Update field.onChange to pass the array of selected models
}}
>
{model.name}
<Check
className={cn(
"ml-auto h-4 w-4",
value.some(
(selectedModel) => selectedModel.url === model.url
)
? "opacity-100"
: "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
{field.value && (
<ScrollArea className="w-full bg-gray-100 mx-auto max-w-[360px] rounded-lg mt-2">
<div className="max-h-[200px]">
<pre className="p-2 rounded-md text-xs ">
{JSON.stringify(field.value, null, 2)}
</pre>
</div>
</ScrollArea>
)}
</div>
);
}
@@ -0,0 +1,125 @@
"use client";
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
import { Button } from "@/components/ui/button";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
} from "@/components/ui/command";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
import { findAllDeployments } from "@/server/curdDeploments";
import { Check, ChevronsUpDown } from "lucide-react";
import * as React from "react";
export function SnapshotPickerView({
field,
}: Pick<AutoFormInputComponentProps, "field">) {
const [open, setOpen] = React.useState(false);
const [selected, setSelected] = React.useState<string | null>(null);
const [frameworks, setFramework] = React.useState<
{
id: string;
label: string;
value: string;
}[]
>();
React.useEffect(() => {
findAllDeployments().then((a) => {
console.log(a);
const frameworks = a
.map((item) => {
if (
item.deployments.length == 0 ||
item.deployments[0].version.snapshot == null
)
return null;
return {
id: item.deployments[0].version.id,
label: `${item.name} - ${item.deployments[0].environment}`,
value: JSON.stringify(item.deployments[0].version.snapshot),
};
})
.filter((item): item is NonNullable<typeof item> => item != null);
setFramework(frameworks);
});
}, []);
function findItem(value: string) {
// console.log(frameworks);
return frameworks?.find((item) => item.id === value);
}
return (
<div className="">
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
aria-expanded={open}
className="w-full justify-between flex"
>
{selected ? findItem(selected)?.label : "Select snapshot..."}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[375px] p-0">
<Command>
<CommandInput placeholder="Search framework..." className="h-9" />
<CommandEmpty>No framework found.</CommandEmpty>
<CommandGroup>
{frameworks?.map((framework) => (
<CommandItem
key={framework.id}
value={framework.id}
onSelect={(currentValue) => {
setSelected(currentValue);
const json =
frameworks?.find((item) => item.id === currentValue)
?.value ?? null;
field.onChange(json ? JSON.parse(json) : null);
setOpen(false);
}}
>
{framework.label}
<Check
className={cn(
"ml-auto h-4 w-4",
field.value === framework.value
? "opacity-100"
: "opacity-0"
)}
/>
</CommandItem>
))}
</CommandGroup>
</Command>
</PopoverContent>
</Popover>
{field.value && (
<ScrollArea className="w-full bg-gray-100 mx-auto max-w-[360px] rounded-lg mt-2">
<div className="max-h-[200px]">
<pre className="p-2 rounded-md text-xs ">
{JSON.stringify(field.value, null, 2)}
</pre>
</div>
</ScrollArea>
)}
</div>
);
}
@@ -0,0 +1,39 @@
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
import {
FormControl,
FormDescription,
FormItem,
FormLabel,
FormMessage,
} from "../ui/form";
import { LoadingIcon } from "@/components/LoadingIcon";
import { ModelPickerView } from "@/components/custom-form/ModelPickerView";
// import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
import * as React from "react";
import { Suspense } from "react";
export default function AutoFormModelsPicker({
label,
isRequired,
field,
fieldConfigItem,
zodItem,
}: AutoFormInputComponentProps) {
return (
<FormItem>
<FormLabel>
{label}
{isRequired && <span className="text-destructive"> *</span>}
</FormLabel>
<FormControl>
<Suspense fallback={<LoadingIcon />}>
<ModelPickerView field={field} />
</Suspense>
</FormControl>
{fieldConfigItem.description && (
<FormDescription>{fieldConfigItem.description}</FormDescription>
)}
<FormMessage />
</FormItem>
);
}
@@ -0,0 +1,39 @@
import type { AutoFormInputComponentProps } from "../ui/auto-form/types";
import {
FormControl,
FormDescription,
FormItem,
FormLabel,
FormMessage,
} from "../ui/form";
import { SnapshotPickerView } from "./SnapshotPickerView";
import { LoadingIcon } from "@/components/LoadingIcon";
// import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
import * as React from "react";
import { Suspense } from "react";
export default function AutoFormSnapshotPicker({
label,
isRequired,
field,
fieldConfigItem,
zodItem,
}: AutoFormInputComponentProps) {
return (
<FormItem>
<FormLabel>
{label}
{isRequired && <span className="text-destructive"> *</span>}
</FormLabel>
<FormControl>
<Suspense fallback={<LoadingIcon />}>
<SnapshotPickerView field={field} />
</Suspense>
</FormControl>
{fieldConfigItem.description && (
<FormDescription>{fieldConfigItem.description}</FormDescription>
)}
<FormMessage />
</FormItem>
);
}
@@ -6,6 +6,8 @@ import AutoFormNumber from "./fields/number";
import AutoFormRadioGroup from "./fields/radio-group";
import AutoFormSwitch from "./fields/switch";
import AutoFormTextarea from "./fields/textarea";
import AutoFormModelsPicker from "@/components/custom-form/model-picker";
import AutoFormSnapshotPicker from "@/components/custom-form/snapshot-picker";
export const INPUT_COMPONENTS = {
checkbox: AutoFormCheckbox,
@@ -16,6 +18,10 @@ export const INPUT_COMPONENTS = {
textarea: AutoFormTextarea,
number: AutoFormNumber,
fallback: AutoFormInput,
// Customs
snapshot: AutoFormSnapshotPicker,
models: AutoFormModelsPicker,
};
/**
@@ -6,7 +6,7 @@ import {
FormMessage,
} from "../../form";
import { Input } from "../../input";
import { AutoFormInputComponentProps } from "../types";
import type { AutoFormInputComponentProps } from "../types";
export default function AutoFormInput({
label,
+10 -5
View File
@@ -6,6 +6,7 @@ import type { FieldConfig } from "./types";
import type { ZodObjectOrWrapped } from "./utils";
import { getDefaultValues, getObjectFormSchema } from "./utils";
import AutoFormObject from "@/components/ui/auto-form/fields/object";
import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from "@/lib/utils";
import { zodResolver } from "@hookform/resolvers/zod";
import type { DefaultValues } from "react-hook-form";
@@ -68,11 +69,15 @@ function AutoForm<SchemaType extends ZodObjectOrWrapped>({
}}
className={cn("space-y-5", className)}
>
<AutoFormObject
schema={objectFormSchema}
form={form}
fieldConfig={fieldConfig}
/>
<ScrollArea>
<div className="max-h-[400px] px-1 py-1 w-full">
<AutoFormObject
schema={objectFormSchema}
form={form}
fieldConfig={fieldConfig}
/>
</div>
</ScrollArea>
{children}
</form>
+3 -3
View File
@@ -1,6 +1,6 @@
import { ControllerRenderProps, FieldValues } from "react-hook-form";
import * as z from "zod";
import { INPUT_COMPONENTS } from "./config";
import type { INPUT_COMPONENTS } from "./config";
import type { ControllerRenderProps, FieldValues } from "react-hook-form";
import type * as z from "zod";
export type FieldConfigItem = {
description?: React.ReactNode;
+154
View File
@@ -0,0 +1,154 @@
"use client";
import { Dialog, DialogContent } from "@/components/ui/dialog";
import { cn } from "@/lib/utils";
import { type DialogProps } from "@radix-ui/react-dialog";
import { Command as CommandPrimitive } from "cmdk";
import { Search } from "lucide-react";
import * as React from "react";
const Command = React.forwardRef<
React.ElementRef<typeof CommandPrimitive>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
<CommandPrimitive
ref={ref}
className={cn(
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
className
)}
{...props}
/>
));
Command.displayName = CommandPrimitive.displayName;
interface CommandDialogProps extends DialogProps {}
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
return (
<Dialog {...props}>
<DialogContent className="overflow-hidden p-0 shadow-lg">
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
{children}
</Command>
</DialogContent>
</Dialog>
);
};
const CommandInput = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Input>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
<CommandPrimitive.Input
ref={ref}
className={cn(
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
/>
</div>
));
CommandInput.displayName = CommandPrimitive.Input.displayName;
const CommandList = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.List>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
<CommandPrimitive.List
ref={ref}
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
{...props}
/>
));
CommandList.displayName = CommandPrimitive.List.displayName;
const CommandEmpty = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Empty>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
<CommandPrimitive.Empty
ref={ref}
className="py-6 text-center text-sm"
{...props}
/>
));
CommandEmpty.displayName = CommandPrimitive.Empty.displayName;
const CommandGroup = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Group>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Group
ref={ref}
className={cn(
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
className
)}
{...props}
/>
));
CommandGroup.displayName = CommandPrimitive.Group.displayName;
const CommandSeparator = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Separator
ref={ref}
className={cn("-mx-1 h-px bg-border", className)}
{...props}
/>
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;
const CommandItem = React.forwardRef<
React.ElementRef<typeof CommandPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
<CommandPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
/>
));
CommandItem.displayName = CommandPrimitive.Item.displayName;
const CommandShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn(
"ml-auto text-xs tracking-widest text-muted-foreground",
className
)}
{...props}
/>
);
};
CommandShortcut.displayName = "CommandShortcut";
export {
Command,
CommandDialog,
CommandInput,
CommandList,
CommandEmpty,
CommandGroup,
CommandItem,
CommandShortcut,
CommandSeparator,
};
+6 -7
View File
@@ -1,6 +1,5 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { cn } from "@/lib/utils";
import * as React from "react";
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
@@ -17,9 +16,9 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
ref={ref}
{...props}
/>
)
);
}
)
Input.displayName = "Input"
);
Input.displayName = "Input";
export { Input }
export { Input };
+23 -10
View File
@@ -1,13 +1,12 @@
"use client"
"use client";
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import * as React from "react";
import { cn } from "@/lib/utils"
const Popover = PopoverPrimitive.Root;
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
@@ -22,10 +21,24 @@ const PopoverContent = React.forwardRef<
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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
)}
// https://github.com/shadcn-ui/ui/pull/2123/files#diff-e43c79299129c57a9055c3d6a20ff7bbeea4035dd6aa80eebe381b29f82f90a8
onWheel={(e) => {
e.stopPropagation();
const isScrollingDown = e.deltaY > 0;
if (isScrollingDown) {
e.currentTarget.dispatchEvent(
new KeyboardEvent("keydown", { key: "ArrowDown" })
);
} else {
e.currentTarget.dispatchEvent(
new KeyboardEvent("keydown", { key: "ArrowUp" })
);
}
}}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent }
export { Popover, PopoverTrigger, PopoverContent };