mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-02-05 13:10:08 +00:00
Dashboard Redesign (#48)
* feat: add user_template setting * style: header * style: page padding * style: header * feat: header now time * style: login page * feat: nav indicator * style: button inset shadow * style: footer text size * feat: header show login_ip * fix: error toast * fix: frontend_templates setting * fix: lint * feat: pr auto format * chore: auto-fix linting and formatting issues --------- Co-authored-by: hamster1963 <hamster1963@users.noreply.github.com>
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
import { IconButton } from "@/components/xui/icon-button";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -10,22 +9,33 @@ import {
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import { KeyedMutator } from "swr";
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
import { KeyedMutator } from "swr"
|
||||
|
||||
interface ButtonGroupProps<E, U> {
|
||||
className?: string;
|
||||
children: React.ReactNode;
|
||||
delete: { fn: (id: E[]) => Promise<void>, id: E, mutate: KeyedMutator<U> };
|
||||
className?: string
|
||||
children: React.ReactNode
|
||||
delete: { fn: (id: E[]) => Promise<void>; id: E; mutate: KeyedMutator<U> }
|
||||
}
|
||||
|
||||
export function ActionButtonGroup<E, U>({ className, children, delete: { fn, id, mutate } }: ButtonGroupProps<E, U>) {
|
||||
const { t } = useTranslation();
|
||||
export function ActionButtonGroup<E, U>({
|
||||
className,
|
||||
children,
|
||||
delete: { fn, id, mutate },
|
||||
}: ButtonGroupProps<E, U>) {
|
||||
const { t } = useTranslation()
|
||||
const handleDelete = async () => {
|
||||
await fn([id]);
|
||||
await mutate();
|
||||
try {
|
||||
await fn([id])
|
||||
} catch (error: any) {
|
||||
toast(t("Error"), {
|
||||
description: error.message,
|
||||
})
|
||||
}
|
||||
await mutate()
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -44,7 +54,12 @@ export function ActionButtonGroup<E, U>({ className, children, delete: { fn, id,
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("Close")}</AlertDialogCancel>
|
||||
<AlertDialogAction className={buttonVariants({ variant: "destructive" })} onClick={handleDelete}>{t("Confirm")}</AlertDialogAction>
|
||||
<AlertDialogAction
|
||||
className={buttonVariants({ variant: "destructive" })}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
{t("Confirm")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { createAlertRule, updateAlertRule } from "@/api/alert-rule"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
@@ -9,14 +11,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -25,29 +19,35 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { ModelAlertRule } from "@/types"
|
||||
import { createAlertRule, updateAlertRule } from "@/api/alert-rule"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { conv } from "@/lib/utils"
|
||||
import { useState } from "react"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { asOptionalField } from "@/lib/utils"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { triggerModes } from "@/types"
|
||||
import { Textarea } from "./ui/textarea"
|
||||
import { useNotification } from "@/hooks/useNotfication"
|
||||
import { Combobox } from "./ui/combobox"
|
||||
import { conv } from "@/lib/utils"
|
||||
import { asOptionalField } from "@/lib/utils"
|
||||
import { ModelAlertRule } from "@/types"
|
||||
import { triggerModes } from "@/types"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { z } from "zod"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Combobox } from "./ui/combobox"
|
||||
import { Textarea } from "./ui/textarea"
|
||||
|
||||
interface AlertRuleCardProps {
|
||||
data?: ModelAlertRule;
|
||||
mutate: KeyedMutator<ModelAlertRule[]>;
|
||||
data?: ModelAlertRule
|
||||
mutate: KeyedMutator<ModelAlertRule[]>
|
||||
}
|
||||
|
||||
const ruleSchema = z.object({
|
||||
@@ -56,87 +56,91 @@ const ruleSchema = z.object({
|
||||
max: asOptionalField(z.number()),
|
||||
cycle_start: asOptionalField(z.string()),
|
||||
cycle_interval: asOptionalField(z.number()),
|
||||
cycle_unit: asOptionalField(z.enum(['hour', 'day', 'week', 'month', 'year'])),
|
||||
cycle_unit: asOptionalField(z.enum(["hour", "day", "week", "month", "year"])),
|
||||
duration: asOptionalField(z.number()),
|
||||
cover: z.number().int().min(0),
|
||||
ignore: asOptionalField(z.record(z.boolean())),
|
||||
next_transfer_at: asOptionalField(z.record(z.string())),
|
||||
last_cycle_status: asOptionalField((z.boolean())),
|
||||
});
|
||||
last_cycle_status: asOptionalField(z.boolean()),
|
||||
})
|
||||
|
||||
const alertRuleFormSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
rules_raw: z.string().refine((val) => {
|
||||
try {
|
||||
JSON.parse(val);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}, {
|
||||
message: 'Invalid JSON string',
|
||||
}),
|
||||
rules_raw: z.string().refine(
|
||||
(val) => {
|
||||
try {
|
||||
JSON.parse(val)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
},
|
||||
{
|
||||
message: "Invalid JSON string",
|
||||
},
|
||||
),
|
||||
rules: z.array(ruleSchema),
|
||||
fail_trigger_tasks: z.array(z.number()),
|
||||
recover_trigger_tasks: z.array(z.number()),
|
||||
notification_group_id: z.coerce.number().int(),
|
||||
trigger_mode: z.coerce.number().int().min(0),
|
||||
enable: asOptionalField(z.boolean()),
|
||||
});
|
||||
})
|
||||
|
||||
export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<z.infer<typeof alertRuleFormSchema>>({
|
||||
resolver: zodResolver(alertRuleFormSchema),
|
||||
defaultValues: data ? {
|
||||
...data,
|
||||
rules_raw: JSON.stringify(data.rules),
|
||||
} : {
|
||||
name: "",
|
||||
rules_raw: "",
|
||||
rules: [],
|
||||
fail_trigger_tasks: [],
|
||||
recover_trigger_tasks: [],
|
||||
notification_group_id: 0,
|
||||
trigger_mode: 0,
|
||||
},
|
||||
defaultValues: data
|
||||
? {
|
||||
...data,
|
||||
rules_raw: JSON.stringify(data.rules),
|
||||
}
|
||||
: {
|
||||
name: "",
|
||||
rules_raw: "",
|
||||
rules: [],
|
||||
fail_trigger_tasks: [],
|
||||
recover_trigger_tasks: [],
|
||||
notification_group_id: 0,
|
||||
trigger_mode: 0,
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof alertRuleFormSchema>) => {
|
||||
values.rules = JSON.parse(values.rules_raw);
|
||||
const { rules_raw, ...requiredFields } = values;
|
||||
data?.id ? await updateAlertRule(data.id, requiredFields) : await createAlertRule(requiredFields);
|
||||
setOpen(false);
|
||||
await mutate();
|
||||
form.reset();
|
||||
values.rules = JSON.parse(values.rules_raw)
|
||||
const { rules_raw, ...requiredFields } = values
|
||||
data?.id
|
||||
? await updateAlertRule(data.id, requiredFields)
|
||||
: await createAlertRule(requiredFields)
|
||||
setOpen(false)
|
||||
await mutate()
|
||||
form.reset()
|
||||
}
|
||||
|
||||
const { notifierGroup } = useNotification();
|
||||
const ngroupList = notifierGroup?.map(ng => ({
|
||||
const { notifierGroup } = useNotification()
|
||||
const ngroupList = notifierGroup?.map((ng) => ({
|
||||
value: `${ng.group.id}`,
|
||||
label: ng.group.name,
|
||||
})) || [{ value: "", label: "" }];
|
||||
})) || [{ value: "", label: "" }]
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{data
|
||||
?
|
||||
<IconButton variant="outline" icon="edit" />
|
||||
:
|
||||
<IconButton icon="plus" />
|
||||
}
|
||||
{data ? <IconButton variant="outline" icon="edit" /> : <IconButton icon="plus" />}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{data ? t("EditAlertRule") : t("CreateAlertRule")}</DialogTitle>
|
||||
<DialogTitle>
|
||||
{data ? t("EditAlertRule") : t("CreateAlertRule")}
|
||||
</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -148,9 +152,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
<FormItem>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
/>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -163,10 +165,7 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
<FormItem>
|
||||
<FormLabel>{t("Rules")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-y"
|
||||
{...field}
|
||||
/>
|
||||
<Textarea className="resize-y" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -196,7 +195,10 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("TriggerMode")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
@@ -204,7 +206,9 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.entries(triggerModes).map(([k, v]) => (
|
||||
<SelectItem key={k} value={k}>{v}</SelectItem>
|
||||
<SelectItem key={k} value={k}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -217,15 +221,20 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
name="fail_trigger_tasks"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("TasksToTriggerOnAlert") + t("SeparateWithComma")}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("TasksToTriggerOnAlert") +
|
||||
t("SeparateWithComma")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
{...field}
|
||||
value={conv.arrToStr(field.value ?? [])}
|
||||
onChange={e => {
|
||||
const arr = conv.strToArr(e.target.value).map(Number);
|
||||
field.onChange(arr);
|
||||
onChange={(e) => {
|
||||
const arr = conv
|
||||
.strToArr(e.target.value)
|
||||
.map(Number)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -238,15 +247,20 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
name="recover_trigger_tasks"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("TasksToTriggerAfterRecovery") + t("SeparateWithComma")}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("TasksToTriggerAfterRecovery") +
|
||||
t("SeparateWithComma")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
{...field}
|
||||
value={conv.arrToStr(field.value ?? [])}
|
||||
onChange={e => {
|
||||
const arr = conv.strToArr(e.target.value).map(Number);
|
||||
field.onChange(arr);
|
||||
onChange={(e) => {
|
||||
const arr = conv
|
||||
.strToArr(e.target.value)
|
||||
.map(Number)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -278,7 +292,9 @@ export const AlertRuleCard: React.FC<AlertRuleCardProps> = ({ data, mutate }) =>
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
<Button type="submit" className="my-2">
|
||||
{t("Confirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createCron, updateCron } from "@/api/cron"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -25,28 +27,26 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { ModelCron } from "@/types"
|
||||
import { useState } from "react"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { createCron, updateCron } from "@/api/cron"
|
||||
import { asOptionalField } from "@/lib/utils"
|
||||
import { cronTypes, cronCoverageTypes } from "@/types"
|
||||
import { Textarea } from "./ui/textarea"
|
||||
import { useServer } from "@/hooks/useServer"
|
||||
import { useNotification } from "@/hooks/useNotfication"
|
||||
import { MultiSelect } from "./xui/multi-select"
|
||||
import { Combobox } from "./ui/combobox"
|
||||
import { useServer } from "@/hooks/useServer"
|
||||
import { asOptionalField } from "@/lib/utils"
|
||||
import { ModelCron } from "@/types"
|
||||
import { cronCoverageTypes, cronTypes } from "@/types"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { z } from "zod"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Combobox } from "./ui/combobox"
|
||||
import { Textarea } from "./ui/textarea"
|
||||
import { MultiSelect } from "./xui/multi-select"
|
||||
|
||||
interface CronCardProps {
|
||||
data?: ModelCron;
|
||||
mutate: KeyedMutator<ModelCron[]>;
|
||||
data?: ModelCron
|
||||
mutate: KeyedMutator<ModelCron[]>
|
||||
}
|
||||
|
||||
const cronFormSchema = z.object({
|
||||
@@ -58,61 +58,58 @@ const cronFormSchema = z.object({
|
||||
cover: z.coerce.number().int(),
|
||||
push_successful: asOptionalField(z.boolean()),
|
||||
notification_group_id: z.coerce.number().int(),
|
||||
});
|
||||
})
|
||||
|
||||
export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<z.infer<typeof cronFormSchema>>({
|
||||
resolver: zodResolver(cronFormSchema),
|
||||
defaultValues: data ? data : {
|
||||
name: "",
|
||||
task_type: 0,
|
||||
scheduler: "",
|
||||
servers: [],
|
||||
cover: 0,
|
||||
notification_group_id: 0,
|
||||
},
|
||||
defaultValues: data
|
||||
? data
|
||||
: {
|
||||
name: "",
|
||||
task_type: 0,
|
||||
scheduler: "",
|
||||
servers: [],
|
||||
cover: 0,
|
||||
notification_group_id: 0,
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof cronFormSchema>) => {
|
||||
data?.id ? await updateCron(data.id, values) : await createCron(values);
|
||||
setOpen(false);
|
||||
await mutate();
|
||||
form.reset();
|
||||
data?.id ? await updateCron(data.id, values) : await createCron(values)
|
||||
setOpen(false)
|
||||
await mutate()
|
||||
form.reset()
|
||||
}
|
||||
|
||||
const { servers } = useServer();
|
||||
const serverList = servers?.map(s => ({
|
||||
const { servers } = useServer()
|
||||
const serverList = servers?.map((s) => ({
|
||||
value: `${s.id}`,
|
||||
label: s.name,
|
||||
})) || [{ value: "", label: "" }];
|
||||
})) || [{ value: "", label: "" }]
|
||||
|
||||
const { notifierGroup } = useNotification();
|
||||
const ngroupList = notifierGroup?.map(ng => ({
|
||||
const { notifierGroup } = useNotification()
|
||||
const ngroupList = notifierGroup?.map((ng) => ({
|
||||
value: `${ng.group.id}`,
|
||||
label: ng.group.name,
|
||||
})) || [{ value: "", label: "" }];
|
||||
})) || [{ value: "", label: "" }]
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{data
|
||||
?
|
||||
<IconButton variant="outline" icon="edit" />
|
||||
:
|
||||
<IconButton icon="plus" />
|
||||
}
|
||||
{data ? <IconButton variant="outline" icon="edit" /> : <IconButton icon="plus" />}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{data?t("EditTask"):t("CreateTask")}</DialogTitle>
|
||||
<DialogTitle>{data ? t("EditTask") : t("CreateTask")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -124,10 +121,7 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My Task"
|
||||
{...field}
|
||||
/>
|
||||
<Input placeholder="My Task" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -139,7 +133,10 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Type")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select task type" />
|
||||
@@ -147,7 +144,9 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.entries(cronTypes).map(([k, v]) => (
|
||||
<SelectItem key={k} value={k}>{v}</SelectItem>
|
||||
<SelectItem key={k} value={k}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -160,7 +159,7 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
name="scheduler"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("CronExpression") }</FormLabel>
|
||||
<FormLabel>{t("CronExpression")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="0 0 0 3 * * (At 3 AM)"
|
||||
@@ -178,10 +177,7 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("Command")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-y"
|
||||
{...field}
|
||||
/>
|
||||
<Textarea className="resize-y" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -193,16 +189,23 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Coverage")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.entries(cronCoverageTypes).map(([k, v]) => (
|
||||
<SelectItem key={k} value={k}>{v}</SelectItem>
|
||||
))}
|
||||
{Object.entries(cronCoverageTypes).map(
|
||||
([k, v]) => (
|
||||
<SelectItem key={k} value={k}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
@@ -218,9 +221,9 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
<FormControl>
|
||||
<MultiSelect
|
||||
options={serverList}
|
||||
onValueChange={e => {
|
||||
const arr = e.map(Number);
|
||||
field.onChange(arr);
|
||||
onValueChange={(e) => {
|
||||
const arr = e.map(Number)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
defaultValue={field.value?.map(String)}
|
||||
/>
|
||||
@@ -253,7 +256,9 @@ export const CronCard: React.FC<CronCardProps> = ({ data, mutate }) => {
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
<Button type="submit" className="my-2">
|
||||
{t("Confirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { createDDNSProfile, updateDDNSProfile } from "@/api/ddns"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
@@ -9,14 +11,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -25,28 +19,34 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { ModelDDNSProfile } from "@/types"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { conv } from "@/lib/utils"
|
||||
import { useState } from "react"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { asOptionalField } from "@/lib/utils"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { ddnsTypes, ddnsRequestTypes } from "@/types"
|
||||
import { createDDNSProfile, updateDDNSProfile } from "@/api/ddns"
|
||||
import { conv } from "@/lib/utils"
|
||||
import { asOptionalField } from "@/lib/utils"
|
||||
import { ModelDDNSProfile } from "@/types"
|
||||
import { ddnsRequestTypes, ddnsTypes } from "@/types"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { z } from "zod"
|
||||
|
||||
import { Textarea } from "./ui/textarea"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface DDNSCardProps {
|
||||
data?: ModelDDNSProfile;
|
||||
providers: string[];
|
||||
mutate: KeyedMutator<ModelDDNSProfile[]>;
|
||||
data?: ModelDDNSProfile
|
||||
providers: string[]
|
||||
mutate: KeyedMutator<ModelDDNSProfile[]>
|
||||
}
|
||||
|
||||
const ddnsFormSchema = z.object({
|
||||
@@ -63,47 +63,44 @@ const ddnsFormSchema = z.object({
|
||||
webhook_request_type: asOptionalField(z.coerce.number().int().min(1).max(255)),
|
||||
webhook_request_body: asOptionalField(z.string()),
|
||||
webhook_headers: asOptionalField(z.string()),
|
||||
});
|
||||
})
|
||||
|
||||
export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<z.infer<typeof ddnsFormSchema>>({
|
||||
resolver: zodResolver(ddnsFormSchema),
|
||||
defaultValues: data ? data : {
|
||||
max_retries: 3,
|
||||
name: "",
|
||||
provider: "dummy",
|
||||
domains: [],
|
||||
},
|
||||
defaultValues: data
|
||||
? data
|
||||
: {
|
||||
max_retries: 3,
|
||||
name: "",
|
||||
provider: "dummy",
|
||||
domains: [],
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof ddnsFormSchema>) => {
|
||||
data?.id ? await updateDDNSProfile(data.id, values) : await createDDNSProfile(values);
|
||||
setOpen(false);
|
||||
await mutate();
|
||||
form.reset();
|
||||
data?.id ? await updateDDNSProfile(data.id, values) : await createDDNSProfile(values)
|
||||
setOpen(false)
|
||||
await mutate()
|
||||
form.reset()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{data
|
||||
?
|
||||
<IconButton variant="outline" icon="edit" />
|
||||
:
|
||||
<IconButton icon="plus" />
|
||||
}
|
||||
{data ? <IconButton variant="outline" icon="edit" /> : <IconButton icon="plus" />}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{data?t("EditDDNS"):t("CreateDDNS")}</DialogTitle>
|
||||
<DialogTitle>{data ? t("EditDDNS") : t("CreateDDNS")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -115,10 +112,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
<FormItem>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My DDNS Profile"
|
||||
{...field}
|
||||
/>
|
||||
<Input placeholder="My DDNS Profile" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -130,7 +124,10 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Provider")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select service type" />
|
||||
@@ -138,7 +135,9 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{providers.map((v, i) => (
|
||||
<SelectItem key={i} value={v}>{v}</SelectItem>
|
||||
<SelectItem key={i} value={v}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -151,15 +150,17 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
name="domains"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Domains") + t("SeparateWithComma")}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("Domains") + t("SeparateWithComma")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="www.example.com"
|
||||
{...field}
|
||||
value={conv.arrToStr(field.value ?? [])}
|
||||
onChange={e => {
|
||||
const arr = conv.strToArr(e.target.value);
|
||||
field.onChange(arr);
|
||||
onChange={(e) => {
|
||||
const arr = conv.strToArr(e.target.value)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -174,10 +175,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
<FormItem>
|
||||
<FormLabel>{t("Credential")} 1</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Token ID"
|
||||
{...field}
|
||||
/>
|
||||
<Input placeholder="Token ID" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -190,10 +188,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
<FormItem>
|
||||
<FormLabel>{t("Credential")} 2</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Token Secret"
|
||||
{...field}
|
||||
/>
|
||||
<Input placeholder="Token Secret" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -206,11 +201,7 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
<FormItem>
|
||||
<FormLabel>{t("MaximumRetryAttempts")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="3"
|
||||
{...field}
|
||||
/>
|
||||
<Input type="number" placeholder="3" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -238,7 +229,10 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Webhook {t("RequestMethod")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Webhook Request Method" />
|
||||
@@ -246,7 +240,9 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.entries(ddnsTypes).map(([k, v]) => (
|
||||
<SelectItem key={k} value={k}>{v}</SelectItem>
|
||||
<SelectItem key={k} value={k}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -260,16 +256,23 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Webhook {t("RequestType")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Webhook Request Type" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.entries(ddnsRequestTypes).map(([k, v]) => (
|
||||
<SelectItem key={k} value={k}>{v}</SelectItem>
|
||||
))}
|
||||
{Object.entries(ddnsRequestTypes).map(
|
||||
([k, v]) => (
|
||||
<SelectItem key={k} value={k}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
@@ -321,7 +324,9 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">{t("Enable")} IPv4</Label>
|
||||
<Label className="text-sm">
|
||||
{t("Enable")} IPv4
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -339,7 +344,9 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">{t("Enable")} IPv6</Label>
|
||||
<Label className="text-sm">
|
||||
{t("Enable")} IPv6
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -352,7 +359,9 @@ export const DDNSCard: React.FC<DDNSCardProps> = ({ data, providers, mutate }) =
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
<Button type="submit" className="my-2">
|
||||
{t("Confirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,42 +1,15 @@
|
||||
import { useEffect, useState, useRef, HTMLAttributes } from "react"
|
||||
import {
|
||||
Sheet,
|
||||
SheetTrigger,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
} from "./xui/overlayless-sheet"
|
||||
import { IconButton } from "./xui/icon-button"
|
||||
import { createFM } from "@/api/fm"
|
||||
import { ModelCreateFMResponse, FMEntry, FMOpcode, FMIdentifier, FMWorkerData, FMWorkerOpcode } from "@/types"
|
||||
import { toast } from "sonner"
|
||||
import { ColumnDef } from "@tanstack/react-table"
|
||||
import { Folder, File } from "lucide-react"
|
||||
import { copyToClipboard, fm, formatPath, fmWorker as worker } from "@/lib/utils"
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialogContent,
|
||||
AlertDialogDescription,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogFooter,
|
||||
AlertDialogCancel,
|
||||
AlertDialogAction,
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { Row, flexRender } from "@tanstack/react-table"
|
||||
import { TableRow, TableCell } from "./ui/table"
|
||||
import { DataTable } from "./xui/virtulized-data-table"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Filepath } from "./xui/filepath"
|
||||
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
||||
import {
|
||||
Drawer,
|
||||
DrawerContent,
|
||||
@@ -44,49 +17,80 @@ import {
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/components/ui/drawer"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
||||
import { copyToClipboard, fm, formatPath, fmWorker as worker } from "@/lib/utils"
|
||||
import {
|
||||
FMEntry,
|
||||
FMIdentifier,
|
||||
FMOpcode,
|
||||
FMWorkerData,
|
||||
FMWorkerOpcode,
|
||||
ModelCreateFMResponse,
|
||||
} from "@/types"
|
||||
import { ColumnDef } from "@tanstack/react-table"
|
||||
import { Row, flexRender } from "@tanstack/react-table"
|
||||
import { File, Folder } from "lucide-react"
|
||||
import { HTMLAttributes, useEffect, useRef, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { TableCell, TableRow } from "./ui/table"
|
||||
import { Filepath } from "./xui/filepath"
|
||||
import { IconButton } from "./xui/icon-button"
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetDescription,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
SheetTrigger,
|
||||
} from "./xui/overlayless-sheet"
|
||||
import { DataTable } from "./xui/virtulized-data-table"
|
||||
|
||||
interface FMProps {
|
||||
wsUrl: string;
|
||||
wsUrl: string
|
||||
}
|
||||
|
||||
const arraysEqual = (a: Uint8Array, b: Uint8Array) => {
|
||||
if (a.length !== b.length) return false;
|
||||
if (a.length !== b.length) return false
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
if (a[i] !== b[i]) return false
|
||||
}
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
|
||||
const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
const fmRef = useRef<HTMLDivElement>(null);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const { t } = useTranslation()
|
||||
const fmRef = useRef<HTMLDivElement>(null)
|
||||
const wsRef = useRef<WebSocket | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
wsRef.current?.close();
|
||||
};
|
||||
}, []);
|
||||
wsRef.current?.close()
|
||||
}
|
||||
}, [])
|
||||
|
||||
const [dOpen, setdOpen] = useState(false);
|
||||
const [uOpen, setuOpen] = useState(false);
|
||||
const [dOpen, setdOpen] = useState(false)
|
||||
const [uOpen, setuOpen] = useState(false)
|
||||
|
||||
const columns: ColumnDef<FMEntry>[] = [
|
||||
{
|
||||
id: "type",
|
||||
header: () => <span>{t("Type")}</span>,
|
||||
accessorFn: row => row.type,
|
||||
cell: ({ row }) => (
|
||||
row.original.type == 0 ? <File size={24} /> : <Folder size={24} />
|
||||
),
|
||||
accessorFn: (row) => row.type,
|
||||
cell: ({ row }) => (row.original.type == 0 ? <File size={24} /> : <Folder size={24} />),
|
||||
},
|
||||
{
|
||||
header: () => <span>{t("Name")}</span>,
|
||||
id: "name",
|
||||
accessorFn: row => row.name,
|
||||
accessorFn: (row) => row.name,
|
||||
cell: ({ row }) => (
|
||||
<div className="max-w-48 text-sm whitespace-normal break-words">
|
||||
{row.original.name}
|
||||
@@ -99,24 +103,26 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
id: "download",
|
||||
cell: ({ row }) => {
|
||||
return (
|
||||
<IconButton variant="ghost" icon="download" onClick={
|
||||
() => {
|
||||
if (!dOpen) setdOpen(true);
|
||||
downloadFile(row.original.name);
|
||||
}
|
||||
} />
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
icon="download"
|
||||
onClick={() => {
|
||||
if (!dOpen) setdOpen(true)
|
||||
downloadFile(row.original.name)
|
||||
}}
|
||||
/>
|
||||
)
|
||||
},
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
const tableRowComponent = (rows: Row<FMEntry>[]) =>
|
||||
function getTableRow(props: HTMLAttributes<HTMLTableRowElement>) {
|
||||
// @ts-expect-error data-index is a valid attribute
|
||||
const index = props["data-index"];
|
||||
const row = rows[index];
|
||||
const index = props["data-index"]
|
||||
const row = rows[index]
|
||||
|
||||
if (!row) return null;
|
||||
if (!row) return null
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
@@ -124,7 +130,7 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
data-state={row.getIsSelected() && "selected"}
|
||||
onClick={() => {
|
||||
if (row.original.type === 1) {
|
||||
setPath(`${currentPath}/${row.original.name}`);
|
||||
setPath(`${currentPath}/${row.original.name}`)
|
||||
}
|
||||
}}
|
||||
className={row.original.type === 1 ? "cursor-pointer" : "cursor-default"}
|
||||
@@ -136,155 +142,163 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
const [fmEntires, setFMEntries] = useState<FMEntry[]>([]);
|
||||
const [fmEntires, setFMEntries] = useState<FMEntry[]>([])
|
||||
|
||||
const firstChunk = useRef(true);
|
||||
const handleReady = useRef(false);
|
||||
const currentBasename = useRef('temp');
|
||||
const firstChunk = useRef(true)
|
||||
const handleReady = useRef(false)
|
||||
const currentBasename = useRef("temp")
|
||||
|
||||
const waitForHandleReady = async () => {
|
||||
while (!handleReady.current) {
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
await new Promise((resolve) => setTimeout(resolve, 10))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
worker.onmessage = async (event: MessageEvent<FMWorkerData>) => {
|
||||
switch (event.data.type) {
|
||||
case FMWorkerOpcode.Error: {
|
||||
console.error('Error from worker', event.data.error);
|
||||
break;
|
||||
console.error("Error from worker", event.data.error)
|
||||
break
|
||||
}
|
||||
case FMWorkerOpcode.Progress: {
|
||||
handleReady.current = true;
|
||||
break;
|
||||
handleReady.current = true
|
||||
break
|
||||
}
|
||||
case FMWorkerOpcode.Result: {
|
||||
handleReady.current = false;
|
||||
handleReady.current = false
|
||||
|
||||
if (event.data.blob && event.data.fileName) {
|
||||
const url = URL.createObjectURL(event.data.blob);
|
||||
const anchor = document.createElement('a');
|
||||
anchor.href = url;
|
||||
anchor.download = event.data.fileName;
|
||||
anchor.click();
|
||||
URL.revokeObjectURL(url);
|
||||
const url = URL.createObjectURL(event.data.blob)
|
||||
const anchor = document.createElement("a")
|
||||
anchor.href = url
|
||||
anchor.download = event.data.fileName
|
||||
anchor.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
firstChunk.current = true;
|
||||
if (dOpen) setdOpen(false);
|
||||
break;
|
||||
firstChunk.current = true
|
||||
if (dOpen) setdOpen(false)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const [currentPath, setPath] = useState('');
|
||||
const [currentPath, setPath] = useState("")
|
||||
useEffect(() => {
|
||||
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
|
||||
listFile();
|
||||
listFile()
|
||||
}
|
||||
}, [wsRef.current, currentPath])
|
||||
|
||||
useEffect(() => {
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
ws.binaryType = 'arraybuffer';
|
||||
const ws = new WebSocket(wsUrl)
|
||||
wsRef.current = ws
|
||||
ws.binaryType = "arraybuffer"
|
||||
ws.onopen = () => {
|
||||
listFile();
|
||||
listFile()
|
||||
}
|
||||
ws.onclose = (e) => {
|
||||
console.log('WebSocket connection closed:', e);
|
||||
console.log("WebSocket connection closed:", e)
|
||||
}
|
||||
ws.onerror = (e) => {
|
||||
console.error(e);
|
||||
console.error(e)
|
||||
toast("Websocket" + " " + t("Error"), {
|
||||
description: t("Results.UnExpectedError"),
|
||||
})
|
||||
}
|
||||
ws.onmessage = async (e) => {
|
||||
try {
|
||||
const buf: ArrayBufferLike = e.data;
|
||||
const buf: ArrayBufferLike = e.data
|
||||
|
||||
if (firstChunk.current) {
|
||||
const identifier = new Uint8Array(buf, 0, 4);
|
||||
const identifier = new Uint8Array(buf, 0, 4)
|
||||
if (arraysEqual(identifier, FMIdentifier.file)) {
|
||||
worker.postMessage({ operation: 1, arrayBuffer: buf, fileName: currentBasename.current });
|
||||
firstChunk.current = false;
|
||||
worker.postMessage({
|
||||
operation: 1,
|
||||
arrayBuffer: buf,
|
||||
fileName: currentBasename.current,
|
||||
})
|
||||
firstChunk.current = false
|
||||
} else if (arraysEqual(identifier, FMIdentifier.fileName)) {
|
||||
const { path, fmList } = await fm.parseFMList(buf);
|
||||
setPath(path);
|
||||
setFMEntries(fmList);
|
||||
const { path, fmList } = await fm.parseFMList(buf)
|
||||
setPath(path)
|
||||
setFMEntries(fmList)
|
||||
} else if (arraysEqual(identifier, FMIdentifier.error)) {
|
||||
const errBytes = buf.slice(4);
|
||||
const errMsg = new TextDecoder('utf-8').decode(errBytes);
|
||||
throw new Error(errMsg);
|
||||
const errBytes = buf.slice(4)
|
||||
const errMsg = new TextDecoder("utf-8").decode(errBytes)
|
||||
throw new Error(errMsg)
|
||||
} else if (arraysEqual(identifier, FMIdentifier.complete)) {
|
||||
// Upload completed
|
||||
if (uOpen) setuOpen(false);
|
||||
listFile();
|
||||
if (uOpen) setuOpen(false)
|
||||
listFile()
|
||||
} else {
|
||||
throw new Error(t("Results.UnknownIdentifier"));
|
||||
throw new Error(t("Results.UnknownIdentifier"))
|
||||
}
|
||||
} else {
|
||||
await waitForHandleReady();
|
||||
worker.postMessage({ operation: 2, arrayBuffer: buf, fileName: currentBasename.current });
|
||||
await waitForHandleReady()
|
||||
worker.postMessage({
|
||||
operation: 2,
|
||||
arrayBuffer: buf,
|
||||
fileName: currentBasename.current,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing received data:', error);
|
||||
console.error("Error processing received data:", error)
|
||||
toast("FM" + " " + t("Error"), {
|
||||
description: t("Results.UnExpectedError"),
|
||||
})
|
||||
if (dOpen) setdOpen(false);
|
||||
if (uOpen) setuOpen(false);
|
||||
if (dOpen) setdOpen(false)
|
||||
if (uOpen) setuOpen(false)
|
||||
}
|
||||
}
|
||||
}, [wsUrl])
|
||||
|
||||
let listFile = () => {
|
||||
const prefix = new Int8Array([FMOpcode.List]);
|
||||
const pathMsg = new TextEncoder().encode(currentPath);
|
||||
const listFile = () => {
|
||||
const prefix = new Int8Array([FMOpcode.List])
|
||||
const pathMsg = new TextEncoder().encode(currentPath)
|
||||
|
||||
const msg = new Int8Array(prefix.length + pathMsg.length);
|
||||
msg.set(prefix);
|
||||
msg.set(pathMsg, prefix.length);
|
||||
const msg = new Int8Array(prefix.length + pathMsg.length)
|
||||
msg.set(prefix)
|
||||
msg.set(pathMsg, prefix.length)
|
||||
|
||||
wsRef.current?.send(msg);
|
||||
wsRef.current?.send(msg)
|
||||
}
|
||||
|
||||
const downloadFile = (basename: string) => {
|
||||
currentBasename.current = basename;
|
||||
const prefix = new Int8Array([FMOpcode.Download]);
|
||||
const filePathMessage = new TextEncoder().encode(`${currentPath}/${basename}`);
|
||||
currentBasename.current = basename
|
||||
const prefix = new Int8Array([FMOpcode.Download])
|
||||
const filePathMessage = new TextEncoder().encode(`${currentPath}/${basename}`)
|
||||
|
||||
const msg = new Int8Array(prefix.length + filePathMessage.length);
|
||||
msg.set(prefix);
|
||||
msg.set(filePathMessage, prefix.length);
|
||||
const msg = new Int8Array(prefix.length + filePathMessage.length)
|
||||
msg.set(prefix)
|
||||
msg.set(filePathMessage, prefix.length)
|
||||
|
||||
wsRef.current?.send(msg);
|
||||
wsRef.current?.send(msg)
|
||||
}
|
||||
|
||||
const uploadFile = async (file: File) => {
|
||||
const chunkSize = 1048576; // 1MB chunk
|
||||
let offset = 0;
|
||||
const chunkSize = 1048576 // 1MB chunk
|
||||
let offset = 0
|
||||
|
||||
// Send header
|
||||
const header = fm.buildUploadHeader({ path: currentPath, file: file });
|
||||
wsRef.current?.send(header);
|
||||
const header = fm.buildUploadHeader({ path: currentPath, file: file })
|
||||
wsRef.current?.send(header)
|
||||
|
||||
// Send data chunks
|
||||
while (offset < file.size) {
|
||||
const chunk = file.slice(offset, offset + chunkSize);
|
||||
const arrayBuffer = await fm.readFileAsArrayBuffer(chunk);
|
||||
if (arrayBuffer) wsRef.current?.send(arrayBuffer);
|
||||
offset += chunkSize;
|
||||
const chunk = file.slice(offset, offset + chunkSize)
|
||||
const arrayBuffer = await fm.readFileAsArrayBuffer(chunk)
|
||||
if (arrayBuffer) wsRef.current?.send(arrayBuffer)
|
||||
offset += chunkSize
|
||||
}
|
||||
}
|
||||
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const [gotoPath, setGotoPath] = useState('');
|
||||
const [gotoPath, setGotoPath] = useState("")
|
||||
return (
|
||||
<div ref={fmRef} {...props}>
|
||||
<div className="flex justify-center items-center gap-4">
|
||||
@@ -294,45 +308,72 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
<IconButton variant="ghost" icon="menu" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={listFile}>{t('Refresh')}</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={
|
||||
async () => {
|
||||
await copyToClipboard(formatPath(currentPath));
|
||||
}
|
||||
}>{t("CopyPath")}</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={listFile}>{t("Refresh")}</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={async () => {
|
||||
try {
|
||||
await copyToClipboard(formatPath(currentPath))
|
||||
} catch (error: any) {
|
||||
toast("FM" + " " + t("Error"), {
|
||||
description: error.message,
|
||||
})
|
||||
console.log("copy error: ", error)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{t("CopyPath")}
|
||||
</DropdownMenuItem>
|
||||
<AlertDialogTrigger asChild>
|
||||
<DropdownMenuItem>{t('Goto')}</DropdownMenuItem>
|
||||
<DropdownMenuItem>{t("Goto")}</DropdownMenuItem>
|
||||
</AlertDialogTrigger>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{t('Goto')}</AlertDialogTitle>
|
||||
<AlertDialogTitle>{t("Goto")}</AlertDialogTitle>
|
||||
<AlertDialogDescription />
|
||||
</AlertDialogHeader>
|
||||
<Input className="mb-1" placeholder="Path" value={gotoPath} onChange={(e) => { setGotoPath(e.target.value) }} />
|
||||
<Input
|
||||
className="mb-1"
|
||||
placeholder="Path"
|
||||
value={gotoPath}
|
||||
onChange={(e) => {
|
||||
setGotoPath(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("Close")}</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={() => { setPath(gotoPath) }}>{t("Confirm")}</AlertDialogAction>
|
||||
<AlertDialogAction
|
||||
onClick={() => {
|
||||
setPath(gotoPath)
|
||||
}}
|
||||
>
|
||||
{t("Confirm")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<h1 className="text-base">{t("FileManager")}</h1>
|
||||
<div className="ml-auto">
|
||||
<input ref={fileInputRef} type="file" className="hidden" onChange={
|
||||
async (e) => {
|
||||
const files = e.target.files;
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
className="hidden"
|
||||
onChange={async (e) => {
|
||||
const files = e.target.files
|
||||
if (files && files.length > 0) {
|
||||
if (!uOpen) setuOpen(true);
|
||||
await uploadFile(files[0]);
|
||||
if (!uOpen) setuOpen(true)
|
||||
await uploadFile(files[0])
|
||||
}
|
||||
}
|
||||
} />
|
||||
<IconButton icon="upload" variant="ghost" onClick={
|
||||
() => {
|
||||
if (fileInputRef.current) fileInputRef.current.click();
|
||||
}
|
||||
} />
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
icon="upload"
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
if (fileInputRef.current) fileInputRef.current.click()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Filepath path={currentPath} setPath={setPath} />
|
||||
@@ -354,83 +395,83 @@ const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({ wsUrl,
|
||||
</AlertDialog>
|
||||
<DataTable columns={columns} data={fmEntires} rowComponent={tableRowComponent} />
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export const FMCard = ({ id }: { id?: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const [open, setOpen] = useState(false);
|
||||
const [fm, setFM] = useState<ModelCreateFMResponse | null>(null);
|
||||
const [init, setInit] = useState(false);
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [fm, setFM] = useState<ModelCreateFMResponse | null>(null)
|
||||
const [init, setInit] = useState(false)
|
||||
|
||||
const isDesktop = useMediaQuery("(min-width: 640px)");
|
||||
const isDesktop = useMediaQuery("(min-width: 640px)")
|
||||
|
||||
const fetchFM = async () => {
|
||||
if (id) {
|
||||
try {
|
||||
setInit(false);
|
||||
const createdFM = await createFM(id);
|
||||
setFM(createdFM);
|
||||
setInit(false)
|
||||
const createdFM = await createFM(id)
|
||||
setFM(createdFM)
|
||||
} catch (e) {
|
||||
toast(t("Error"), {
|
||||
description: t("Results.UnExpectedError"),
|
||||
})
|
||||
console.error("fetch error", e);
|
||||
return;
|
||||
console.error("fetch error", e)
|
||||
return
|
||||
}
|
||||
setInit(true);
|
||||
setInit(true)
|
||||
}
|
||||
}
|
||||
|
||||
return (isDesktop ?
|
||||
(
|
||||
<Sheet
|
||||
modal={false}
|
||||
open={open}
|
||||
onOpenChange={(isOpen) => { if (isOpen) setOpen(true); }}
|
||||
>
|
||||
<SheetTrigger asChild>
|
||||
<IconButton icon="folder-closed" onClick={fetchFM} />
|
||||
</SheetTrigger>
|
||||
<SheetContent
|
||||
setOpen={setOpen}
|
||||
className="min-w-[35%]"
|
||||
>
|
||||
<div className="overflow-auto">
|
||||
<SheetTitle />
|
||||
<SheetHeader className="pb-2">
|
||||
<SheetDescription />
|
||||
</SheetHeader>
|
||||
{fm?.session_id && init
|
||||
?
|
||||
<FMComponent className="p-1 space-y-5" wsUrl={`/api/v1/ws/file/${fm.session_id}`} />
|
||||
:
|
||||
<p>{t("Results.TheServerDoesNotOnline")}</p>
|
||||
}
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
)
|
||||
: (
|
||||
<Drawer>
|
||||
<DrawerTrigger asChild>
|
||||
<IconButton icon="folder-closed" onClick={fetchFM} />
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="min-h-[60%] p-4">
|
||||
<div className="overflow-auto">
|
||||
<DrawerTitle />
|
||||
<DrawerHeader className="pb-2">
|
||||
<SheetDescription />
|
||||
</DrawerHeader>
|
||||
{fm?.session_id && init
|
||||
?
|
||||
<FMComponent className="p-1 space-y-5" wsUrl={`/api/v1/ws/file/${fm.session_id}`} />
|
||||
:
|
||||
<p>{t("Results.TheServerDoesNotOnline")}</p>
|
||||
}
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
return isDesktop ? (
|
||||
<Sheet
|
||||
modal={false}
|
||||
open={open}
|
||||
onOpenChange={(isOpen) => {
|
||||
if (isOpen) setOpen(true)
|
||||
}}
|
||||
>
|
||||
<SheetTrigger asChild>
|
||||
<IconButton icon="folder-closed" onClick={fetchFM} />
|
||||
</SheetTrigger>
|
||||
<SheetContent setOpen={setOpen} className="min-w-[35%]">
|
||||
<div className="overflow-auto">
|
||||
<SheetTitle />
|
||||
<SheetHeader className="pb-2">
|
||||
<SheetDescription />
|
||||
</SheetHeader>
|
||||
{fm?.session_id && init ? (
|
||||
<FMComponent
|
||||
className="p-1 space-y-5"
|
||||
wsUrl={`/api/v1/ws/file/${fm.session_id}`}
|
||||
/>
|
||||
) : (
|
||||
<p>{t("Results.TheServerDoesNotOnline")}</p>
|
||||
)}
|
||||
</div>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
) : (
|
||||
<Drawer>
|
||||
<DrawerTrigger asChild>
|
||||
<IconButton icon="folder-closed" onClick={fetchFM} />
|
||||
</DrawerTrigger>
|
||||
<DrawerContent className="min-h-[60%] p-4">
|
||||
<div className="overflow-auto">
|
||||
<DrawerTitle />
|
||||
<DrawerHeader className="pb-2">
|
||||
<SheetDescription />
|
||||
</DrawerHeader>
|
||||
{fm?.session_id && init ? (
|
||||
<FMComponent
|
||||
className="p-1 space-y-5"
|
||||
wsUrl={`/api/v1/ws/file/${fm.session_id}`}
|
||||
/>
|
||||
) : (
|
||||
<p>{t("Results.TheServerDoesNotOnline")}</p>
|
||||
)}
|
||||
</div>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import {
|
||||
Tabs,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/components/ui/tabs"
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, useLocation } from "react-router-dom"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const GroupTab = ({ className }: { className?: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const { t } = useTranslation()
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<Tabs defaultValue={location.pathname} className={className}>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { buttonVariants } from "@/components/ui/button";
|
||||
import { IconButton } from "@/components/xui/icon-button";
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
@@ -11,34 +9,48 @@ import {
|
||||
AlertDialogTitle,
|
||||
AlertDialogTrigger,
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import { KeyedMutator } from "swr";
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { KeyedMutator } from "swr"
|
||||
|
||||
interface ButtonGroupProps<E, U> {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
delete: { fn: (id: E[]) => Promise<void>, id: E[], mutate: KeyedMutator<U> };
|
||||
className?: string
|
||||
children?: React.ReactNode
|
||||
delete: { fn: (id: E[]) => Promise<void>; id: E[]; mutate: KeyedMutator<U> }
|
||||
}
|
||||
|
||||
export function HeaderButtonGroup<E, U>({ className, children, delete: { fn, id, mutate } }: ButtonGroupProps<E, U>) {
|
||||
export function HeaderButtonGroup<E, U>({
|
||||
className,
|
||||
children,
|
||||
delete: { fn, id, mutate },
|
||||
}: ButtonGroupProps<E, U>) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const handleDelete = async () => {
|
||||
await fn(id);
|
||||
await mutate();
|
||||
try {
|
||||
await fn(id)
|
||||
} catch (error: any) {
|
||||
toast(t("Error"), {
|
||||
description: error.message,
|
||||
})
|
||||
}
|
||||
await mutate()
|
||||
}
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{id.length < 1 ? (
|
||||
<>
|
||||
<IconButton variant="destructive" icon="trash" onClick={() => {
|
||||
toast(t("Error"), {
|
||||
description: t("Results.NoRowsAreSelected")
|
||||
});
|
||||
}} />
|
||||
<IconButton
|
||||
variant="destructive"
|
||||
icon="trash"
|
||||
onClick={() => {
|
||||
toast(t("Error"), {
|
||||
description: t("Results.NoRowsAreSelected"),
|
||||
})
|
||||
}}
|
||||
/>
|
||||
{children}
|
||||
</>
|
||||
) : (
|
||||
@@ -56,7 +68,12 @@ export function HeaderButtonGroup<E, U>({ className, children, delete: { fn, id,
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>{t("Close")}</AlertDialogCancel>
|
||||
<AlertDialogAction className={buttonVariants({ variant: "destructive" })} onClick={handleDelete}>{t("Confirm")}</AlertDialogAction>
|
||||
<AlertDialogAction
|
||||
className={buttonVariants({ variant: "destructive" })}
|
||||
onClick={handleDelete}
|
||||
>
|
||||
{t("Confirm")}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
@@ -1,20 +1,4 @@
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuList,
|
||||
navigationMenuTriggerStyle,
|
||||
} from "@/components/ui/navigation-menu"
|
||||
import { ModeToggle } from "@/components/mode-toggle";
|
||||
import { Card } from "./ui/card";
|
||||
import { useMainStore } from "@/hooks/useMainStore";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
|
||||
import { NzNavigationMenuLink } from "./xui/navigation-menu";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuShortcut, DropdownMenuTrigger } from "./ui/dropdown-menu";
|
||||
import { LogOut, Settings, User2 } from "lucide-react";
|
||||
import { useAuth } from "@/hooks/useAuth";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { useMediaQuery } from "@/hooks/useMediaQuery";
|
||||
import { ModeToggle } from "@/components/mode-toggle"
|
||||
import {
|
||||
Drawer,
|
||||
DrawerClose,
|
||||
@@ -25,13 +9,38 @@ import {
|
||||
DrawerTitle,
|
||||
DrawerTrigger,
|
||||
} from "@/components/ui/drawer"
|
||||
import { Button } from "./ui/button";
|
||||
import { IconButton } from "./xui/icon-button";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
NavigationMenu,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuLink,
|
||||
navigationMenuTriggerStyle,
|
||||
} from "@/components/ui/navigation-menu"
|
||||
import { useAuth } from "@/hooks/useAuth"
|
||||
import { useMainStore } from "@/hooks/useMainStore"
|
||||
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
||||
import i18next from "i18next"
|
||||
import { LogOut, Settings, User2 } from "lucide-react"
|
||||
import { DateTime } from "luxon"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar"
|
||||
import { Button } from "./ui/button"
|
||||
import { Card } from "./ui/card"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuTrigger,
|
||||
} from "./ui/dropdown-menu"
|
||||
import { IconButton } from "./xui/icon-button"
|
||||
import { NzNavigationMenuLink } from "./xui/navigation-menu"
|
||||
|
||||
import i18next from "i18next";
|
||||
const pages = [
|
||||
{ href: "/dashboard", label: i18next.t("Server") },
|
||||
{ href: "/dashboard/service", label: i18next.t("Service") },
|
||||
@@ -43,209 +52,329 @@ const pages = [
|
||||
]
|
||||
|
||||
export default function Header() {
|
||||
const { t } = useTranslation();
|
||||
const { logout } = useAuth();
|
||||
const profile = useMainStore(store => store.profile);
|
||||
const { t } = useTranslation()
|
||||
const { logout } = useAuth()
|
||||
const profile = useMainStore((store) => store.profile)
|
||||
|
||||
const location = useLocation();
|
||||
const location = useLocation()
|
||||
const isDesktop = useMediaQuery("(min-width: 890px)")
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false)
|
||||
|
||||
const navigate = useNavigate();
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
isDesktop ? (
|
||||
<header className="h-16 flex items-center border-b-2 px-4 overflow-x-auto">
|
||||
<NavigationMenu className="sm:max-w-full">
|
||||
<NavigationMenuList>
|
||||
<Card className="mr-1">
|
||||
<NavigationMenuLink asChild className={navigationMenuTriggerStyle() + ' !text-foreground'}>
|
||||
<Link to={profile ? "/dashboard" : '#'}><img className="h-7 mr-1" src='/dashboard/logo.svg' /> {t("nezha")}</Link>
|
||||
</NavigationMenuLink>
|
||||
</Card>
|
||||
return isDesktop ? (
|
||||
<header className="flex pt-8 px-4 overflow-x-auto dark:bg-black/40 bg-muted border-b-[1px]">
|
||||
<NavigationMenu className="flex flex-col items-start max-w-5xl mx-auto">
|
||||
<section className="w-full flex items-center justify-between">
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<NavigationMenuLink
|
||||
asChild
|
||||
className={navigationMenuTriggerStyle() + " !text-foreground"}
|
||||
>
|
||||
<Link to={profile ? "/dashboard" : "#"}>
|
||||
<img className="h-7 mr-1" src="/dashboard/logo.svg" />
|
||||
{t("nezha")}
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
|
||||
{
|
||||
profile && (
|
||||
<div className="flex items-center gap-1">
|
||||
<ModeToggle />
|
||||
{profile && (
|
||||
<>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard">{t("Server")}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard/service"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard/service">{t("Service")}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard/cron"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard/cron">{t('Task')}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard/notification" || location.pathname === "/dashboard/alert-rule"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard/notification">{t('Notification')}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard/ddns"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard/ddns">{t('DDNS')}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard/nat"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard/nat">{t('NATT')}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink asChild active={location.pathname === "/dashboard/server-group" || location.pathname === "/dashboard/notification-group"} className={navigationMenuTriggerStyle()}>
|
||||
<Link to="/dashboard/server-group">{t('Group')}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<DropdownMenu
|
||||
open={dropdownOpen}
|
||||
onOpenChange={setDropdownOpen}
|
||||
>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Avatar className="ml-1 h-8 w-8 cursor-pointer border-foreground border-[1px]">
|
||||
<AvatarImage
|
||||
src={
|
||||
"https://api.dicebear.com/7.x/notionists/svg?seed=" +
|
||||
profile.username
|
||||
}
|
||||
alt={profile.username}
|
||||
/>
|
||||
<AvatarFallback>{profile.username}</AvatarFallback>
|
||||
</Avatar>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-32">
|
||||
<DropdownMenuLabel>
|
||||
{profile.username}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setDropdownOpen(false)
|
||||
navigate("/dashboard/profile")
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<User2 />
|
||||
{t("Profile")}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setDropdownOpen(false)
|
||||
navigate("/dashboard/settings")
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<Settings />
|
||||
{t("Settings")}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={logout}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<LogOut />
|
||||
{t("Logout")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</NavigationMenuList>
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
<ModeToggle />
|
||||
{
|
||||
profile && <>
|
||||
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Avatar className="ml-1 h-8 w-8 cursor-pointer border-foreground border-[1px]">
|
||||
<AvatarImage src={'https://api.dicebear.com/7.x/notionists/svg?seed=' + profile.username} alt={profile.username} />
|
||||
<AvatarFallback>{profile.username}</AvatarFallback>
|
||||
</Avatar>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-32">
|
||||
<DropdownMenuLabel>{profile.username}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem onClick={() => {
|
||||
setDropdownOpen(false)
|
||||
navigate("/dashboard/profile")
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<User2 />
|
||||
{t('Profile')}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => {
|
||||
setDropdownOpen(false)
|
||||
navigate("/dashboard/settings")
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<Settings />
|
||||
{t('Settings')}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={logout} className="cursor-pointer">
|
||||
<LogOut />
|
||||
{t('Logout')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</>
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</NavigationMenu>
|
||||
</header>
|
||||
)
|
||||
: (
|
||||
<header className="flex border-b-2 px-4 h-16">
|
||||
<div className="flex max-w-max flex-1 items-center justify-center gap-2">
|
||||
{profile &&
|
||||
<Drawer open={open} onOpenChange={setOpen}>
|
||||
<DrawerTrigger aria-label="Toggle Menu" asChild>
|
||||
<IconButton icon="menu" variant="ghost" />
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader className="text-left">
|
||||
<DrawerTitle>{t('NavigateTo')}</DrawerTitle>
|
||||
<DrawerDescription>{t('SelectAPageToNavigateTo')}</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="grid gap-1 px-4">
|
||||
{pages.slice(0).map((item, index) => (
|
||||
<Link
|
||||
key={index}
|
||||
to={item.href ? item.href : "#"}
|
||||
className="py-1 text-sm"
|
||||
onClick={() => { setOpen(false) }}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<DrawerFooter>
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">{t('Close')}</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
}
|
||||
</div>
|
||||
<Card className="mx-2 my-2 flex justify-center items-center hover:bg-accent transition duration-200">
|
||||
<Link className="inline-flex w-full items-center px-4 py-2" to={profile ? "/dashboard" : '#'}><img className="h-7 mr-1" src='/dashboard/logo.svg' /> NEZHA</Link>
|
||||
</Card>
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
<ModeToggle />
|
||||
{
|
||||
profile && <>
|
||||
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Avatar className="ml-1 h-8 w-8 cursor-pointer border-foreground border-[1px]">
|
||||
<AvatarImage src={'https://api.dicebear.com/7.x/notionists/svg?seed=' + profile.username} alt={profile.username} />
|
||||
<AvatarFallback>{profile.username}</AvatarFallback>
|
||||
</Avatar>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56">
|
||||
<DropdownMenuLabel>{profile.username}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem onClick={() => {
|
||||
setDropdownOpen(false)
|
||||
navigate("/dashboard/profile")
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<User2 />
|
||||
{t('Profile')}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => {
|
||||
setDropdownOpen(false)
|
||||
navigate("/dashboard/settings")
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<Settings />
|
||||
{t('Settings')}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={logout} className="cursor-pointer">
|
||||
<LogOut />
|
||||
{t('Logout')}
|
||||
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
</section>
|
||||
<div className="flex mt-4 ml-4">
|
||||
<Overview />
|
||||
</div>
|
||||
<div className="flex mt-4 list-none">
|
||||
{profile && (
|
||||
<>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink
|
||||
asChild
|
||||
active={location.pathname === "/dashboard"}
|
||||
className={navigationMenuTriggerStyle()}
|
||||
>
|
||||
<Link to="/dashboard">{t("Server")}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink
|
||||
asChild
|
||||
active={location.pathname === "/dashboard/service"}
|
||||
className={navigationMenuTriggerStyle()}
|
||||
>
|
||||
<Link to="/dashboard/service">{t("Service")}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink
|
||||
asChild
|
||||
active={location.pathname === "/dashboard/cron"}
|
||||
className={navigationMenuTriggerStyle()}
|
||||
>
|
||||
<Link to="/dashboard/cron">{t("Task")}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink
|
||||
asChild
|
||||
active={
|
||||
location.pathname === "/dashboard/notification" ||
|
||||
location.pathname === "/dashboard/alert-rule"
|
||||
}
|
||||
className={navigationMenuTriggerStyle()}
|
||||
>
|
||||
<Link to="/dashboard/notification">{t("Notification")}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink
|
||||
asChild
|
||||
active={location.pathname === "/dashboard/ddns"}
|
||||
className={navigationMenuTriggerStyle()}
|
||||
>
|
||||
<Link to="/dashboard/ddns">{t("DDNS")}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink
|
||||
asChild
|
||||
active={location.pathname === "/dashboard/nat"}
|
||||
className={navigationMenuTriggerStyle()}
|
||||
>
|
||||
<Link to="/dashboard/nat">{t("NATT")}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
<NavigationMenuItem>
|
||||
<NzNavigationMenuLink
|
||||
asChild
|
||||
active={
|
||||
location.pathname === "/dashboard/server-group" ||
|
||||
location.pathname === "/dashboard/notification-group"
|
||||
}
|
||||
className={navigationMenuTriggerStyle()}
|
||||
>
|
||||
<Link to="/dashboard/server-group">{t("Group")}</Link>
|
||||
</NzNavigationMenuLink>
|
||||
</NavigationMenuItem>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</NavigationMenu>
|
||||
</header>
|
||||
) : (
|
||||
<header className="flex border-b-2 px-4 h-16">
|
||||
<div className="flex max-w-max flex-1 items-center justify-center gap-2">
|
||||
{profile && (
|
||||
<Drawer open={open} onOpenChange={setOpen}>
|
||||
<DrawerTrigger aria-label="Toggle Menu" asChild>
|
||||
<IconButton icon="menu" variant="ghost" />
|
||||
</DrawerTrigger>
|
||||
<DrawerContent>
|
||||
<DrawerHeader className="text-left">
|
||||
<DrawerTitle>{t("NavigateTo")}</DrawerTitle>
|
||||
<DrawerDescription>
|
||||
{t("SelectAPageToNavigateTo")}
|
||||
</DrawerDescription>
|
||||
</DrawerHeader>
|
||||
<div className="grid gap-1 px-4">
|
||||
{pages.slice(0).map((item, index) => (
|
||||
<Link
|
||||
key={index}
|
||||
to={item.href ? item.href : "#"}
|
||||
className="py-1 text-sm"
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<DrawerFooter>
|
||||
<DrawerClose asChild>
|
||||
<Button variant="outline">{t("Close")}</Button>
|
||||
</DrawerClose>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
)}
|
||||
</div>
|
||||
<Card className="mx-2 my-2 flex justify-center items-center hover:bg-accent transition duration-200">
|
||||
<Link
|
||||
className="inline-flex w-full items-center px-4 py-2"
|
||||
to={profile ? "/dashboard" : "#"}
|
||||
>
|
||||
<img className="h-7 mr-1" src="/dashboard/logo.svg" /> NEZHA
|
||||
</Link>
|
||||
</Card>
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
<ModeToggle />
|
||||
{profile && (
|
||||
<>
|
||||
<DropdownMenu open={dropdownOpen} onOpenChange={setDropdownOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Avatar className="ml-1 h-8 w-8 cursor-pointer border-foreground border-[1px]">
|
||||
<AvatarImage
|
||||
src={
|
||||
"https://api.dicebear.com/7.x/notionists/svg?seed=" +
|
||||
profile.username
|
||||
}
|
||||
alt={profile.username}
|
||||
/>
|
||||
<AvatarFallback>{profile.username}</AvatarFallback>
|
||||
</Avatar>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56">
|
||||
<DropdownMenuLabel>{profile.username}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setDropdownOpen(false)
|
||||
navigate("/dashboard/profile")
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<User2 />
|
||||
{t("Profile")}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setDropdownOpen(false)
|
||||
navigate("/dashboard/settings")
|
||||
}}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<Settings />
|
||||
{t("Settings")}
|
||||
</div>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={logout} className="cursor-pointer">
|
||||
<LogOut />
|
||||
{t("Logout")}
|
||||
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
// https://github.com/streamich/react-use/blob/master/src/useInterval.ts
|
||||
const useInterval = (callback: () => void, delay?: number | null) => {
|
||||
const savedCallback = useRef<() => void>(() => {})
|
||||
useEffect(() => {
|
||||
savedCallback.current = callback
|
||||
})
|
||||
useEffect(() => {
|
||||
if (delay !== null) {
|
||||
const interval = setInterval(() => savedCallback.current(), delay || 0)
|
||||
return () => clearInterval(interval)
|
||||
}
|
||||
return undefined
|
||||
}, [delay])
|
||||
}
|
||||
|
||||
function Overview() {
|
||||
const { t } = useTranslation()
|
||||
const profile = useMainStore((store) => store.profile)
|
||||
const timeOption = DateTime.TIME_SIMPLE
|
||||
timeOption.hour12 = true
|
||||
const [timeString, setTimeString] = useState(
|
||||
DateTime.now().setLocale("en-US").toLocaleString(timeOption),
|
||||
)
|
||||
useInterval(() => {
|
||||
setTimeString(DateTime.now().setLocale("en-US").toLocaleString(timeOption))
|
||||
}, 1000)
|
||||
return (
|
||||
<section className={"flex flex-col"}>
|
||||
{profile && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
<div className="flex gap-1.5 text-sm font-semibold">
|
||||
👋 Hi, {profile?.username}
|
||||
{profile?.login_ip && (
|
||||
<p className="font-medium opacity-45">from {profile?.login_ip}</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!profile && <p className="text-sm font-semibold">{t("LoginFirst")}</p>}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<p className="text-[13px] font-medium opacity-50">{t("CurrentTime")}</p>
|
||||
<p className="opacity-1 text-[13px] font-medium">{timeString}</p>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,46 +1,45 @@
|
||||
import { Button, ButtonProps } from "@/components/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { Button, ButtonProps } from "@/components/ui/button"
|
||||
import { forwardRef, useState } from "react"
|
||||
import useSettings from "@/hooks/useSetting"
|
||||
import { ModelSettingResponse } from "@/types"
|
||||
import { Check, Clipboard } from "lucide-react"
|
||||
import { toast } from "sonner"
|
||||
import { copyToClipboard } from "@/lib/utils"
|
||||
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { ModelSettingResponse } from "@/types"
|
||||
import i18next from "i18next"
|
||||
import { Check, Clipboard } from "lucide-react"
|
||||
import { forwardRef, useState } from "react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
enum OSTypes {
|
||||
Linux = 1,
|
||||
macOS,
|
||||
Windows
|
||||
Windows,
|
||||
}
|
||||
|
||||
export const InstallCommandsMenu = forwardRef<HTMLButtonElement, ButtonProps>((props, ref) => {
|
||||
const [copy, setCopy] = useState(false);
|
||||
const settings = useSettings();
|
||||
const { t } = useTranslation();
|
||||
const [copy, setCopy] = useState(false)
|
||||
const settings = useSettings()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const switchState = async (type: number) => {
|
||||
if (!copy) {
|
||||
try {
|
||||
setCopy(true);
|
||||
if (!settings) throw new Error("Settings is not found.");
|
||||
await copyToClipboard(generateCommand(type, settings) || '');
|
||||
setCopy(true)
|
||||
if (!settings) throw new Error("Settings is not found.")
|
||||
await copyToClipboard(generateCommand(type, settings) || "")
|
||||
} catch (e: Error | any) {
|
||||
console.error(e);
|
||||
console.error(e)
|
||||
toast(t("Error"), {
|
||||
description: e.message,
|
||||
})
|
||||
} finally {
|
||||
setTimeout(() => {
|
||||
setCopy(false);
|
||||
}, 2 * 1000);
|
||||
setCopy(false)
|
||||
}, 2 * 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,32 +53,54 @@ export const InstallCommandsMenu = forwardRef<HTMLButtonElement, ButtonProps>((p
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem className="nezha-copy" onClick={async () => { switchState(OSTypes.Linux) }}>Linux</DropdownMenuItem>
|
||||
<DropdownMenuItem className="nezha-copy" onClick={async () => { switchState(OSTypes.macOS) }}>macOS</DropdownMenuItem>
|
||||
<DropdownMenuItem className="nezha-copy" onClick={async () => { switchState(OSTypes.Windows) }}>Windows</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="nezha-copy"
|
||||
onClick={async () => {
|
||||
switchState(OSTypes.Linux)
|
||||
}}
|
||||
>
|
||||
Linux
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="nezha-copy"
|
||||
onClick={async () => {
|
||||
switchState(OSTypes.macOS)
|
||||
}}
|
||||
>
|
||||
macOS
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="nezha-copy"
|
||||
onClick={async () => {
|
||||
switchState(OSTypes.Windows)
|
||||
}}
|
||||
>
|
||||
Windows
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
)
|
||||
})
|
||||
|
||||
const generateCommand = (type: number, { agent_secret_key, install_host, tls }: ModelSettingResponse) => {
|
||||
|
||||
if (!install_host)
|
||||
throw new Error(i18next.t("Results.InstallHostRequired"));
|
||||
const generateCommand = (
|
||||
type: number,
|
||||
{ agent_secret_key, install_host, tls }: ModelSettingResponse,
|
||||
) => {
|
||||
if (!install_host) throw new Error(i18next.t("Results.InstallHostRequired"))
|
||||
|
||||
const env = `NZ_SERVER=${install_host} NZ_TLS=${tls || false} NZ_CLIENT_SECRET=${agent_secret_key}`;
|
||||
const env_win = `$env:NZ_SERVER=\"${install_host}\";$env:NZ_TLS=\"${tls || false}\";$env:NZ_CLIENT_SECRET=\"${agent_secret_key}\";`;
|
||||
const env = `NZ_SERVER=${install_host} NZ_TLS=${tls || false} NZ_CLIENT_SECRET=${agent_secret_key}`
|
||||
const env_win = `$env:NZ_SERVER=\"${install_host}\";$env:NZ_TLS=\"${tls || false}\";$env:NZ_CLIENT_SECRET=\"${agent_secret_key}\";`
|
||||
|
||||
switch (type) {
|
||||
case OSTypes.Linux:
|
||||
case OSTypes.macOS: {
|
||||
return `curl -L https://raw.githubusercontent.com/nezhahq/scripts/main/agent/install.sh -o agent.sh && chmod +x agent.sh && env ${env} ./agent.sh`
|
||||
}
|
||||
case OSTypes.Windows: {
|
||||
return `${env_win} [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl3 -bor [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12;set-ExecutionPolicy RemoteSigned;Invoke-WebRequest https://raw.githubusercontent.com/nezhahq/scripts/main/agent/install.ps1 -OutFile C:\install.ps1;powershell.exe C:\install.ps1`
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unknown OS: ${type}`);
|
||||
}
|
||||
case OSTypes.Linux:
|
||||
case OSTypes.macOS: {
|
||||
return `curl -L https://raw.githubusercontent.com/nezhahq/scripts/main/agent/install.sh -o agent.sh && chmod +x agent.sh && env ${env} ./agent.sh`
|
||||
}
|
||||
case OSTypes.Windows: {
|
||||
return `${env_win} [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Ssl3 -bor [Net.SecurityProtocolType]::Tls -bor [Net.SecurityProtocolType]::Tls11 -bor [Net.SecurityProtocolType]::Tls12;set-ExecutionPolicy RemoteSigned;Invoke-WebRequest https://raw.githubusercontent.com/nezhahq/scripts/main/agent/install.ps1 -OutFile C:\install.ps1;powershell.exe C:\install.ps1`
|
||||
}
|
||||
default: {
|
||||
throw new Error(`Unknown OS: ${type}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Moon, Sun } from "lucide-react"
|
||||
|
||||
import { Theme, useTheme } from "@/components/theme-provider"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -7,16 +6,15 @@ import {
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { Theme, useTheme } from "@/components/theme-provider"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Moon, Sun } from "lucide-react"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
export function ModeToggle() {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation()
|
||||
const { setTheme } = useTheme()
|
||||
|
||||
const toggleTheme = (theme: Theme) => {
|
||||
setTheme(theme);
|
||||
setTheme(theme)
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createNAT, updateNAT } from "@/api/nat"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -18,21 +18,20 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { ModelNAT } from "@/types"
|
||||
import { useState } from "react"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { createNAT, updateNAT } from "@/api/nat"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ModelNAT } from "@/types"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { z } from "zod"
|
||||
|
||||
interface NATCardProps {
|
||||
data?: ModelNAT;
|
||||
mutate: KeyedMutator<ModelNAT[]>;
|
||||
data?: ModelNAT
|
||||
mutate: KeyedMutator<ModelNAT[]>
|
||||
}
|
||||
|
||||
const natFormSchema = z.object({
|
||||
@@ -40,47 +39,44 @@ const natFormSchema = z.object({
|
||||
server_id: z.coerce.number().int(),
|
||||
host: z.string(),
|
||||
domain: z.string(),
|
||||
});
|
||||
})
|
||||
|
||||
export const NATCard: React.FC<NATCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<z.infer<typeof natFormSchema>>({
|
||||
resolver: zodResolver(natFormSchema),
|
||||
defaultValues: data ? data : {
|
||||
name: "",
|
||||
server_id: 0,
|
||||
host: "",
|
||||
domain: "",
|
||||
},
|
||||
defaultValues: data
|
||||
? data
|
||||
: {
|
||||
name: "",
|
||||
server_id: 0,
|
||||
host: "",
|
||||
domain: "",
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof natFormSchema>) => {
|
||||
data?.id ? await updateNAT(data.id, values) : await createNAT(values);
|
||||
setOpen(false);
|
||||
await mutate();
|
||||
form.reset();
|
||||
data?.id ? await updateNAT(data.id, values) : await createNAT(values)
|
||||
setOpen(false)
|
||||
await mutate()
|
||||
form.reset()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{data
|
||||
?
|
||||
<IconButton variant="outline" icon="edit" />
|
||||
:
|
||||
<IconButton icon="plus" />
|
||||
}
|
||||
{data ? <IconButton variant="outline" icon="edit" /> : <IconButton icon="plus" />}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{data?t("EditNAT"):t("CreateNAT")}</DialogTitle>
|
||||
<DialogTitle>{data ? t("EditNAT") : t("CreateNAT")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -92,10 +88,7 @@ export const NATCard: React.FC<NATCardProps> = ({ data, mutate }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My NAT Profile"
|
||||
{...field}
|
||||
/>
|
||||
<Input placeholder="My NAT Profile" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -108,11 +101,7 @@ export const NATCard: React.FC<NATCardProps> = ({ data, mutate }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("Server")} ID</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="1"
|
||||
{...field}
|
||||
/>
|
||||
<Input type="number" placeholder="1" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -156,7 +145,9 @@ export const NATCard: React.FC<NATCardProps> = ({ data, mutate }) => {
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
<Button type="submit" className="my-2">
|
||||
{t("Confirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,53 +1,69 @@
|
||||
import { ButtonProps } from "@/components/ui/button"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { ButtonProps } from "@/components/ui/button"
|
||||
import { copyToClipboard } from "@/lib/utils"
|
||||
import { forwardRef, useState } from "react"
|
||||
import { IconButton } from "./xui/icon-button"
|
||||
import { toast } from "sonner";
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { copyToClipboard } from "@/lib/utils";
|
||||
import { IconButton } from "./xui/icon-button"
|
||||
|
||||
interface NoteMenuProps extends ButtonProps {
|
||||
note: { private?: string, public?: string };
|
||||
note: { private?: string; public?: string }
|
||||
}
|
||||
|
||||
export const NoteMenu = forwardRef<HTMLButtonElement, NoteMenuProps>((props, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [copy, setCopy] = useState(false);
|
||||
const { t } = useTranslation()
|
||||
const [copy, setCopy] = useState(false)
|
||||
|
||||
const switchState = async (text?: string) => {
|
||||
if (!text) {
|
||||
toast("Warning", {
|
||||
description: "You didn't have any note."
|
||||
description: "You didn't have any note.",
|
||||
})
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
if (!copy) {
|
||||
setCopy(true);
|
||||
await copyToClipboard(text);
|
||||
setCopy(true)
|
||||
await copyToClipboard(text)
|
||||
setTimeout(() => {
|
||||
setCopy(false);
|
||||
}, 2 * 1000);
|
||||
setCopy(false)
|
||||
}, 2 * 1000)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<IconButton {...props} ref={ref} variant="outline" size="icon" icon={
|
||||
copy ? "check" : "clipboard"
|
||||
} />
|
||||
<IconButton
|
||||
{...props}
|
||||
ref={ref}
|
||||
variant="outline"
|
||||
size="icon"
|
||||
icon={copy ? "check" : "clipboard"}
|
||||
/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem onClick={() => { switchState(props.note.private) }}>{t("Private")}</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={() => { switchState(props.note.public) }}>{t("Public")}</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
switchState(props.note.private)
|
||||
}}
|
||||
>
|
||||
{t("Private")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
switchState(props.note.public)
|
||||
}}
|
||||
>
|
||||
{t("Public")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createNotificationGroup, updateNotificationGroup } from "@/api/notification-group"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -18,76 +18,76 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { ModelNotificationGroupResponseItem } from "@/types"
|
||||
import { useState } from "react"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { createNotificationGroup, updateNotificationGroup } from "@/api/notification-group"
|
||||
import { MultiSelect } from "@/components/xui/multi-select"
|
||||
import { useNotification } from "@/hooks/useNotfication"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ModelNotificationGroupResponseItem } from "@/types"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { z } from "zod"
|
||||
|
||||
interface NotificationGroupCardProps {
|
||||
data?: ModelNotificationGroupResponseItem;
|
||||
mutate: KeyedMutator<ModelNotificationGroupResponseItem[]>;
|
||||
data?: ModelNotificationGroupResponseItem
|
||||
mutate: KeyedMutator<ModelNotificationGroupResponseItem[]>
|
||||
}
|
||||
|
||||
const notificationGroupFormSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
notifications: z.array(z.number()),
|
||||
});
|
||||
})
|
||||
|
||||
export const NotificationGroupCard: React.FC<NotificationGroupCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<z.infer<typeof notificationGroupFormSchema>>({
|
||||
resolver: zodResolver(notificationGroupFormSchema),
|
||||
defaultValues: data ? {
|
||||
name: data.group.name,
|
||||
notifications: data.notifications,
|
||||
} : {
|
||||
name: "",
|
||||
notifications: [],
|
||||
},
|
||||
defaultValues: data
|
||||
? {
|
||||
name: data.group.name,
|
||||
notifications: data.notifications,
|
||||
}
|
||||
: {
|
||||
name: "",
|
||||
notifications: [],
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof notificationGroupFormSchema>) => {
|
||||
data?.group.id ? await updateNotificationGroup(data.group.id, values) : await createNotificationGroup(values);
|
||||
setOpen(false);
|
||||
await mutate();
|
||||
form.reset();
|
||||
data?.group.id
|
||||
? await updateNotificationGroup(data.group.id, values)
|
||||
: await createNotificationGroup(values)
|
||||
setOpen(false)
|
||||
await mutate()
|
||||
form.reset()
|
||||
}
|
||||
|
||||
const { notifiers } = useNotification();
|
||||
const notifierList = notifiers?.map(n => ({
|
||||
const { notifiers } = useNotification()
|
||||
const notifierList = notifiers?.map((n) => ({
|
||||
value: `${n.id}`,
|
||||
label: n.name,
|
||||
})) || [{ value: "", label: "" }];
|
||||
})) || [{ value: "", label: "" }]
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{data
|
||||
?
|
||||
<IconButton variant="outline" icon="edit" />
|
||||
:
|
||||
<IconButton icon="plus" />
|
||||
}
|
||||
{data ? <IconButton variant="outline" icon="edit" /> : <IconButton icon="plus" />}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{data ? t("EditNotifierGroup") : t("CreateNotifierGroup")}</DialogTitle>
|
||||
<DialogTitle>
|
||||
{data ? t("EditNotifierGroup") : t("CreateNotifierGroup")}
|
||||
</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -99,10 +99,7 @@ export const NotificationGroupCard: React.FC<NotificationGroupCardProps> = ({ da
|
||||
<FormItem>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Group Name"
|
||||
{...field}
|
||||
/>
|
||||
<Input placeholder="Group Name" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -116,9 +113,9 @@ export const NotificationGroupCard: React.FC<NotificationGroupCardProps> = ({ da
|
||||
<FormLabel>{t("Notification")}</FormLabel>
|
||||
<MultiSelect
|
||||
options={notifierList}
|
||||
onValueChange={e => {
|
||||
const arr = e.map(Number);
|
||||
field.onChange(arr);
|
||||
onValueChange={(e) => {
|
||||
const arr = e.map(Number)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
defaultValue={field.value?.map(String)}
|
||||
/>
|
||||
@@ -132,7 +129,9 @@ export const NotificationGroupCard: React.FC<NotificationGroupCardProps> = ({ da
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
<Button type="submit" className="my-2">
|
||||
{t("Confirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
import {
|
||||
Tabs,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/components/ui/tabs"
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, useLocation } from "react-router-dom"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
|
||||
export const NotificationTab = ({ className }: { className?: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const { t } = useTranslation()
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<Tabs defaultValue={location.pathname} className={className}>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { createNotification, updateNotification } from "@/api/notification"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
@@ -9,14 +11,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -25,26 +19,32 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { ModelNotification } from "@/types"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { useState } from "react"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { asOptionalField } from "@/lib/utils"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { nrequestTypes, nrequestMethods } from "@/types"
|
||||
import { createNotification, updateNotification } from "@/api/notification"
|
||||
import { asOptionalField } from "@/lib/utils"
|
||||
import { ModelNotification } from "@/types"
|
||||
import { nrequestMethods, nrequestTypes } from "@/types"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { z } from "zod"
|
||||
|
||||
import { Textarea } from "./ui/textarea"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
interface NotifierCardProps {
|
||||
data?: ModelNotification;
|
||||
mutate: KeyedMutator<ModelNotification[]>;
|
||||
data?: ModelNotification
|
||||
mutate: KeyedMutator<ModelNotification[]>
|
||||
}
|
||||
|
||||
const notificationFormSchema = z.object({
|
||||
@@ -56,49 +56,48 @@ const notificationFormSchema = z.object({
|
||||
request_body: z.string(),
|
||||
verify_tls: asOptionalField(z.boolean()),
|
||||
skip_check: asOptionalField(z.boolean()),
|
||||
});
|
||||
})
|
||||
|
||||
export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<z.infer<typeof notificationFormSchema>>({
|
||||
resolver: zodResolver(notificationFormSchema),
|
||||
defaultValues: data ? data : {
|
||||
name: "",
|
||||
url: "",
|
||||
request_method: 1,
|
||||
request_type: 1,
|
||||
request_header: "",
|
||||
request_body: "",
|
||||
},
|
||||
defaultValues: data
|
||||
? data
|
||||
: {
|
||||
name: "",
|
||||
url: "",
|
||||
request_method: 1,
|
||||
request_type: 1,
|
||||
request_header: "",
|
||||
request_body: "",
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof notificationFormSchema>) => {
|
||||
data?.id ? await updateNotification(data.id, values) : await createNotification(values);
|
||||
setOpen(false);
|
||||
await mutate();
|
||||
form.reset();
|
||||
data?.id ? await updateNotification(data.id, values) : await createNotification(values)
|
||||
setOpen(false)
|
||||
await mutate()
|
||||
form.reset()
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{data
|
||||
?
|
||||
<IconButton variant="outline" icon="edit" />
|
||||
:
|
||||
<IconButton icon="plus" />
|
||||
}
|
||||
{data ? <IconButton variant="outline" icon="edit" /> : <IconButton icon="plus" />}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{data?t("EditNotifier"):t("CreateNotifier")}</DialogTitle>
|
||||
<DialogTitle>
|
||||
{data ? t("EditNotifier") : t("CreateNotifier")}
|
||||
</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -110,10 +109,7 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My Notifier"
|
||||
{...field}
|
||||
/>
|
||||
<Input placeholder="My Notifier" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -126,9 +122,7 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
<FormItem>
|
||||
<FormLabel>URL</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
/>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -140,16 +134,23 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("RequestMethod")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Request Method" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.entries(nrequestMethods).map(([k, v]) => (
|
||||
<SelectItem key={k} value={k}>{v}</SelectItem>
|
||||
))}
|
||||
{Object.entries(nrequestMethods).map(
|
||||
([k, v]) => (
|
||||
<SelectItem key={k} value={k}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
@@ -162,7 +163,10 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Type")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Request Type" />
|
||||
@@ -170,7 +174,9 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.entries(nrequestTypes).map(([k, v]) => (
|
||||
<SelectItem key={k} value={k}>{v}</SelectItem>
|
||||
<SelectItem key={k} value={k}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -223,7 +229,9 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">{t("VerifyTLS")}</Label>
|
||||
<Label className="text-sm">
|
||||
{t("VerifyTLS")}
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -241,7 +249,9 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">{t("DoNotSendTestMessage")}</Label>
|
||||
<Label className="text-sm">
|
||||
{t("DoNotSendTestMessage")}
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -254,7 +264,9 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
<Button type="submit" className="my-2">
|
||||
{t("Confirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getProfile, updateProfile } from "@/api/user"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -18,54 +18,53 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { getProfile, updateProfile } from "@/api/user"
|
||||
import { useState } from "react"
|
||||
import { useMainStore } from "@/hooks/useMainStore"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { z } from "zod"
|
||||
|
||||
const profileFormSchema = z.object({
|
||||
original_password: z.string().min(5).max(72),
|
||||
new_password: z.string().min(8).max(72),
|
||||
new_username: z.string().min(1).max(32),
|
||||
});
|
||||
})
|
||||
|
||||
export const ProfileCard = ({ className }: { className: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const { profile, setProfile } = useMainStore();
|
||||
const { t } = useTranslation()
|
||||
const { profile, setProfile } = useMainStore()
|
||||
|
||||
const form = useForm<z.infer<typeof profileFormSchema>>({
|
||||
resolver: zodResolver(profileFormSchema),
|
||||
defaultValues: {
|
||||
original_password: '',
|
||||
new_password: '',
|
||||
original_password: "",
|
||||
new_password: "",
|
||||
new_username: profile?.username,
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof profileFormSchema>) => {
|
||||
try {
|
||||
await updateProfile(values);
|
||||
await updateProfile(values)
|
||||
} catch (e) {
|
||||
toast(t("Error"), {
|
||||
description: `${e}`,
|
||||
})
|
||||
return;
|
||||
return
|
||||
}
|
||||
const profile = await getProfile();
|
||||
setProfile(profile);
|
||||
setOpen(false);
|
||||
form.reset();
|
||||
const profile = await getProfile()
|
||||
setProfile(profile)
|
||||
setOpen(false)
|
||||
form.reset()
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -91,10 +90,7 @@ export const ProfileCard = ({ className }: { className: string }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("NewUsername")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
autoComplete="username"
|
||||
{...field}
|
||||
/>
|
||||
<Input autoComplete="username" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -107,10 +103,7 @@ export const ProfileCard = ({ className }: { className: string }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("OriginalPassword")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
autoComplete="current-password"
|
||||
{...field}
|
||||
/>
|
||||
<Input autoComplete="current-password" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -123,9 +116,7 @@ export const ProfileCard = ({ className }: { className: string }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("NewPassword")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
/>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -138,7 +129,9 @@ export const ProfileCard = ({ className }: { className: string }) => {
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
<Button type="submit" className="my-2">
|
||||
{t("Confirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createServerGroup, updateServerGroup } from "@/api/server-group"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -18,76 +18,76 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { ModelServerGroupResponseItem } from "@/types"
|
||||
import { useState } from "react"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { createServerGroup, updateServerGroup } from "@/api/server-group"
|
||||
import { MultiSelect } from "@/components/xui/multi-select"
|
||||
import { useServer } from "@/hooks/useServer"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ModelServerGroupResponseItem } from "@/types"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { z } from "zod"
|
||||
|
||||
interface ServerGroupCardProps {
|
||||
data?: ModelServerGroupResponseItem;
|
||||
mutate: KeyedMutator<ModelServerGroupResponseItem[]>;
|
||||
data?: ModelServerGroupResponseItem
|
||||
mutate: KeyedMutator<ModelServerGroupResponseItem[]>
|
||||
}
|
||||
|
||||
const serverGroupFormSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
servers: z.array(z.number()),
|
||||
});
|
||||
})
|
||||
|
||||
export const ServerGroupCard: React.FC<ServerGroupCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<z.infer<typeof serverGroupFormSchema>>({
|
||||
resolver: zodResolver(serverGroupFormSchema),
|
||||
defaultValues: data ? {
|
||||
name: data.group.name,
|
||||
servers: data.servers,
|
||||
} : {
|
||||
name: "",
|
||||
servers: [],
|
||||
},
|
||||
defaultValues: data
|
||||
? {
|
||||
name: data.group.name,
|
||||
servers: data.servers,
|
||||
}
|
||||
: {
|
||||
name: "",
|
||||
servers: [],
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof serverGroupFormSchema>) => {
|
||||
data?.group.id ? await updateServerGroup(data.group.id, values) : await createServerGroup(values);
|
||||
setOpen(false);
|
||||
await mutate();
|
||||
form.reset();
|
||||
data?.group.id
|
||||
? await updateServerGroup(data.group.id, values)
|
||||
: await createServerGroup(values)
|
||||
setOpen(false)
|
||||
await mutate()
|
||||
form.reset()
|
||||
}
|
||||
|
||||
const { servers } = useServer();
|
||||
const serverList = servers?.map(s => ({
|
||||
const { servers } = useServer()
|
||||
const serverList = servers?.map((s) => ({
|
||||
value: `${s.id}`,
|
||||
label: s.name,
|
||||
})) || [{ value: "", label: "" }];
|
||||
})) || [{ value: "", label: "" }]
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{data
|
||||
?
|
||||
<IconButton variant="outline" icon="edit" />
|
||||
:
|
||||
<IconButton icon="plus" />
|
||||
}
|
||||
{data ? <IconButton variant="outline" icon="edit" /> : <IconButton icon="plus" />}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{data? t("EditServerGroup"):t("CreateServerGroup")}</DialogTitle>
|
||||
<DialogTitle>
|
||||
{data ? t("EditServerGroup") : t("CreateServerGroup")}
|
||||
</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -99,10 +99,7 @@ export const ServerGroupCard: React.FC<ServerGroupCardProps> = ({ data, mutate }
|
||||
<FormItem>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="Group Name"
|
||||
{...field}
|
||||
/>
|
||||
<Input placeholder="Group Name" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -117,9 +114,9 @@ export const ServerGroupCard: React.FC<ServerGroupCardProps> = ({ data, mutate }
|
||||
<FormControl>
|
||||
<MultiSelect
|
||||
options={serverList}
|
||||
onValueChange={e => {
|
||||
const arr = e.map(Number);
|
||||
field.onChange(arr);
|
||||
onValueChange={(e) => {
|
||||
const arr = e.map(Number)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
defaultValue={field.value?.map(String)}
|
||||
/>
|
||||
@@ -134,7 +131,9 @@ export const ServerGroupCard: React.FC<ServerGroupCardProps> = ({ data, mutate }
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
<Button type="submit" className="my-2">
|
||||
{t("Confirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { updateServer } from "@/api/server"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
@@ -9,7 +11,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -18,25 +19,24 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { ModelServer } from "@/types"
|
||||
import { updateServer } from "@/api/server"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { conv } from "@/lib/utils"
|
||||
import { useState } from "react"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { asOptionalField } from "@/lib/utils"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { conv } from "@/lib/utils"
|
||||
import { asOptionalField } from "@/lib/utils"
|
||||
import { ModelServer } from "@/types"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { z } from "zod"
|
||||
|
||||
interface ServerCardProps {
|
||||
data: ModelServer;
|
||||
mutate: KeyedMutator<ModelServer[]>;
|
||||
data: ModelServer
|
||||
mutate: KeyedMutator<ModelServer[]>
|
||||
}
|
||||
|
||||
const serverFormSchema = z.object({
|
||||
@@ -47,25 +47,25 @@ const serverFormSchema = z.object({
|
||||
hide_for_guest: asOptionalField(z.boolean()),
|
||||
enable_ddns: asOptionalField(z.boolean()),
|
||||
ddns_profiles: asOptionalField(z.array(z.number())),
|
||||
});
|
||||
})
|
||||
|
||||
export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<z.infer<typeof serverFormSchema>>({
|
||||
resolver: zodResolver(serverFormSchema),
|
||||
defaultValues: data,
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof serverFormSchema>) => {
|
||||
await updateServer(data.id, values);
|
||||
setOpen(false);
|
||||
await mutate();
|
||||
form.reset();
|
||||
await updateServer(data.id, values)
|
||||
setOpen(false)
|
||||
await mutate()
|
||||
form.reset()
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -77,7 +77,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t("EditServer") }</DialogTitle>
|
||||
<DialogTitle>{t("EditServer")}</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -89,10 +89,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("Name")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="My Server"
|
||||
{...field}
|
||||
/>
|
||||
<Input placeholder="My Server" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -105,11 +102,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("Weight")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="0"
|
||||
{...field}
|
||||
/>
|
||||
<Input type="number" placeholder="0" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -120,16 +113,20 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
name="ddns_profiles"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("DDNSProfiles") + t("SeparateWithComma")}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("DDNSProfiles") + t("SeparateWithComma")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
{...field}
|
||||
value={conv.arrToStr(field.value || [])}
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
console.log(field.value)
|
||||
const arr = conv.strToArr(e.target.value).map(Number);
|
||||
field.onChange(arr);
|
||||
const arr = conv
|
||||
.strToArr(e.target.value)
|
||||
.map(Number)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -148,7 +145,9 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">{t("Enable") + t("DDNS") }</Label>
|
||||
<Label className="text-sm">
|
||||
{t("Enable") + t("DDNS")}
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -166,7 +165,9 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">{t("HideForGuest")}</Label>
|
||||
<Label className="text-sm">
|
||||
{t("HideForGuest")}
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -180,10 +181,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("Private") + t("Note")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-none"
|
||||
{...field}
|
||||
/>
|
||||
<Textarea className="resize-none" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -196,10 +194,7 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("Public") + t("Note")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-y"
|
||||
{...field}
|
||||
/>
|
||||
<Textarea className="resize-y" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -211,7 +206,9 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">{t("Submit")}</Button>
|
||||
<Button type="submit" className="my-2">
|
||||
{t("Submit")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { createService, updateService } from "@/api/service"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
@@ -9,14 +11,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -25,30 +19,36 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { ModelService } from "@/types"
|
||||
import { createService, updateService } from "@/api/service"
|
||||
import { Checkbox } from "@/components/ui/checkbox"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { conv } from "@/lib/utils"
|
||||
import { useState } from "react"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { asOptionalField } from "@/lib/utils"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { serviceTypes, serviceCoverageTypes } from "@/types"
|
||||
import { MultiSelect } from "./xui/multi-select"
|
||||
import { Combobox } from "./ui/combobox"
|
||||
import { useServer } from "@/hooks/useServer"
|
||||
import { useNotification } from "@/hooks/useNotfication"
|
||||
import { useServer } from "@/hooks/useServer"
|
||||
import { conv } from "@/lib/utils"
|
||||
import { asOptionalField } from "@/lib/utils"
|
||||
import { ModelService } from "@/types"
|
||||
import { serviceCoverageTypes, serviceTypes } from "@/types"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { z } from "zod"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Combobox } from "./ui/combobox"
|
||||
import { MultiSelect } from "./xui/multi-select"
|
||||
|
||||
interface ServiceCardProps {
|
||||
data?: ModelService;
|
||||
mutate: KeyedMutator<ModelService[]>;
|
||||
data?: ModelService
|
||||
mutate: KeyedMutator<ModelService[]>
|
||||
}
|
||||
|
||||
const serviceFormSchema = z.object({
|
||||
@@ -68,72 +68,73 @@ const serviceFormSchema = z.object({
|
||||
skip_servers_raw: z.array(z.string()),
|
||||
target: z.string(),
|
||||
type: z.coerce.number().int().min(0),
|
||||
});
|
||||
})
|
||||
|
||||
export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<z.infer<typeof serviceFormSchema>>({
|
||||
resolver: zodResolver(serviceFormSchema),
|
||||
defaultValues: data ? {
|
||||
...data,
|
||||
skip_servers_raw: conv.recordToStrArr(data.skip_servers ? data.skip_servers : {}),
|
||||
} : {
|
||||
type: 1,
|
||||
cover: 0,
|
||||
name: "",
|
||||
target: "",
|
||||
max_latency: 0.0,
|
||||
min_latency: 0.0,
|
||||
duration: 30,
|
||||
notification_group_id: 0,
|
||||
fail_trigger_tasks: [],
|
||||
recover_trigger_tasks: [],
|
||||
skip_servers: {},
|
||||
skip_servers_raw: [],
|
||||
},
|
||||
defaultValues: data
|
||||
? {
|
||||
...data,
|
||||
skip_servers_raw: conv.recordToStrArr(data.skip_servers ? data.skip_servers : {}),
|
||||
}
|
||||
: {
|
||||
type: 1,
|
||||
cover: 0,
|
||||
name: "",
|
||||
target: "",
|
||||
max_latency: 0.0,
|
||||
min_latency: 0.0,
|
||||
duration: 30,
|
||||
notification_group_id: 0,
|
||||
fail_trigger_tasks: [],
|
||||
recover_trigger_tasks: [],
|
||||
skip_servers: {},
|
||||
skip_servers_raw: [],
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof serviceFormSchema>) => {
|
||||
values.skip_servers = conv.arrToRecord(values.skip_servers_raw);
|
||||
const { skip_servers_raw, ...requiredFields } = values;
|
||||
data?.id ? await updateService(data.id, requiredFields) : await createService(requiredFields);
|
||||
setOpen(false);
|
||||
await mutate();
|
||||
form.reset();
|
||||
values.skip_servers = conv.arrToRecord(values.skip_servers_raw)
|
||||
const { skip_servers_raw, ...requiredFields } = values
|
||||
data?.id
|
||||
? await updateService(data.id, requiredFields)
|
||||
: await createService(requiredFields)
|
||||
setOpen(false)
|
||||
await mutate()
|
||||
form.reset()
|
||||
}
|
||||
|
||||
const { servers } = useServer();
|
||||
const serverList = servers?.map(s => ({
|
||||
const { servers } = useServer()
|
||||
const serverList = servers?.map((s) => ({
|
||||
value: `${s.id}`,
|
||||
label: s.name,
|
||||
})) || [{ value: "", label: "" }];
|
||||
})) || [{ value: "", label: "" }]
|
||||
|
||||
const { notifierGroup } = useNotification();
|
||||
const ngroupList = notifierGroup?.map(ng => ({
|
||||
const { notifierGroup } = useNotification()
|
||||
const ngroupList = notifierGroup?.map((ng) => ({
|
||||
value: `${ng.group.id}`,
|
||||
label: ng.group.name,
|
||||
})) || [{ value: "", label: "" }];
|
||||
})) || [{ value: "", label: "" }]
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
{data
|
||||
?
|
||||
<IconButton variant="outline" icon="edit" />
|
||||
:
|
||||
<IconButton icon="plus" />
|
||||
}
|
||||
{data ? <IconButton variant="outline" icon="edit" /> : <IconButton icon="plus" />}
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-xl">
|
||||
<ScrollArea className="max-h-[calc(100dvh-5rem)] p-3">
|
||||
<div className="items-center mx-1">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{data?t("EditService"):t("CreateService")}</DialogTitle>
|
||||
<DialogTitle>
|
||||
{data ? t("EditService") : t("CreateService")}
|
||||
</DialogTitle>
|
||||
<DialogDescription />
|
||||
</DialogHeader>
|
||||
<Form {...form}>
|
||||
@@ -176,7 +177,10 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Type")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select service type" />
|
||||
@@ -184,7 +188,9 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.entries(serviceTypes).map(([k, v]) => (
|
||||
<SelectItem key={k} value={k}>{v}</SelectItem>
|
||||
<SelectItem key={k} value={k}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
@@ -203,7 +209,9 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">{t("ShowInService")}</Label>
|
||||
<Label className="text-sm">
|
||||
{t("ShowInService")}
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -217,11 +225,7 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("Interval")} (s)</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
placeholder="30"
|
||||
{...field}
|
||||
/>
|
||||
<Input type="number" placeholder="30" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -233,16 +237,23 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Coverage")}</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={`${field.value}`}>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.entries(serviceCoverageTypes).map(([k, v]) => (
|
||||
<SelectItem key={k} value={k}>{v}</SelectItem>
|
||||
))}
|
||||
{Object.entries(serviceCoverageTypes).map(
|
||||
([k, v]) => (
|
||||
<SelectItem key={k} value={k}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
@@ -295,7 +306,9 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">{t("EnableFailureNotification")}</Label>
|
||||
<Label className="text-sm">
|
||||
{t("EnableFailureNotification")}
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -347,7 +360,9 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">{t("EnableLatencyNotification")}</Label>
|
||||
<Label className="text-sm">
|
||||
{t("EnableLatencyNotification")}
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -365,7 +380,9 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
<Label className="text-sm">{t("EnableTriggerTask")}</Label>
|
||||
<Label className="text-sm">
|
||||
{t("EnableTriggerTask")}
|
||||
</Label>
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
@@ -377,15 +394,20 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
name="fail_trigger_tasks"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("TasksToTriggerOnAlert") + t("SeparateWithComma")}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("TasksToTriggerOnAlert") +
|
||||
t("SeparateWithComma")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
{...field}
|
||||
value={conv.arrToStr(field.value ?? [])}
|
||||
onChange={e => {
|
||||
const arr = conv.strToArr(e.target.value).map(Number);
|
||||
field.onChange(arr);
|
||||
onChange={(e) => {
|
||||
const arr = conv
|
||||
.strToArr(e.target.value)
|
||||
.map(Number)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -398,15 +420,20 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
name="recover_trigger_tasks"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("TasksToTriggerAfterRecovery") + t("SeparateWithComma")}</FormLabel>
|
||||
<FormLabel>
|
||||
{t("TasksToTriggerAfterRecovery") +
|
||||
t("SeparateWithComma")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder="1,2,3"
|
||||
{...field}
|
||||
value={conv.arrToStr(field.value ?? [])}
|
||||
onChange={e => {
|
||||
const arr = conv.strToArr(e.target.value).map(Number);
|
||||
field.onChange(arr);
|
||||
onChange={(e) => {
|
||||
const arr = conv
|
||||
.strToArr(e.target.value)
|
||||
.map(Number)
|
||||
field.onChange(arr)
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -420,7 +447,9 @@ export const ServiceCard: React.FC<ServiceCardProps> = ({ data, mutate }) => {
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">{t("Submit")}</Button>
|
||||
<Button type="submit" className="my-2">
|
||||
{t("Submit")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import {
|
||||
Tabs,
|
||||
TabsList,
|
||||
TabsTrigger,
|
||||
} from "@/components/ui/tabs"
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { Link, useLocation } from "react-router-dom"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const SettingsTab = ({ className }: { className?: string }) => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const { t } = useTranslation()
|
||||
const location = useLocation()
|
||||
|
||||
return (
|
||||
<Tabs defaultValue={location.pathname} className={className}>
|
||||
|
||||
@@ -7,137 +7,144 @@ import {
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "@/components/ui/alert-dialog"
|
||||
import { Terminal } from "@xterm/xterm";
|
||||
import { AttachAddon } from "@xterm/addon-attach";
|
||||
import { FitAddon } from '@xterm/addon-fit';
|
||||
import { useRef, useEffect, useState, useMemo } from "react";
|
||||
import { sleep } from "@/lib/utils";
|
||||
import "@xterm/xterm/css/xterm.css";
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { Button } from "./ui/button";
|
||||
import { toast } from "sonner";
|
||||
import { FMCard } from "./fm";
|
||||
import useTerminal from "@/hooks/useTerminal";
|
||||
import { IconButton } from "./xui/icon-button";
|
||||
import useTerminal from "@/hooks/useTerminal"
|
||||
import { sleep } from "@/lib/utils"
|
||||
import { AttachAddon } from "@xterm/addon-attach"
|
||||
import { FitAddon } from "@xterm/addon-fit"
|
||||
import { Terminal } from "@xterm/xterm"
|
||||
import "@xterm/xterm/css/xterm.css"
|
||||
import { useEffect, useMemo, useRef, useState } from "react"
|
||||
import { useParams } from "react-router-dom"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { FMCard } from "./fm"
|
||||
import { Button } from "./ui/button"
|
||||
import { IconButton } from "./xui/icon-button"
|
||||
|
||||
interface XtermProps {
|
||||
wsUrl: string;
|
||||
setClose: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
wsUrl: string
|
||||
setClose: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
const XtermComponent: React.FC<XtermProps & JSX.IntrinsicElements["div"]> = ({ wsUrl, setClose, ...props }) => {
|
||||
const terminalIdRef = useRef<HTMLDivElement>(null);
|
||||
const terminalRef = useRef<Terminal | null>(null);
|
||||
const wsRef = useRef<WebSocket | null>(null);
|
||||
const XtermComponent: React.FC<XtermProps & JSX.IntrinsicElements["div"]> = ({
|
||||
wsUrl,
|
||||
setClose,
|
||||
...props
|
||||
}) => {
|
||||
const terminalIdRef = useRef<HTMLDivElement>(null)
|
||||
const terminalRef = useRef<Terminal | null>(null)
|
||||
const wsRef = useRef<WebSocket | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
wsRef.current?.close();
|
||||
terminalRef.current?.dispose();
|
||||
};
|
||||
}, []);
|
||||
wsRef.current?.close()
|
||||
terminalRef.current?.dispose()
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
terminalRef.current = new Terminal({
|
||||
cursorBlink: true,
|
||||
fontSize: 16,
|
||||
});
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
ws.binaryType = "arraybuffer";
|
||||
})
|
||||
const ws = new WebSocket(wsUrl)
|
||||
wsRef.current = ws
|
||||
ws.binaryType = "arraybuffer"
|
||||
ws.onopen = () => {
|
||||
onResize();
|
||||
onResize()
|
||||
}
|
||||
ws.onclose = () => {
|
||||
terminalRef.current?.dispose();
|
||||
setClose(true);
|
||||
terminalRef.current?.dispose()
|
||||
setClose(true)
|
||||
}
|
||||
ws.onerror = (e) => {
|
||||
console.error(e);
|
||||
console.error(e)
|
||||
toast("Websocket error", {
|
||||
description: "View console for details.",
|
||||
})
|
||||
}
|
||||
}, [wsUrl]);
|
||||
}, [wsUrl])
|
||||
|
||||
|
||||
const fitAddon = useRef(new FitAddon()).current;
|
||||
const sendResize = useRef(false);
|
||||
const fitAddon = useRef(new FitAddon()).current
|
||||
const sendResize = useRef(false)
|
||||
|
||||
const doResize = () => {
|
||||
if (!terminalIdRef.current) return;
|
||||
if (!terminalIdRef.current) return
|
||||
|
||||
fitAddon.fit();
|
||||
fitAddon.fit()
|
||||
|
||||
const dimensions = fitAddon.proposeDimensions();
|
||||
const dimensions = fitAddon.proposeDimensions()
|
||||
|
||||
if (dimensions) {
|
||||
const prefix = new Int8Array([1]);
|
||||
const resizeMessage = new TextEncoder().encode(JSON.stringify({
|
||||
Rows: dimensions.rows,
|
||||
Cols: dimensions.cols,
|
||||
}));
|
||||
const prefix = new Int8Array([1])
|
||||
const resizeMessage = new TextEncoder().encode(
|
||||
JSON.stringify({
|
||||
Rows: dimensions.rows,
|
||||
Cols: dimensions.cols,
|
||||
}),
|
||||
)
|
||||
|
||||
const msg = new Int8Array(prefix.length + resizeMessage.length);
|
||||
msg.set(prefix);
|
||||
msg.set(resizeMessage, prefix.length);
|
||||
const msg = new Int8Array(prefix.length + resizeMessage.length)
|
||||
msg.set(prefix)
|
||||
msg.set(resizeMessage, prefix.length)
|
||||
|
||||
wsRef.current?.send(msg);
|
||||
wsRef.current?.send(msg)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const onResize = async () => {
|
||||
if (sendResize.current) return;
|
||||
if (sendResize.current) return
|
||||
|
||||
sendResize.current = true;
|
||||
sendResize.current = true
|
||||
try {
|
||||
await sleep(1500);
|
||||
doResize();
|
||||
await sleep(1500)
|
||||
doResize()
|
||||
} catch (error) {
|
||||
console.error('resize error', error);
|
||||
console.error("resize error", error)
|
||||
} finally {
|
||||
sendResize.current = false;
|
||||
sendResize.current = false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!wsRef.current || !terminalIdRef.current || !terminalRef.current) return;
|
||||
const attachAddon = new AttachAddon(wsRef.current);
|
||||
terminalRef.current.loadAddon(attachAddon);
|
||||
terminalRef.current.loadAddon(fitAddon);
|
||||
terminalRef.current.open(terminalIdRef.current);
|
||||
window.addEventListener('resize', onResize);
|
||||
if (!wsRef.current || !terminalIdRef.current || !terminalRef.current) return
|
||||
const attachAddon = new AttachAddon(wsRef.current)
|
||||
terminalRef.current.loadAddon(attachAddon)
|
||||
terminalRef.current.loadAddon(fitAddon)
|
||||
terminalRef.current.open(terminalIdRef.current)
|
||||
window.addEventListener("resize", onResize)
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
window.removeEventListener("resize", onResize)
|
||||
if (wsRef.current) {
|
||||
wsRef.current.close();
|
||||
wsRef.current.close()
|
||||
}
|
||||
};
|
||||
}, [wsRef.current, terminalRef.current, terminalIdRef.current]);
|
||||
}
|
||||
}, [wsRef.current, terminalRef.current, terminalIdRef.current])
|
||||
|
||||
return <div ref={terminalIdRef} {...props} />;
|
||||
};
|
||||
return <div ref={terminalIdRef} {...props} />
|
||||
}
|
||||
|
||||
export const TerminalPage = () => {
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const [open, setOpen] = useState(false);
|
||||
const terminal = useTerminal(id ? parseInt(id) : undefined);
|
||||
const { id } = useParams<{ id: string }>()
|
||||
const [open, setOpen] = useState(false)
|
||||
const terminal = useTerminal(id ? parseInt(id) : undefined)
|
||||
return (
|
||||
<div className="px-8">
|
||||
<div className="flex mt-6 mb-4">
|
||||
<h1 className="flex-1 text-3xl font-bold tracking-tight">
|
||||
{`Terminal (${id})`}
|
||||
</h1>
|
||||
<h1 className="flex-1 text-3xl font-bold tracking-tight">{`Terminal (${id})`}</h1>
|
||||
<div className="flex-2 flex ml-auto gap-2">
|
||||
<FMCard id={id} />
|
||||
</div>
|
||||
</div>
|
||||
{terminal?.session_id
|
||||
?
|
||||
<XtermComponent className="max-h-[60%] mb-5" wsUrl={`/api/v1/ws/terminal/${terminal?.session_id}`} setClose={setOpen} />
|
||||
:
|
||||
{terminal?.session_id ? (
|
||||
<XtermComponent
|
||||
className="max-h-[60%] mb-5"
|
||||
wsUrl={`/api/v1/ws/terminal/${terminal?.session_id}`}
|
||||
setClose={setOpen}
|
||||
/>
|
||||
) : (
|
||||
<p>The server does not exist, or have not been connected yet.</p>
|
||||
}
|
||||
)}
|
||||
<AlertDialog open={open} onOpenChange={setOpen}>
|
||||
<AlertDialogContent className="sm:max-w-lg">
|
||||
<AlertDialogHeader>
|
||||
@@ -148,9 +155,7 @@ export const TerminalPage = () => {
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogAction asChild>
|
||||
<Button onClick={window.close}>
|
||||
Close
|
||||
</Button>
|
||||
<Button onClick={window.close}>Close</Button>
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
@@ -161,10 +166,8 @@ export const TerminalPage = () => {
|
||||
|
||||
export const TerminalButton = ({ id }: { id: number }) => {
|
||||
const handleOpenNewTab = () => {
|
||||
window.open(`/dashboard/terminal/${id}`, '_blank');
|
||||
};
|
||||
window.open(`/dashboard/terminal/${id}`, "_blank")
|
||||
}
|
||||
|
||||
return (
|
||||
<IconButton variant="outline" icon="terminal" onClick={handleOpenNewTab} />
|
||||
)
|
||||
return <IconButton variant="outline" icon="terminal" onClick={handleOpenNewTab} />
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ export function ThemeProvider({
|
||||
...props
|
||||
}: ThemeProviderProps) {
|
||||
const [theme, setTheme] = useState<Theme>(
|
||||
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
|
||||
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
@@ -36,8 +36,7 @@ export function ThemeProvider({
|
||||
root.classList.remove("light", "dark")
|
||||
|
||||
if (theme === "system") {
|
||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
||||
.matches
|
||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light"
|
||||
|
||||
@@ -66,8 +65,7 @@ export function ThemeProvider({
|
||||
export const useTheme = () => {
|
||||
const context = useContext(ThemeProviderContext)
|
||||
|
||||
if (context === undefined)
|
||||
throw new Error("useTheme must be used within a ThemeProvider")
|
||||
if (context === undefined) throw new Error("useTheme must be used within a ThemeProvider")
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as React from "react"
|
||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||
import * as React from "react"
|
||||
|
||||
const AlertDialog = AlertDialogPrimitive.Root
|
||||
|
||||
@@ -11,129 +10,105 @@ const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
||||
const AlertDialogPortal = AlertDialogPrimitive.Portal
|
||||
|
||||
const AlertDialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
<AlertDialogPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
))
|
||||
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
||||
|
||||
const AlertDialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</AlertDialogPortal>
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</AlertDialogPortal>
|
||||
))
|
||||
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
||||
|
||||
const AlertDialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-2 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
const AlertDialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
|
||||
)
|
||||
AlertDialogHeader.displayName = "AlertDialogHeader"
|
||||
|
||||
const AlertDialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
const AlertDialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
AlertDialogFooter.displayName = "AlertDialogFooter"
|
||||
|
||||
const AlertDialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
<AlertDialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
||||
|
||||
const AlertDialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
<AlertDialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogDescription.displayName =
|
||||
AlertDialogPrimitive.Description.displayName
|
||||
AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName
|
||||
|
||||
const AlertDialogAction = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Action
|
||||
ref={ref}
|
||||
className={cn(buttonVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
<AlertDialogPrimitive.Action ref={ref} className={cn(buttonVariants(), className)} {...props} />
|
||||
))
|
||||
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
||||
|
||||
const AlertDialogCancel = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Cancel
|
||||
ref={ref}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"mt-2 sm:mt-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<AlertDialogPrimitive.Cancel
|
||||
ref={ref}
|
||||
className={cn(buttonVariants({ variant: "outline" }), "mt-2 sm:mt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
||||
|
||||
export {
|
||||
AlertDialog,
|
||||
AlertDialogPortal,
|
||||
AlertDialogOverlay,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogFooter,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
AlertDialog,
|
||||
AlertDialogPortal,
|
||||
AlertDialogOverlay,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogFooter,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
}
|
||||
|
||||
@@ -1,47 +1,43 @@
|
||||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
import * as React from "react"
|
||||
|
||||
const Avatar = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
||||
React.ElementRef<typeof AvatarPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
Avatar.displayName = AvatarPrimitive.Root.displayName
|
||||
|
||||
const AvatarImage = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Image>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
||||
React.ElementRef<typeof AvatarPrimitive.Image>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Image
|
||||
ref={ref}
|
||||
className={cn("aspect-square h-full w-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
<AvatarPrimitive.Image
|
||||
ref={ref}
|
||||
className={cn("aspect-square h-full w-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
||||
|
||||
const AvatarFallback = React.forwardRef<
|
||||
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
||||
React.ElementRef<typeof AvatarPrimitive.Fallback>,
|
||||
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-full w-full items-center justify-center rounded-full bg-muted",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
||||
|
||||
|
||||
@@ -1,36 +1,33 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { type VariantProps, cva } from "class-variance-authority"
|
||||
import * as React from "react"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
)
|
||||
return <div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
|
||||
@@ -1,115 +1,100 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { ChevronRight, MoreHorizontal } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as React from "react"
|
||||
|
||||
const Breadcrumb = React.forwardRef<
|
||||
HTMLElement,
|
||||
React.ComponentPropsWithoutRef<"nav"> & {
|
||||
separator?: React.ReactNode
|
||||
}
|
||||
HTMLElement,
|
||||
React.ComponentPropsWithoutRef<"nav"> & {
|
||||
separator?: React.ReactNode
|
||||
}
|
||||
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
|
||||
Breadcrumb.displayName = "Breadcrumb"
|
||||
|
||||
const BreadcrumbList = React.forwardRef<
|
||||
HTMLOListElement,
|
||||
React.ComponentPropsWithoutRef<"ol">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ol
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
const BreadcrumbList = React.forwardRef<HTMLOListElement, React.ComponentPropsWithoutRef<"ol">>(
|
||||
({ className, ...props }, ref) => (
|
||||
<ol
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
)
|
||||
BreadcrumbList.displayName = "BreadcrumbList"
|
||||
|
||||
const BreadcrumbItem = React.forwardRef<
|
||||
HTMLLIElement,
|
||||
React.ComponentPropsWithoutRef<"li">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<li
|
||||
ref={ref}
|
||||
className={cn("inline-flex items-center gap-1.5", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
const BreadcrumbItem = React.forwardRef<HTMLLIElement, React.ComponentPropsWithoutRef<"li">>(
|
||||
({ className, ...props }, ref) => (
|
||||
<li ref={ref} className={cn("inline-flex items-center gap-1.5", className)} {...props} />
|
||||
),
|
||||
)
|
||||
BreadcrumbItem.displayName = "BreadcrumbItem"
|
||||
|
||||
const BreadcrumbLink = React.forwardRef<
|
||||
HTMLAnchorElement,
|
||||
React.ComponentPropsWithoutRef<"a"> & {
|
||||
asChild?: boolean
|
||||
}
|
||||
HTMLAnchorElement,
|
||||
React.ComponentPropsWithoutRef<"a"> & {
|
||||
asChild?: boolean
|
||||
}
|
||||
>(({ asChild, className, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "a"
|
||||
const Comp = asChild ? Slot : "a"
|
||||
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
className={cn("transition-colors hover:text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<Comp
|
||||
ref={ref}
|
||||
className={cn("transition-colors hover:text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
BreadcrumbLink.displayName = "BreadcrumbLink"
|
||||
|
||||
const BreadcrumbPage = React.forwardRef<
|
||||
HTMLSpanElement,
|
||||
React.ComponentPropsWithoutRef<"span">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<span
|
||||
ref={ref}
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
className={cn("font-normal text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
const BreadcrumbPage = React.forwardRef<HTMLSpanElement, React.ComponentPropsWithoutRef<"span">>(
|
||||
({ className, ...props }, ref) => (
|
||||
<span
|
||||
ref={ref}
|
||||
role="link"
|
||||
aria-disabled="true"
|
||||
aria-current="page"
|
||||
className={cn("font-normal text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
)
|
||||
BreadcrumbPage.displayName = "BreadcrumbPage"
|
||||
|
||||
const BreadcrumbSeparator = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"li">) => (
|
||||
<li
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <ChevronRight />}
|
||||
</li>
|
||||
const BreadcrumbSeparator = ({ children, className, ...props }: React.ComponentProps<"li">) => (
|
||||
<li
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
|
||||
{...props}
|
||||
>
|
||||
{children ?? <ChevronRight />}
|
||||
</li>
|
||||
)
|
||||
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
|
||||
|
||||
const BreadcrumbEllipsis = ({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"span">) => (
|
||||
<span
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">More</span>
|
||||
</span>
|
||||
const BreadcrumbEllipsis = ({ className, ...props }: React.ComponentProps<"span">) => (
|
||||
<span
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
<span className="sr-only">More</span>
|
||||
</span>
|
||||
)
|
||||
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
|
||||
|
||||
export {
|
||||
Breadcrumb,
|
||||
BreadcrumbList,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbEllipsis,
|
||||
Breadcrumb,
|
||||
BreadcrumbList,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbLink,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
BreadcrumbEllipsis,
|
||||
}
|
||||
|
||||
@@ -1,55 +1,52 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { type VariantProps, cva } from "class-variance-authority"
|
||||
import * as React from "react"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-10 px-4 py-2",
|
||||
sm: "h-9 rounded-md px-3",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
|
||||
@@ -1,79 +1,55 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
)
|
||||
Card.displayName = "Card"
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("flex flex-col space-y-1.5 p-6", className)} {...props} />
|
||||
),
|
||||
)
|
||||
CardHeader.displayName = "CardHeader"
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-2xl font-semibold leading-none tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
const CardTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn("text-2xl font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
)
|
||||
CardTitle.displayName = "CardTitle"
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
<p ref={ref} className={cn("text-sm text-muted-foreground", className)} {...props} />
|
||||
))
|
||||
CardDescription.displayName = "CardDescription"
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
))
|
||||
const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||
),
|
||||
)
|
||||
CardContent.displayName = "CardContent"
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn("flex items-center p-6 pt-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn("flex items-center p-6 pt-0", className)} {...props} />
|
||||
),
|
||||
)
|
||||
CardFooter.displayName = "CardFooter"
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||
import { Check } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as React from "react"
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("flex items-center justify-center text-current")}
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("flex items-center justify-center text-current")}
|
||||
>
|
||||
<Check className="h-4 w-4" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
))
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import { Check, ChevronDown } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Command,
|
||||
@@ -13,106 +9,97 @@ import {
|
||||
CommandItem,
|
||||
CommandList,
|
||||
} from "@/components/ui/command"
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Check, ChevronDown } from "lucide-react"
|
||||
import * as React from "react"
|
||||
|
||||
interface ComboboxProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
interface ComboboxProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
options: {
|
||||
label: string,
|
||||
value: string,
|
||||
}[];
|
||||
label: string
|
||||
value: string
|
||||
}[]
|
||||
|
||||
placeholder?: string;
|
||||
defaultValue?: string;
|
||||
className?: string;
|
||||
onValueChange: (value: string) => void;
|
||||
placeholder?: string
|
||||
defaultValue?: string
|
||||
className?: string
|
||||
onValueChange: (value: string) => void
|
||||
}
|
||||
|
||||
export const Combobox = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
ComboboxProps
|
||||
>(({
|
||||
options,
|
||||
placeholder,
|
||||
defaultValue,
|
||||
className,
|
||||
onValueChange,
|
||||
...props
|
||||
}, ref) => {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [value, setValue] = React.useState(defaultValue)
|
||||
export const Combobox = React.forwardRef<HTMLButtonElement, ComboboxProps>(
|
||||
({ options, placeholder, defaultValue, className, onValueChange, ...props }, ref) => {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const [value, setValue] = React.useState(defaultValue)
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
ref={ref}
|
||||
{...props}
|
||||
role="combobox"
|
||||
variant="outline"
|
||||
aria-expanded={open}
|
||||
className={cn(
|
||||
"flex w-full justify-between hover:bg-inherit",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{value
|
||||
? (() => {
|
||||
const val = options.find((option) => option.value === value)?.label
|
||||
return (
|
||||
val ? (
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
ref={ref}
|
||||
{...props}
|
||||
role="combobox"
|
||||
variant="outline"
|
||||
aria-expanded={open}
|
||||
className={cn("flex w-full justify-between hover:bg-inherit", className)}
|
||||
>
|
||||
{value ? (
|
||||
(() => {
|
||||
const val = options.find((option) => option.value === value)?.label
|
||||
return val ? (
|
||||
<div>{val}</div>
|
||||
) : (
|
||||
<div className="text-muted-foreground">{placeholder}</div>
|
||||
)
|
||||
)
|
||||
})()
|
||||
: <div className="text-muted-foreground">{placeholder}</div>}
|
||||
<ChevronDown className="ml-auto opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Command
|
||||
filter={(value, search, keywords = []) => {
|
||||
const extendValue = value + " " + keywords.join(" ");
|
||||
if (extendValue.toLowerCase().includes(search.toLowerCase())) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}}
|
||||
>
|
||||
<CommandInput placeholder={placeholder} className="h-9" />
|
||||
<CommandList>
|
||||
<CommandEmpty>No result found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{options.map((option) => (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
keywords={[option.label]}
|
||||
onSelect={(currentValue) => {
|
||||
setValue(currentValue === value ? "" : currentValue)
|
||||
onValueChange(currentValue === value ? "" : currentValue)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"justify-start",
|
||||
value === option.value ? "opacity-100" : "opacity-0"
|
||||
)}
|
||||
/>
|
||||
<span>{option.label}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
});
|
||||
})()
|
||||
) : (
|
||||
<div className="text-muted-foreground">{placeholder}</div>
|
||||
)}
|
||||
<ChevronDown className="ml-auto opacity-50" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-auto p-0" align="start">
|
||||
<Command
|
||||
filter={(value, search, keywords = []) => {
|
||||
const extendValue = value + " " + keywords.join(" ")
|
||||
if (extendValue.toLowerCase().includes(search.toLowerCase())) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}}
|
||||
>
|
||||
<CommandInput placeholder={placeholder} className="h-9" />
|
||||
<CommandList>
|
||||
<CommandEmpty>No result found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
{options.map((option) => (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
keywords={[option.label]}
|
||||
onSelect={(currentValue) => {
|
||||
setValue(currentValue === value ? "" : currentValue)
|
||||
onValueChange(
|
||||
currentValue === value ? "" : currentValue,
|
||||
)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<Check
|
||||
className={cn(
|
||||
"justify-start",
|
||||
value === option.value
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
)}
|
||||
/>
|
||||
<span>{option.label}</span>
|
||||
</CommandItem>
|
||||
))}
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1,151 +1,140 @@
|
||||
import * as React from "react"
|
||||
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 { cn } from "@/lib/utils"
|
||||
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
||||
import * as React from "react"
|
||||
|
||||
const Command = React.forwardRef<
|
||||
React.ElementRef<typeof CommandPrimitive>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
||||
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}
|
||||
/>
|
||||
<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
|
||||
|
||||
const CommandDialog = ({ children, ...props }: DialogProps) => {
|
||||
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>
|
||||
)
|
||||
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>
|
||||
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>
|
||||
<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>
|
||||
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}
|
||||
/>
|
||||
<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>
|
||||
React.ElementRef<typeof CommandPrimitive.Empty>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
||||
>((props, ref) => (
|
||||
<CommandPrimitive.Empty
|
||||
ref={ref}
|
||||
className="py-6 text-center text-sm"
|
||||
{...props}
|
||||
/>
|
||||
<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>
|
||||
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}
|
||||
/>
|
||||
<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>
|
||||
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}
|
||||
/>
|
||||
<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>
|
||||
React.ElementRef<typeof CommandPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<CommandPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
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}
|
||||
/>
|
||||
)
|
||||
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,
|
||||
Command,
|
||||
CommandDialog,
|
||||
CommandInput,
|
||||
CommandList,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandItem,
|
||||
CommandShortcut,
|
||||
CommandSeparator,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as React from "react"
|
||||
|
||||
const Dialog = DialogPrimitive.Root
|
||||
|
||||
@@ -13,108 +12,93 @@ const DialogPortal = DialogPrimitive.Portal
|
||||
const DialogClose = DialogPrimitive.Close
|
||||
|
||||
const DialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||
|
||||
const DialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
<DialogPortal>
|
||||
<DialogOverlay />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
))
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
const DialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogHeader.displayName = "DialogHeader"
|
||||
|
||||
const DialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogFooter.displayName = "DialogFooter"
|
||||
|
||||
const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||
|
||||
const DialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogClose,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
Dialog,
|
||||
DialogPortal,
|
||||
DialogOverlay,
|
||||
DialogClose,
|
||||
DialogTrigger,
|
||||
DialogContent,
|
||||
DialogHeader,
|
||||
DialogFooter,
|
||||
DialogTitle,
|
||||
DialogDescription,
|
||||
}
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as React from "react"
|
||||
import { Drawer as DrawerPrimitive } from "vaul"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Drawer = ({
|
||||
shouldScaleBackground = true,
|
||||
...props
|
||||
shouldScaleBackground = true,
|
||||
...props
|
||||
}: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
|
||||
<DrawerPrimitive.Root
|
||||
shouldScaleBackground={shouldScaleBackground}
|
||||
{...props}
|
||||
/>
|
||||
<DrawerPrimitive.Root shouldScaleBackground={shouldScaleBackground} {...props} />
|
||||
)
|
||||
Drawer.displayName = "Drawer"
|
||||
|
||||
@@ -21,96 +17,81 @@ const DrawerPortal = DrawerPrimitive.Portal
|
||||
const DrawerClose = DrawerPrimitive.Close
|
||||
|
||||
const DrawerOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
||||
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn("fixed inset-0 z-50 bg-black/80", className)}
|
||||
{...props}
|
||||
/>
|
||||
<DrawerPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn("fixed inset-0 z-50 bg-black/80", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
|
||||
|
||||
const DrawerContent = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
||||
React.ElementRef<typeof DrawerPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay />
|
||||
<DrawerPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||
{children}
|
||||
</DrawerPrimitive.Content>
|
||||
</DrawerPortal>
|
||||
<DrawerPortal>
|
||||
<DrawerOverlay />
|
||||
<DrawerPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
|
||||
{children}
|
||||
</DrawerPrimitive.Content>
|
||||
</DrawerPortal>
|
||||
))
|
||||
DrawerContent.displayName = "DrawerContent"
|
||||
|
||||
const DrawerHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
|
||||
{...props}
|
||||
/>
|
||||
const DrawerHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)} {...props} />
|
||||
)
|
||||
DrawerHeader.displayName = "DrawerHeader"
|
||||
|
||||
const DrawerFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />
|
||||
)
|
||||
DrawerFooter.displayName = "DrawerFooter"
|
||||
|
||||
const DrawerTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
||||
React.ElementRef<typeof DrawerPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"text-lg font-semibold leading-none tracking-tight",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<DrawerPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DrawerTitle.displayName = DrawerPrimitive.Title.displayName
|
||||
|
||||
const DrawerDescription = React.forwardRef<
|
||||
React.ElementRef<typeof DrawerPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
||||
React.ElementRef<typeof DrawerPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DrawerPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
<DrawerPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DrawerDescription.displayName = DrawerPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Drawer,
|
||||
DrawerPortal,
|
||||
DrawerOverlay,
|
||||
DrawerTrigger,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerFooter,
|
||||
DrawerTitle,
|
||||
DrawerDescription,
|
||||
Drawer,
|
||||
DrawerPortal,
|
||||
DrawerOverlay,
|
||||
DrawerTrigger,
|
||||
DrawerClose,
|
||||
DrawerContent,
|
||||
DrawerHeader,
|
||||
DrawerFooter,
|
||||
DrawerTitle,
|
||||
DrawerDescription,
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
|
||||
import { Check, ChevronRight, Circle } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as React from "react"
|
||||
|
||||
const DropdownMenu = DropdownMenuPrimitive.Root
|
||||
|
||||
@@ -17,182 +16,169 @@ const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
|
||||
|
||||
const DropdownMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRight className="ml-auto" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRight className="ml-auto" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
))
|
||||
DropdownMenuSubTrigger.displayName =
|
||||
DropdownMenuPrimitive.SubTrigger.displayName
|
||||
DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
|
||||
|
||||
const DropdownMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
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",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
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",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSubContent.displayName =
|
||||
DropdownMenuPrimitive.SubContent.displayName
|
||||
DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
|
||||
|
||||
const DropdownMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
|
||||
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
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",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Portal>
|
||||
<DropdownMenuPrimitive.Content
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-lg 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,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
))
|
||||
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
|
||||
|
||||
const DropdownMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
}
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center gap-2 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 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||
inset && "pl-8",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
|
||||
|
||||
const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
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",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
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",
|
||||
className,
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.CheckboxItem>
|
||||
))
|
||||
DropdownMenuCheckboxItem.displayName =
|
||||
DropdownMenuPrimitive.CheckboxItem.displayName
|
||||
DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
|
||||
|
||||
const DropdownMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
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",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Circle className="h-2 w-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
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",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<DropdownMenuPrimitive.ItemIndicator>
|
||||
<Circle className="h-2 w-2 fill-current" />
|
||||
</DropdownMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.RadioItem>
|
||||
))
|
||||
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
|
||||
|
||||
const DropdownMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<DropdownMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
|
||||
|
||||
const DropdownMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
<DropdownMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
|
||||
|
||||
const DropdownMenuShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span className={cn("ml-auto text-xs tracking-widest opacity-60", className)} {...props} />
|
||||
)
|
||||
}
|
||||
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
|
||||
|
||||
export {
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenu,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuCheckboxItem,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuShortcut,
|
||||
DropdownMenuGroup,
|
||||
DropdownMenuPortal,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuRadioGroup,
|
||||
}
|
||||
|
||||
@@ -1,176 +1,168 @@
|
||||
import * as React from "react"
|
||||
import { Label } from "@/components/ui/label"
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import * as React from "react"
|
||||
import {
|
||||
Controller,
|
||||
ControllerProps,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
Controller,
|
||||
ControllerProps,
|
||||
FieldPath,
|
||||
FieldValues,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
} from "react-hook-form"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Label } from "@/components/ui/label"
|
||||
|
||||
const Form = FormProvider
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName
|
||||
name: TName
|
||||
}
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue
|
||||
)
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue)
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
)
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext)
|
||||
const itemContext = React.useContext(FormItemContext)
|
||||
const { getFieldState, formState } = useFormContext()
|
||||
const fieldContext = React.useContext(FormFieldContext)
|
||||
const itemContext = React.useContext(FormItemContext)
|
||||
const { getFieldState, formState } = useFormContext()
|
||||
|
||||
const fieldState = getFieldState(fieldContext.name, formState)
|
||||
const fieldState = getFieldState(fieldContext.name, formState)
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>")
|
||||
}
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>")
|
||||
}
|
||||
|
||||
const { id } = itemContext
|
||||
const { id } = itemContext
|
||||
|
||||
return {
|
||||
id,
|
||||
name: fieldContext.name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
}
|
||||
return {
|
||||
id,
|
||||
name: fieldContext.name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
}
|
||||
}
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string
|
||||
id: string
|
||||
}
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue
|
||||
const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue)
|
||||
|
||||
const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, ...props }, ref) => {
|
||||
const id = React.useId()
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
const FormItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const id = React.useId()
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||
</FormItemContext.Provider>
|
||||
)
|
||||
})
|
||||
FormItem.displayName = "FormItem"
|
||||
|
||||
const FormLabel = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { error, formItemId } = useFormField()
|
||||
const { error, formItemId } = useFormField()
|
||||
|
||||
return (
|
||||
<Label
|
||||
ref={ref}
|
||||
className={cn(error && "text-destructive", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<Label
|
||||
ref={ref}
|
||||
className={cn(error && "text-destructive", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormLabel.displayName = "FormLabel"
|
||||
|
||||
const FormControl = React.forwardRef<
|
||||
React.ElementRef<typeof Slot>,
|
||||
React.ComponentPropsWithoutRef<typeof Slot>
|
||||
React.ElementRef<typeof Slot>,
|
||||
React.ComponentPropsWithoutRef<typeof Slot>
|
||||
>(({ ...props }, ref) => {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||
|
||||
return (
|
||||
<Slot
|
||||
ref={ref}
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error
|
||||
? `${formDescriptionId}`
|
||||
: `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<Slot
|
||||
ref={ref}
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormControl.displayName = "FormControl"
|
||||
|
||||
const FormDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { formDescriptionId } = useFormField()
|
||||
const { formDescriptionId } = useFormField()
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formDescriptionId}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formDescriptionId}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
FormDescription.displayName = "FormDescription"
|
||||
|
||||
const FormMessage = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { error, formMessageId } = useFormField()
|
||||
const body = error ? String(error?.message) : children
|
||||
const { error, formMessageId } = useFormField()
|
||||
const body = error ? String(error?.message) : children
|
||||
|
||||
if (!body) {
|
||||
return null
|
||||
}
|
||||
if (!body) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formMessageId}
|
||||
className={cn("text-sm font-medium text-destructive", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
)
|
||||
return (
|
||||
<p
|
||||
ref={ref}
|
||||
id={formMessageId}
|
||||
className={cn("text-sm font-medium text-destructive", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
)
|
||||
})
|
||||
FormMessage.displayName = "FormMessage"
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
}
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
import * as React from "react"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||
import { type VariantProps, cva } from "class-variance-authority"
|
||||
import * as React from "react"
|
||||
|
||||
const labelVariants = cva(
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70",
|
||||
)
|
||||
|
||||
const Label = React.forwardRef<
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||
VariantProps<typeof labelVariants>
|
||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<LabelPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(labelVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
<LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
|
||||
))
|
||||
Label.displayName = LabelPrimitive.Root.displayName
|
||||
|
||||
|
||||
@@ -1,128 +1,122 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
||||
import { cva } from "class-variance-authority"
|
||||
import { ChevronDown } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as React from "react"
|
||||
|
||||
const NavigationMenu = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-10 flex max-w-max flex-1 items-center justify-center",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<NavigationMenuViewport />
|
||||
</NavigationMenuPrimitive.Root>
|
||||
<NavigationMenuPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative z-10 flex max-w-max flex-1 items-center justify-center", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<NavigationMenuViewport />
|
||||
</NavigationMenuPrimitive.Root>
|
||||
))
|
||||
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
|
||||
|
||||
const NavigationMenuList = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group flex flex-1 list-none items-center justify-center space-x-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<NavigationMenuPrimitive.List
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"group flex flex-1 list-none items-center justify-center space-x-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
|
||||
|
||||
const NavigationMenuItem = NavigationMenuPrimitive.Item
|
||||
|
||||
const navigationMenuTriggerStyle = cva(
|
||||
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
|
||||
"group inline-flex h-10 w-max items-center justify-center rounded-md px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50",
|
||||
)
|
||||
|
||||
const NavigationMenuTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}{" "}
|
||||
<ChevronDown
|
||||
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
<NavigationMenuPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||
{...props}
|
||||
>
|
||||
{children}{" "}
|
||||
<ChevronDown
|
||||
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</NavigationMenuPrimitive.Trigger>
|
||||
))
|
||||
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
|
||||
|
||||
const NavigationMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<NavigationMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
|
||||
|
||||
const NavigationMenuLink = NavigationMenuPrimitive.Link
|
||||
|
||||
const NavigationMenuViewport = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
className={cn(
|
||||
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
className={cn(
|
||||
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
NavigationMenuViewport.displayName =
|
||||
NavigationMenuPrimitive.Viewport.displayName
|
||||
NavigationMenuViewport.displayName = NavigationMenuPrimitive.Viewport.displayName
|
||||
|
||||
const NavigationMenuIndicator = React.forwardRef<
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
||||
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
||||
</NavigationMenuPrimitive.Indicator>
|
||||
<NavigationMenuPrimitive.Indicator
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
||||
</NavigationMenuPrimitive.Indicator>
|
||||
))
|
||||
NavigationMenuIndicator.displayName =
|
||||
NavigationMenuPrimitive.Indicator.displayName
|
||||
NavigationMenuIndicator.displayName = NavigationMenuPrimitive.Indicator.displayName
|
||||
|
||||
export {
|
||||
navigationMenuTriggerStyle,
|
||||
NavigationMenu,
|
||||
NavigationMenuList,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuIndicator,
|
||||
NavigationMenuViewport,
|
||||
navigationMenuTriggerStyle,
|
||||
NavigationMenu,
|
||||
NavigationMenuList,
|
||||
NavigationMenuItem,
|
||||
NavigationMenuContent,
|
||||
NavigationMenuTrigger,
|
||||
NavigationMenuLink,
|
||||
NavigationMenuIndicator,
|
||||
NavigationMenuViewport,
|
||||
}
|
||||
|
||||
@@ -1,28 +1,27 @@
|
||||
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"
|
||||
|
||||
const Popover = PopoverPrimitive.Root
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"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
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"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,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
))
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||
|
||||
|
||||
@@ -1,45 +1,42 @@
|
||||
import * as React from "react"
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
|
||||
import * as React from "react"
|
||||
|
||||
const ScrollArea = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
<ScrollAreaPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn("relative overflow-hidden", className)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
|
||||
{children}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollBar />
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
))
|
||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
||||
|
||||
const ScrollBar = React.forwardRef<
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" &&
|
||||
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||
ref={ref}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
"flex touch-none select-none transition-colors",
|
||||
orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||
orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
|
||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||
))
|
||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import * as React from "react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
import { Check, ChevronDown, ChevronUp } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as React from "react"
|
||||
|
||||
const Select = SelectPrimitive.Root
|
||||
|
||||
@@ -11,148 +10,141 @@ const SelectGroup = SelectPrimitive.Group
|
||||
const SelectValue = SelectPrimitive.Value
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUp className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
))
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn("flex cursor-default items-center justify-center py-1", className)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
))
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName
|
||||
SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover 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",
|
||||
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
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover 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",
|
||||
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,
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
))
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
}
|
||||
|
||||
@@ -1,29 +1,23 @@
|
||||
import * as React from "react"
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||
import * as React from "react"
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(
|
||||
(
|
||||
{ className, orientation = "horizontal", decorative = true, ...props },
|
||||
ref
|
||||
) => (
|
||||
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}
|
||||
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 }
|
||||
|
||||
@@ -1,15 +1,7 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Skeleton({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn("animate-pulse rounded-md bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return <div className={cn("animate-pulse rounded-md bg-muted", className)} {...props} />
|
||||
}
|
||||
|
||||
export { Skeleton }
|
||||
|
||||
@@ -4,26 +4,24 @@ import { Toaster as Sonner } from "sonner"
|
||||
type ToasterProps = React.ComponentProps<typeof Sonner>
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme()
|
||||
const { theme = "system" } = useTheme()
|
||||
|
||||
return (
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
toastOptions={{
|
||||
classNames: {
|
||||
toast:
|
||||
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||
description: "group-[.toast]:text-muted-foreground",
|
||||
actionButton:
|
||||
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||
cancelButton:
|
||||
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
return (
|
||||
<Sonner
|
||||
theme={theme as ToasterProps["theme"]}
|
||||
className="toaster group"
|
||||
toastOptions={{
|
||||
classNames: {
|
||||
toast: "group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||
description: "group-[.toast]:text-muted-foreground",
|
||||
actionButton:
|
||||
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||
cancelButton: "group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
||||
},
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Toaster }
|
||||
|
||||
@@ -1,117 +1,94 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
)
|
||||
Table.displayName = "Table"
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
))
|
||||
TableHeader.displayName = "TableHeader"
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
<tbody ref={ref} className={cn("[&_tr:last-child]:border-0", className)} {...props} />
|
||||
))
|
||||
TableBody.displayName = "TableBody"
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn("border-t bg-muted/50 font-medium [&>tr]:last:border-b-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableFooter.displayName = "TableFooter"
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
)
|
||||
TableRow.displayName = "TableRow"
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableHead.displayName = "TableHead"
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn("p-4 align-middle [&:has([role=checkbox])]:pr-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
TableCell.displayName = "TableCell"
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
<caption ref={ref} className={cn("mt-4 text-sm text-muted-foreground", className)} {...props} />
|
||||
))
|
||||
TableCaption.displayName = "TableCaption"
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
}
|
||||
export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption }
|
||||
|
||||
@@ -1,52 +1,51 @@
|
||||
import * as React from "react"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as TabsPrimitive from "@radix-ui/react-tabs"
|
||||
import * as React from "react"
|
||||
|
||||
const Tabs = TabsPrimitive.Root
|
||||
|
||||
const TabsList = React.forwardRef<
|
||||
React.ElementRef<typeof TabsPrimitive.List>,
|
||||
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
|
||||
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}
|
||||
/>
|
||||
<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>
|
||||
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}
|
||||
/>
|
||||
<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>
|
||||
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}
|
||||
/>
|
||||
<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
|
||||
|
||||
|
||||
@@ -1,22 +1,20 @@
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Textarea = React.forwardRef<
|
||||
HTMLTextAreaElement,
|
||||
React.ComponentProps<"textarea">
|
||||
>(({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, React.ComponentProps<"textarea">>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
},
|
||||
)
|
||||
Textarea.displayName = "Textarea"
|
||||
|
||||
export { Textarea }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createUser } from "@/api/user"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -18,29 +18,28 @@ import {
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from "@/components/ui/form"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { z } from "zod"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { ModelUser } from "@/types"
|
||||
import { useState } from "react"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { IconButton } from "@/components/xui/icon-button"
|
||||
import { createUser } from "@/api/user"
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ModelUser } from "@/types"
|
||||
import { zodResolver } from "@hookform/resolvers/zod"
|
||||
import { useState } from "react"
|
||||
import { useForm } from "react-hook-form"
|
||||
import { useTranslation } from "react-i18next"
|
||||
import { KeyedMutator } from "swr"
|
||||
import { z } from "zod"
|
||||
|
||||
interface UserCardProps {
|
||||
mutate: KeyedMutator<ModelUser[]>;
|
||||
mutate: KeyedMutator<ModelUser[]>
|
||||
}
|
||||
|
||||
const userFormSchema = z.object({
|
||||
username: z.string().min(1),
|
||||
password: z.string().min(8).max(72),
|
||||
});
|
||||
})
|
||||
|
||||
export const UserCard: React.FC<UserCardProps> = ({ mutate }) => {
|
||||
const { t } = useTranslation();
|
||||
const { t } = useTranslation()
|
||||
const form = useForm<z.infer<typeof userFormSchema>>({
|
||||
resolver: zodResolver(userFormSchema),
|
||||
defaultValues: {
|
||||
@@ -49,16 +48,16 @@ export const UserCard: React.FC<UserCardProps> = ({ mutate }) => {
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
const onSubmit = async (values: z.infer<typeof userFormSchema>) => {
|
||||
await createUser(values);
|
||||
setOpen(false);
|
||||
await mutate();
|
||||
form.reset();
|
||||
await createUser(values)
|
||||
setOpen(false)
|
||||
await mutate()
|
||||
form.reset()
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -82,9 +81,7 @@ export const UserCard: React.FC<UserCardProps> = ({ mutate }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("Username")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
/>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -97,9 +94,7 @@ export const UserCard: React.FC<UserCardProps> = ({ mutate }) => {
|
||||
<FormItem>
|
||||
<FormLabel>{t("Password")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
/>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
@@ -111,7 +106,9 @@ export const UserCard: React.FC<UserCardProps> = ({ mutate }) => {
|
||||
{t("Close")}
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit" className="my-2">{t("Confirm")}</Button>
|
||||
<Button type="submit" className="my-2">
|
||||
{t("Confirm")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -1,102 +1,115 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbEllipsis,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
Breadcrumb,
|
||||
BreadcrumbEllipsis,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from "@/components/ui/breadcrumb"
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu"
|
||||
import { formatPath } from "@/lib/utils"
|
||||
import * as React from "react"
|
||||
|
||||
const ITEMS_TO_DISPLAY = 3
|
||||
|
||||
interface FilepathProps {
|
||||
path: string;
|
||||
setPath: React.Dispatch<React.SetStateAction<string>>;
|
||||
path: string
|
||||
setPath: React.Dispatch<React.SetStateAction<string>>
|
||||
}
|
||||
|
||||
function pathToItems(path: string) {
|
||||
const segments = path.split('/').filter(Boolean);
|
||||
const segments = path.split("/").filter(Boolean)
|
||||
|
||||
const result: { href: string; label: string; }[] = [];
|
||||
const result: { href: string; label: string }[] = []
|
||||
|
||||
let currentPath = '';
|
||||
segments.forEach(segment => {
|
||||
currentPath += `/${segment}`;
|
||||
result.push({ href: currentPath, label: segment });
|
||||
});
|
||||
let currentPath = ""
|
||||
segments.forEach((segment) => {
|
||||
currentPath += `/${segment}`
|
||||
result.push({ href: currentPath, label: segment })
|
||||
})
|
||||
|
||||
return result;
|
||||
return result
|
||||
}
|
||||
|
||||
export const Filepath: React.FC<FilepathProps> = ({ path, setPath }) => {
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const items = pathToItems(formatPath(path));
|
||||
const [open, setOpen] = React.useState(false)
|
||||
const items = pathToItems(formatPath(path))
|
||||
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<p className="cursor-pointer hover:text-white transition" onClick={() => { setPath('/') }}>{'/'}</p>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
{items.length > ITEMS_TO_DISPLAY ? (
|
||||
<>
|
||||
<BreadcrumbItem>
|
||||
{
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger
|
||||
className="flex items-center gap-1"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<BreadcrumbEllipsis className="h-4 w-4" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
{items.slice(0, -ITEMS_TO_DISPLAY).map((item, index) => (
|
||||
<DropdownMenuItem key={index}>
|
||||
<p onClick={() => { setPath(item.href) }}>
|
||||
{item.label}
|
||||
</p>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
}
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
</>
|
||||
) : null}
|
||||
{items.slice(-ITEMS_TO_DISPLAY).map((item, index, slicedItems) => (
|
||||
<React.Fragment key={index}>
|
||||
<BreadcrumbItem className="overflow-auto">
|
||||
{item.href ? (
|
||||
<>
|
||||
<p
|
||||
className="max-w-20 truncate md:max-w-none cursor-pointer hover:text-white transition"
|
||||
onClick={() => { setPath(item.href) }}
|
||||
>
|
||||
{item.label}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<BreadcrumbPage className="max-w-20 truncate md:max-w-none">
|
||||
{item.label}
|
||||
</BreadcrumbPage>
|
||||
)}
|
||||
</BreadcrumbItem>
|
||||
{index !== slicedItems.length - 1 ? <BreadcrumbSeparator /> : null}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
)
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<p
|
||||
className="cursor-pointer hover:text-white transition"
|
||||
onClick={() => {
|
||||
setPath("/")
|
||||
}}
|
||||
>
|
||||
{"/"}
|
||||
</p>
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
{items.length > ITEMS_TO_DISPLAY ? (
|
||||
<>
|
||||
<BreadcrumbItem>
|
||||
{
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger
|
||||
className="flex items-center gap-1"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
<BreadcrumbEllipsis className="h-4 w-4" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
{items.slice(0, -ITEMS_TO_DISPLAY).map((item, index) => (
|
||||
<DropdownMenuItem key={index}>
|
||||
<p
|
||||
onClick={() => {
|
||||
setPath(item.href)
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</p>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
}
|
||||
</BreadcrumbItem>
|
||||
<BreadcrumbSeparator />
|
||||
</>
|
||||
) : null}
|
||||
{items.slice(-ITEMS_TO_DISPLAY).map((item, index, slicedItems) => (
|
||||
<React.Fragment key={index}>
|
||||
<BreadcrumbItem className="overflow-auto">
|
||||
{item.href ? (
|
||||
<>
|
||||
<p
|
||||
className="max-w-20 truncate md:max-w-none cursor-pointer hover:text-white transition"
|
||||
onClick={() => {
|
||||
setPath(item.href)
|
||||
}}
|
||||
>
|
||||
{item.label}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<BreadcrumbPage className="max-w-20 truncate md:max-w-none">
|
||||
{item.label}
|
||||
</BreadcrumbPage>
|
||||
)}
|
||||
</BreadcrumbItem>
|
||||
{index !== slicedItems.length - 1 ? <BreadcrumbSeparator /> : null}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,79 +1,84 @@
|
||||
import { Button, ButtonProps } from "@/components/ui/button"
|
||||
import {
|
||||
Plus,
|
||||
Edit2,
|
||||
Trash2,
|
||||
Terminal,
|
||||
Check,
|
||||
CircleArrowUp,
|
||||
Clipboard,
|
||||
Check,
|
||||
FolderClosed,
|
||||
Play,
|
||||
Download,
|
||||
Upload,
|
||||
Edit2,
|
||||
FolderClosed,
|
||||
Menu,
|
||||
Play,
|
||||
Plus,
|
||||
Terminal,
|
||||
Trash2,
|
||||
Upload,
|
||||
} from "lucide-react"
|
||||
import { Button, ButtonProps } from "@/components/ui/button"
|
||||
import { forwardRef } from "react";
|
||||
import { forwardRef } from "react"
|
||||
|
||||
export interface IconButtonProps extends ButtonProps {
|
||||
icon:
|
||||
"clipboard" |
|
||||
"check" |
|
||||
"edit" |
|
||||
"trash" |
|
||||
"plus" |
|
||||
"terminal" |
|
||||
"update" |
|
||||
"folder-closed" |
|
||||
"play" |
|
||||
"download" |
|
||||
"upload" |
|
||||
"menu";
|
||||
| "clipboard"
|
||||
| "check"
|
||||
| "edit"
|
||||
| "trash"
|
||||
| "plus"
|
||||
| "terminal"
|
||||
| "update"
|
||||
| "folder-closed"
|
||||
| "play"
|
||||
| "download"
|
||||
| "upload"
|
||||
| "menu"
|
||||
}
|
||||
|
||||
export const IconButton = forwardRef<HTMLButtonElement, IconButtonProps>((props, ref) => {
|
||||
return (
|
||||
<Button {...props} ref={ref} size="icon">
|
||||
<Button
|
||||
className="rounded-lg shadow-[inset_0_1px_0_rgba(255,255,255,0.2)]"
|
||||
{...props}
|
||||
ref={ref}
|
||||
size="icon"
|
||||
>
|
||||
{(() => {
|
||||
switch (props.icon) {
|
||||
case "clipboard": {
|
||||
return <Clipboard />;
|
||||
return <Clipboard />
|
||||
}
|
||||
case "check": {
|
||||
return <Check />;
|
||||
return <Check />
|
||||
}
|
||||
case "edit": {
|
||||
return <Edit2 />;
|
||||
return <Edit2 />
|
||||
}
|
||||
case "trash": {
|
||||
return <Trash2 />;
|
||||
return <Trash2 />
|
||||
}
|
||||
case "plus": {
|
||||
return <Plus />;
|
||||
return <Plus />
|
||||
}
|
||||
case "terminal": {
|
||||
return <Terminal />;
|
||||
return <Terminal />
|
||||
}
|
||||
case "update": {
|
||||
return <CircleArrowUp />;
|
||||
return <CircleArrowUp />
|
||||
}
|
||||
case "folder-closed": {
|
||||
return <FolderClosed />;
|
||||
return <FolderClosed />
|
||||
}
|
||||
case "play": {
|
||||
return <Play />;
|
||||
return <Play />
|
||||
}
|
||||
case "download": {
|
||||
return <Download />;
|
||||
return <Download />
|
||||
}
|
||||
case "upload": {
|
||||
return <Upload />;
|
||||
return <Upload />
|
||||
}
|
||||
case "menu": {
|
||||
return <Menu />;
|
||||
return <Menu />
|
||||
}
|
||||
}
|
||||
})()}
|
||||
</Button>
|
||||
);
|
||||
)
|
||||
})
|
||||
|
||||
@@ -22,392 +22,367 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
CheckIcon,
|
||||
ChevronDown,
|
||||
XIcon,
|
||||
WandSparkles,
|
||||
} from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
import {
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from "@/components/ui/command";
|
||||
Command,
|
||||
CommandEmpty,
|
||||
CommandGroup,
|
||||
CommandInput,
|
||||
CommandItem,
|
||||
CommandList,
|
||||
CommandSeparator,
|
||||
} from "@/components/ui/command"
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
|
||||
import { Separator } from "@/components/ui/separator"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { type VariantProps, cva } from "class-variance-authority"
|
||||
import { CheckIcon, ChevronDown, WandSparkles, XIcon } from "lucide-react"
|
||||
import * as React from "react"
|
||||
|
||||
/**
|
||||
* Variants for the multi-select component to handle different styles.
|
||||
* Uses class-variance-authority (cva) to define different styles based on "variant" prop.
|
||||
*/
|
||||
const multiSelectVariants = cva(
|
||||
"m-1 transition ease-in-out delay-150 hover:-translate-y-1 duration-300",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-foreground/10 text-foreground bg-card hover:bg-card/80",
|
||||
secondary:
|
||||
"border-foreground/10 bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
inverted: "inverted",
|
||||
},
|
||||
"m-1 transition ease-in-out delay-150 hover:-translate-y-1 duration-300",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: "border-foreground/10 text-foreground bg-card hover:bg-card/80",
|
||||
secondary:
|
||||
"border-foreground/10 bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
|
||||
inverted: "inverted",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
);
|
||||
)
|
||||
|
||||
/**
|
||||
* Props for MultiSelect component
|
||||
*/
|
||||
interface MultiSelectProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof multiSelectVariants> {
|
||||
/**
|
||||
* An array of option objects to be displayed in the multi-select component.
|
||||
* Each option object has a label, value, and an optional icon.
|
||||
*/
|
||||
options: {
|
||||
/** The text to display for the option. */
|
||||
label: string;
|
||||
/** The unique value associated with the option. */
|
||||
value: string;
|
||||
/** Optional icon component to display alongside the option. */
|
||||
icon?: React.ComponentType<{ className?: string }>;
|
||||
}[];
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof multiSelectVariants> {
|
||||
/**
|
||||
* An array of option objects to be displayed in the multi-select component.
|
||||
* Each option object has a label, value, and an optional icon.
|
||||
*/
|
||||
options: {
|
||||
/** The text to display for the option. */
|
||||
label: string
|
||||
/** The unique value associated with the option. */
|
||||
value: string
|
||||
/** Optional icon component to display alongside the option. */
|
||||
icon?: React.ComponentType<{ className?: string }>
|
||||
}[]
|
||||
|
||||
/**
|
||||
* Callback function triggered when the selected values change.
|
||||
* Receives an array of the new selected values.
|
||||
*/
|
||||
onValueChange: (value: string[]) => void;
|
||||
/**
|
||||
* Callback function triggered when the selected values change.
|
||||
* Receives an array of the new selected values.
|
||||
*/
|
||||
onValueChange: (value: string[]) => void
|
||||
|
||||
/** The default selected values when the component mounts. */
|
||||
defaultValue?: string[];
|
||||
/** The default selected values when the component mounts. */
|
||||
defaultValue?: string[]
|
||||
|
||||
/**
|
||||
* Placeholder text to be displayed when no values are selected.
|
||||
* Optional, defaults to "Select options".
|
||||
*/
|
||||
placeholder?: string;
|
||||
/**
|
||||
* Placeholder text to be displayed when no values are selected.
|
||||
* Optional, defaults to "Select options".
|
||||
*/
|
||||
placeholder?: string
|
||||
|
||||
/**
|
||||
* Animation duration in seconds for the visual effects (e.g., bouncing badges).
|
||||
* Optional, defaults to 0 (no animation).
|
||||
*/
|
||||
animation?: number;
|
||||
/**
|
||||
* Animation duration in seconds for the visual effects (e.g., bouncing badges).
|
||||
* Optional, defaults to 0 (no animation).
|
||||
*/
|
||||
animation?: number
|
||||
|
||||
/**
|
||||
* Maximum number of items to display. Extra selected items will be summarized.
|
||||
* Optional, defaults to 3.
|
||||
*/
|
||||
maxCount?: number;
|
||||
/**
|
||||
* Maximum number of items to display. Extra selected items will be summarized.
|
||||
* Optional, defaults to 3.
|
||||
*/
|
||||
maxCount?: number
|
||||
|
||||
/**
|
||||
* The modality of the popover. When set to true, interaction with outside elements
|
||||
* will be disabled and only popover content will be visible to screen readers.
|
||||
* Optional, defaults to false.
|
||||
*/
|
||||
modalPopover?: boolean;
|
||||
/**
|
||||
* The modality of the popover. When set to true, interaction with outside elements
|
||||
* will be disabled and only popover content will be visible to screen readers.
|
||||
* Optional, defaults to false.
|
||||
*/
|
||||
modalPopover?: boolean
|
||||
|
||||
/**
|
||||
* If true, renders the multi-select component as a child of another component.
|
||||
* Optional, defaults to false.
|
||||
*/
|
||||
asChild?: boolean;
|
||||
/**
|
||||
* If true, renders the multi-select component as a child of another component.
|
||||
* Optional, defaults to false.
|
||||
*/
|
||||
asChild?: boolean
|
||||
|
||||
/**
|
||||
* Additional class names to apply custom styles to the multi-select component.
|
||||
* Optional, can be used to add custom styles.
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* Additional class names to apply custom styles to the multi-select component.
|
||||
* Optional, can be used to add custom styles.
|
||||
*/
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const MultiSelect = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
MultiSelectProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
options,
|
||||
onValueChange,
|
||||
variant,
|
||||
defaultValue = [],
|
||||
placeholder = "Select options",
|
||||
animation = 0,
|
||||
maxCount = 3,
|
||||
modalPopover = false,
|
||||
asChild = false,
|
||||
className,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [selectedValues, setSelectedValues] =
|
||||
React.useState<string[]>(defaultValue);
|
||||
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false);
|
||||
const [isAnimating, setIsAnimating] = React.useState(false);
|
||||
|
||||
const handleInputKeyDown = (
|
||||
event: React.KeyboardEvent<HTMLInputElement>
|
||||
export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>(
|
||||
(
|
||||
{
|
||||
options,
|
||||
onValueChange,
|
||||
variant,
|
||||
defaultValue = [],
|
||||
placeholder = "Select options",
|
||||
animation = 0,
|
||||
maxCount = 3,
|
||||
modalPopover = false,
|
||||
asChild = false,
|
||||
className,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
if (event.key === "Enter") {
|
||||
setIsPopoverOpen(true);
|
||||
} else if (event.key === "Backspace" && !event.currentTarget.value) {
|
||||
const newSelectedValues = [...selectedValues];
|
||||
newSelectedValues.pop();
|
||||
setSelectedValues(newSelectedValues);
|
||||
onValueChange(newSelectedValues);
|
||||
}
|
||||
};
|
||||
const [selectedValues, setSelectedValues] = React.useState<string[]>(defaultValue)
|
||||
const [isPopoverOpen, setIsPopoverOpen] = React.useState(false)
|
||||
const [isAnimating, setIsAnimating] = React.useState(false)
|
||||
|
||||
const toggleOption = (option: string) => {
|
||||
const newSelectedValues = selectedValues.includes(option)
|
||||
? selectedValues.filter((value) => value !== option)
|
||||
: [...selectedValues, option];
|
||||
setSelectedValues(newSelectedValues);
|
||||
onValueChange(newSelectedValues);
|
||||
};
|
||||
const handleInputKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === "Enter") {
|
||||
setIsPopoverOpen(true)
|
||||
} else if (event.key === "Backspace" && !event.currentTarget.value) {
|
||||
const newSelectedValues = [...selectedValues]
|
||||
newSelectedValues.pop()
|
||||
setSelectedValues(newSelectedValues)
|
||||
onValueChange(newSelectedValues)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClear = () => {
|
||||
setSelectedValues([]);
|
||||
onValueChange([]);
|
||||
};
|
||||
const toggleOption = (option: string) => {
|
||||
const newSelectedValues = selectedValues.includes(option)
|
||||
? selectedValues.filter((value) => value !== option)
|
||||
: [...selectedValues, option]
|
||||
setSelectedValues(newSelectedValues)
|
||||
onValueChange(newSelectedValues)
|
||||
}
|
||||
|
||||
const handleTogglePopover = () => {
|
||||
setIsPopoverOpen((prev) => !prev);
|
||||
};
|
||||
const handleClear = () => {
|
||||
setSelectedValues([])
|
||||
onValueChange([])
|
||||
}
|
||||
|
||||
const clearExtraOptions = () => {
|
||||
const newSelectedValues = selectedValues.slice(0, maxCount);
|
||||
setSelectedValues(newSelectedValues);
|
||||
onValueChange(newSelectedValues);
|
||||
};
|
||||
const handleTogglePopover = () => {
|
||||
setIsPopoverOpen((prev) => !prev)
|
||||
}
|
||||
|
||||
const toggleAll = () => {
|
||||
if (selectedValues.length === options.length) {
|
||||
handleClear();
|
||||
} else {
|
||||
const allValues = options.map((option) => option.value);
|
||||
setSelectedValues(allValues);
|
||||
onValueChange(allValues);
|
||||
}
|
||||
};
|
||||
const clearExtraOptions = () => {
|
||||
const newSelectedValues = selectedValues.slice(0, maxCount)
|
||||
setSelectedValues(newSelectedValues)
|
||||
onValueChange(newSelectedValues)
|
||||
}
|
||||
|
||||
const stopWheelEventPropagation: React.WheelEventHandler = (e) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
const toggleAll = () => {
|
||||
if (selectedValues.length === options.length) {
|
||||
handleClear()
|
||||
} else {
|
||||
const allValues = options.map((option) => option.value)
|
||||
setSelectedValues(allValues)
|
||||
onValueChange(allValues)
|
||||
}
|
||||
}
|
||||
|
||||
const stopTouchMoveEventPropagation: React.TouchEventHandler = (e) => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
const stopWheelEventPropagation: React.WheelEventHandler = (e) => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover
|
||||
open={isPopoverOpen}
|
||||
onOpenChange={setIsPopoverOpen}
|
||||
modal={modalPopover}
|
||||
>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
ref={ref}
|
||||
{...props}
|
||||
onClick={handleTogglePopover}
|
||||
className={cn(
|
||||
"flex w-full p-1 rounded-md border min-h-10 h-auto items-center justify-between bg-inherit hover:bg-inherit [&_svg]:pointer-events-auto",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{selectedValues.length > 0 ? (
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<div className="flex flex-wrap items-center">
|
||||
{selectedValues.slice(0, maxCount).map((value) => {
|
||||
const option = options.find((o) => o.value === value);
|
||||
const IconComponent = option?.icon;
|
||||
return (
|
||||
<Badge
|
||||
key={value}
|
||||
const stopTouchMoveEventPropagation: React.TouchEventHandler = (e) => {
|
||||
e.stopPropagation()
|
||||
}
|
||||
|
||||
return (
|
||||
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen} modal={modalPopover}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
ref={ref}
|
||||
{...props}
|
||||
onClick={handleTogglePopover}
|
||||
className={cn(
|
||||
isAnimating ? "animate-bounce" : "",
|
||||
multiSelectVariants({ variant })
|
||||
"flex w-full p-1 rounded-md border min-h-10 h-auto items-center justify-between bg-inherit hover:bg-inherit [&_svg]:pointer-events-auto",
|
||||
className,
|
||||
)}
|
||||
style={{ animationDuration: `${animation}s` }}
|
||||
>
|
||||
{IconComponent && (
|
||||
<IconComponent className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
{option?.label}
|
||||
<XIcon
|
||||
className="ml-2 h-2 w-2 cursor-pointer"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
toggleOption(value);
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
);
|
||||
})}
|
||||
{selectedValues.length > maxCount && (
|
||||
<Badge
|
||||
className={cn(
|
||||
"bg-transparent text-foreground border-foreground/1 hover:bg-transparent",
|
||||
isAnimating ? "animate-bounce" : "",
|
||||
multiSelectVariants({ variant })
|
||||
)}
|
||||
style={{ animationDuration: `${animation}s` }}
|
||||
>
|
||||
{`+ ${selectedValues.length - maxCount} more`}
|
||||
<XIcon
|
||||
className="ml-2 h-2 w-2 cursor-pointer"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
clearExtraOptions();
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<XIcon
|
||||
className="h-4 mx-2 cursor-pointer text-muted-foreground"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
handleClear();
|
||||
}}
|
||||
/>
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="flex min-h-6 h-full"
|
||||
/>
|
||||
<ChevronDown className="h-4 mx-2 cursor-pointer text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-between w-full mx-auto">
|
||||
<span className="text-sm text-muted-foreground mx-3">
|
||||
{placeholder}
|
||||
</span>
|
||||
<ChevronDown className="h-4 cursor-pointer text-muted-foreground mx-2" />
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-auto p-0"
|
||||
align="start"
|
||||
onEscapeKeyDown={() => setIsPopoverOpen(false)}
|
||||
onWheel={stopWheelEventPropagation}
|
||||
onTouchMove={stopTouchMoveEventPropagation}
|
||||
>
|
||||
<Command>
|
||||
<CommandInput
|
||||
placeholder="Search..."
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
key="all"
|
||||
onSelect={toggleAll}
|
||||
className="cursor-pointer"
|
||||
{selectedValues.length > 0 ? (
|
||||
<div className="flex justify-between items-center w-full">
|
||||
<div className="flex flex-wrap items-center">
|
||||
{selectedValues.slice(0, maxCount).map((value) => {
|
||||
const option = options.find((o) => o.value === value)
|
||||
const IconComponent = option?.icon
|
||||
return (
|
||||
<Badge
|
||||
key={value}
|
||||
className={cn(
|
||||
isAnimating ? "animate-bounce" : "",
|
||||
multiSelectVariants({ variant }),
|
||||
)}
|
||||
style={{ animationDuration: `${animation}s` }}
|
||||
>
|
||||
{IconComponent && (
|
||||
<IconComponent className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
{option?.label}
|
||||
<XIcon
|
||||
className="ml-2 h-2 w-2 cursor-pointer"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
toggleOption(value)
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
)
|
||||
})}
|
||||
{selectedValues.length > maxCount && (
|
||||
<Badge
|
||||
className={cn(
|
||||
"bg-transparent text-foreground border-foreground/1 hover:bg-transparent",
|
||||
isAnimating ? "animate-bounce" : "",
|
||||
multiSelectVariants({ variant }),
|
||||
)}
|
||||
style={{ animationDuration: `${animation}s` }}
|
||||
>
|
||||
{`+ ${selectedValues.length - maxCount} more`}
|
||||
<XIcon
|
||||
className="ml-2 h-2 w-2 cursor-pointer"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
clearExtraOptions()
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<XIcon
|
||||
className="h-4 mx-2 cursor-pointer text-muted-foreground"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
handleClear()
|
||||
}}
|
||||
/>
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="flex min-h-6 h-full"
|
||||
/>
|
||||
<ChevronDown className="h-4 mx-2 cursor-pointer text-muted-foreground" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center justify-between w-full mx-auto">
|
||||
<span className="text-sm text-muted-foreground mx-3">
|
||||
{placeholder}
|
||||
</span>
|
||||
<ChevronDown className="h-4 cursor-pointer text-muted-foreground mx-2" />
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="w-auto p-0"
|
||||
align="start"
|
||||
onEscapeKeyDown={() => setIsPopoverOpen(false)}
|
||||
onWheel={stopWheelEventPropagation}
|
||||
onTouchMove={stopTouchMoveEventPropagation}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||
selectedValues.length === options.length
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "opacity-50 [&_svg]:invisible"
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</div>
|
||||
<span>(Select All)</span>
|
||||
</CommandItem>
|
||||
{options.map((option) => {
|
||||
const isSelected = selectedValues.includes(option.value);
|
||||
return (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
onSelect={() => toggleOption(option.value)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div
|
||||
<Command>
|
||||
<CommandInput placeholder="Search..." onKeyDown={handleInputKeyDown} />
|
||||
<CommandList>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<CommandGroup>
|
||||
<CommandItem
|
||||
key="all"
|
||||
onSelect={toggleAll}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||
selectedValues.length === options.length
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "opacity-50 [&_svg]:invisible",
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</div>
|
||||
<span>(Select All)</span>
|
||||
</CommandItem>
|
||||
{options.map((option) => {
|
||||
const isSelected = selectedValues.includes(option.value)
|
||||
return (
|
||||
<CommandItem
|
||||
key={option.value}
|
||||
onSelect={() => toggleOption(option.value)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||
isSelected
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "opacity-50 [&_svg]:invisible",
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</div>
|
||||
{option.icon && (
|
||||
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
<span>{option.label}</span>
|
||||
</CommandItem>
|
||||
)
|
||||
})}
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup>
|
||||
<div className="flex items-center justify-between">
|
||||
{selectedValues.length > 0 && (
|
||||
<>
|
||||
<CommandItem
|
||||
onSelect={handleClear}
|
||||
className="flex-1 justify-center cursor-pointer"
|
||||
>
|
||||
Clear
|
||||
</CommandItem>
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="flex min-h-6 h-full"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<CommandItem
|
||||
onSelect={() => setIsPopoverOpen(false)}
|
||||
className="flex-1 justify-center cursor-pointer max-w-full"
|
||||
>
|
||||
Close
|
||||
</CommandItem>
|
||||
</div>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
{animation > 0 && selectedValues.length > 0 && (
|
||||
<WandSparkles
|
||||
className={cn(
|
||||
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||
isSelected
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "opacity-50 [&_svg]:invisible"
|
||||
"cursor-pointer my-2 text-foreground bg-background w-3 h-3",
|
||||
isAnimating ? "" : "text-muted-foreground",
|
||||
)}
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</div>
|
||||
{option.icon && (
|
||||
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||
)}
|
||||
<span>{option.label}</span>
|
||||
</CommandItem>
|
||||
);
|
||||
})}
|
||||
</CommandGroup>
|
||||
<CommandSeparator />
|
||||
<CommandGroup>
|
||||
<div className="flex items-center justify-between">
|
||||
{selectedValues.length > 0 && (
|
||||
<>
|
||||
<CommandItem
|
||||
onSelect={handleClear}
|
||||
className="flex-1 justify-center cursor-pointer"
|
||||
>
|
||||
Clear
|
||||
</CommandItem>
|
||||
<Separator
|
||||
orientation="vertical"
|
||||
className="flex min-h-6 h-full"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<CommandItem
|
||||
onSelect={() => setIsPopoverOpen(false)}
|
||||
className="flex-1 justify-center cursor-pointer max-w-full"
|
||||
>
|
||||
Close
|
||||
</CommandItem>
|
||||
</div>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
</Command>
|
||||
</PopoverContent>
|
||||
{animation > 0 && selectedValues.length > 0 && (
|
||||
<WandSparkles
|
||||
className={cn(
|
||||
"cursor-pointer my-2 text-foreground bg-background w-3 h-3",
|
||||
isAnimating ? "" : "text-muted-foreground"
|
||||
)}
|
||||
onClick={() => setIsAnimating(!isAnimating)}
|
||||
/>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
);
|
||||
onClick={() => setIsAnimating(!isAnimating)}
|
||||
/>
|
||||
)}
|
||||
</Popover>
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
MultiSelect.displayName = "MultiSelect";
|
||||
MultiSelect.displayName = "MultiSelect"
|
||||
|
||||
@@ -1,10 +1,48 @@
|
||||
import { NavigationMenuLinkProps, NavigationMenuTriggerProps } from "@radix-ui/react-navigation-menu"
|
||||
import { NavigationMenuLink, NavigationMenuTrigger, navigationMenuTriggerStyle } from "../ui/navigation-menu"
|
||||
import {
|
||||
NavigationMenuLinkProps,
|
||||
NavigationMenuTriggerProps,
|
||||
} from "@radix-ui/react-navigation-menu"
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
export const NzNavigationMenuLink = (props: NavigationMenuLinkProps & React.RefAttributes<HTMLAnchorElement>) => {
|
||||
return <NavigationMenuLink {...props} className={navigationMenuTriggerStyle() + " hover:bg-inherit data-[active]:bg-inherit transition-colors text-foreground/60 data-[active]:text-foreground hover:text-foreground/90"} />
|
||||
import {
|
||||
NavigationMenuLink,
|
||||
NavigationMenuTrigger,
|
||||
navigationMenuTriggerStyle,
|
||||
} from "../ui/navigation-menu"
|
||||
|
||||
export const NzNavigationMenuLink = (
|
||||
props: NavigationMenuLinkProps & React.RefAttributes<HTMLAnchorElement>,
|
||||
) => {
|
||||
return (
|
||||
<div className="relative">
|
||||
<NavigationMenuLink
|
||||
{...props}
|
||||
className={
|
||||
navigationMenuTriggerStyle() +
|
||||
" hover:bg-inherit data-[active]:bg-inherit transition-colors text-foreground/60 data-[active]:text-foreground hover:text-foreground/90"
|
||||
}
|
||||
/>
|
||||
{props.active && (
|
||||
<motion.div
|
||||
layoutId="tab-underline"
|
||||
className="absolute bottom-0 left-0 right-0 h-[2px] bg-black dark:bg-white"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const NzNavigationMenuTrigger = (props: Omit<NavigationMenuTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> & React.RefAttributes<HTMLButtonElement>) => {
|
||||
return <NavigationMenuTrigger {...props} className={navigationMenuTriggerStyle() + " hover:bg-inherit data-[active]:bg-inherit transition-colors text-foreground/60 data-[active]:text-foreground hover:text-foreground/90"} />
|
||||
export const NzNavigationMenuTrigger = (
|
||||
props: Omit<NavigationMenuTriggerProps & React.RefAttributes<HTMLButtonElement>, "ref"> &
|
||||
React.RefAttributes<HTMLButtonElement>,
|
||||
) => {
|
||||
return (
|
||||
<NavigationMenuTrigger
|
||||
{...props}
|
||||
className={
|
||||
navigationMenuTriggerStyle() +
|
||||
" hover:bg-inherit data-[active]:bg-inherit transition-colors text-foreground/60 data-[active]:text-foreground hover:text-foreground/90"
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import * as React from "react"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { X } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import * as SheetPrimitive from "@radix-ui/react-dialog"
|
||||
import { type VariantProps, cva } from "class-variance-authority"
|
||||
import { X } from "lucide-react"
|
||||
import * as React from "react"
|
||||
|
||||
const Sheet = SheetPrimitive.Root
|
||||
|
||||
@@ -14,108 +13,98 @@ const SheetClose = SheetPrimitive.Close
|
||||
const SheetPortal = SheetPrimitive.Portal
|
||||
|
||||
const sheetVariants = cva(
|
||||
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
||||
bottom:
|
||||
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
||||
right:
|
||||
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||
},
|
||||
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
|
||||
{
|
||||
variants: {
|
||||
side: {
|
||||
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
|
||||
bottom: "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
|
||||
right: "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
side: "right",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
side: "right",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
interface SheetContentProps
|
||||
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||
VariantProps<typeof sheetVariants> { setOpen: React.Dispatch<React.SetStateAction<boolean>> }
|
||||
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
||||
VariantProps<typeof sheetVariants> {
|
||||
setOpen: React.Dispatch<React.SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
const SheetContent = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||
SheetContentProps
|
||||
React.ElementRef<typeof SheetPrimitive.Content>,
|
||||
SheetContentProps
|
||||
>(({ side = "right", className, children, setOpen, ...props }, ref) => (
|
||||
<SheetPortal>
|
||||
<SheetPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(sheetVariants({ side }), className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||
<X className="h-4 w-4" onClick={() => { setOpen(false) }} />
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
<SheetPortal>
|
||||
<SheetPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(sheetVariants({ side }), className)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
|
||||
<X
|
||||
className="h-4 w-4"
|
||||
onClick={() => {
|
||||
setOpen(false)
|
||||
}}
|
||||
/>
|
||||
<span className="sr-only">Close</span>
|
||||
</SheetPrimitive.Close>
|
||||
</SheetPrimitive.Content>
|
||||
</SheetPortal>
|
||||
))
|
||||
SheetContent.displayName = SheetPrimitive.Content.displayName
|
||||
|
||||
const SheetHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-2 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
|
||||
)
|
||||
SheetHeader.displayName = "SheetHeader"
|
||||
|
||||
const SheetFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
SheetFooter.displayName = "SheetFooter"
|
||||
|
||||
const SheetTitle = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||
React.ElementRef<typeof SheetPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
<SheetPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold text-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetTitle.displayName = SheetPrimitive.Title.displayName
|
||||
|
||||
const SheetDescription = React.forwardRef<
|
||||
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||
React.ElementRef<typeof SheetPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SheetPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
<SheetPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SheetDescription.displayName = SheetPrimitive.Description.displayName
|
||||
|
||||
export {
|
||||
Sheet,
|
||||
SheetPortal,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
Sheet,
|
||||
SheetPortal,
|
||||
SheetTrigger,
|
||||
SheetClose,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetFooter,
|
||||
SheetTitle,
|
||||
SheetDescription,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
"use client";
|
||||
"use client"
|
||||
|
||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||
import { TableCell, TableHead, TableRow } from "@/components/ui/table"
|
||||
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
ColumnDef,
|
||||
Row,
|
||||
@@ -9,36 +13,26 @@ import {
|
||||
getCoreRowModel,
|
||||
getSortedRowModel,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
|
||||
import { TableCell, TableHead, TableRow } from "@/components/ui/table";
|
||||
import { HTMLAttributes, forwardRef, useState, useRef, useEffect } from "react";
|
||||
import { TableVirtuoso } from "react-virtuoso";
|
||||
import { cn } from "@/lib/utils"
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { useMediaQuery } from "@/hooks/useMediaQuery";
|
||||
} from "@tanstack/react-table"
|
||||
import { HTMLAttributes, forwardRef, useEffect, useRef, useState } from "react"
|
||||
import { TableVirtuoso } from "react-virtuoso"
|
||||
|
||||
// Original Table is wrapped with a <div> (see https://ui.shadcn.com/docs/components/table#radix-:r24:-content-manual),
|
||||
// but here we don't want it, so let's use a new component with only <table> tag
|
||||
const TableComponent = forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableComponent.displayName = "TableComponent";
|
||||
const TableComponent = forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
|
||||
({ className, ...props }, ref) => (
|
||||
<table ref={ref} className={cn("w-full caption-bottom text-sm", className)} {...props} />
|
||||
),
|
||||
)
|
||||
TableComponent.displayName = "TableComponent"
|
||||
|
||||
const TableRowComponent = <TData,>(rows: Row<TData>[]) =>
|
||||
function getTableRow(props: HTMLAttributes<HTMLTableRowElement>) {
|
||||
// @ts-expect-error data-index is a valid attribute
|
||||
const index = props["data-index"];
|
||||
const row = rows[index];
|
||||
const index = props["data-index"]
|
||||
const row = rows[index]
|
||||
|
||||
if (!row) return null;
|
||||
if (!row) return null
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
@@ -53,11 +47,11 @@ const TableRowComponent = <TData,>(rows: Row<TData>[]) =>
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
function SortingIndicator({ isSorted }: { isSorted: SortDirection | false }) {
|
||||
if (!isSorted) return null;
|
||||
if (!isSorted) return null
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
@@ -67,13 +61,15 @@ function SortingIndicator({ isSorted }: { isSorted: SortDirection | false }) {
|
||||
}[isSorted]
|
||||
}
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
interface DataTableProps<TData, TValue> {
|
||||
columns: ColumnDef<TData, TValue>[];
|
||||
data: TData[];
|
||||
rowComponent?: (rows: Row<TData>[]) => (props: HTMLAttributes<HTMLTableRowElement>) => JSX.Element | null,
|
||||
columns: ColumnDef<TData, TValue>[]
|
||||
data: TData[]
|
||||
rowComponent?: (
|
||||
rows: Row<TData>[],
|
||||
) => (props: HTMLAttributes<HTMLTableRowElement>) => JSX.Element | null
|
||||
}
|
||||
|
||||
export function DataTable<TData, TValue>({
|
||||
@@ -81,10 +77,12 @@ export function DataTable<TData, TValue>({
|
||||
data,
|
||||
rowComponent,
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const [sorting, setSorting] = useState<SortingState>([{
|
||||
id: 'type',
|
||||
desc: true,
|
||||
}]);
|
||||
const [sorting, setSorting] = useState<SortingState>([
|
||||
{
|
||||
id: "type",
|
||||
desc: true,
|
||||
},
|
||||
])
|
||||
const table = useReactTable({
|
||||
data,
|
||||
columns,
|
||||
@@ -94,41 +92,43 @@ export function DataTable<TData, TValue>({
|
||||
onSortingChange: setSorting,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getSortedRowModel: getSortedRowModel(),
|
||||
});
|
||||
})
|
||||
|
||||
const { rows } = table.getRowModel();
|
||||
const { rows } = table.getRowModel()
|
||||
|
||||
const [heightState, setHeight] = useState(0)
|
||||
const ref = useRef(null);
|
||||
const isDesktop = useMediaQuery("(min-width: 640px)");
|
||||
const ref = useRef(null)
|
||||
const isDesktop = useMediaQuery("(min-width: 640px)")
|
||||
|
||||
useEffect(() => {
|
||||
const calculateHeight = () => {
|
||||
if (ref.current) {
|
||||
const virtuosoElement = ref.current;
|
||||
let topOffset = 0;
|
||||
let currentElement = virtuosoElement as any;
|
||||
const virtuosoElement = ref.current
|
||||
let topOffset = 0
|
||||
let currentElement = virtuosoElement as any
|
||||
|
||||
// Calculate the total offset from the top of the document
|
||||
while (currentElement) {
|
||||
topOffset += currentElement.offsetTop || 0;
|
||||
currentElement = currentElement.offsetParent as HTMLElement;
|
||||
topOffset += currentElement.offsetTop || 0
|
||||
currentElement = currentElement.offsetParent as HTMLElement
|
||||
}
|
||||
|
||||
const totalHeight = window.innerHeight;
|
||||
const calculatedHeight = totalHeight - topOffset;
|
||||
const totalHeight = window.innerHeight
|
||||
const calculatedHeight = totalHeight - topOffset
|
||||
|
||||
setHeight(calculatedHeight);
|
||||
setHeight(calculatedHeight)
|
||||
}
|
||||
};
|
||||
calculateHeight(); // Initial calculation
|
||||
}
|
||||
calculateHeight() // Initial calculation
|
||||
|
||||
if (isDesktop) {
|
||||
window.addEventListener('resize', calculateHeight);
|
||||
window.addEventListener("resize", calculateHeight)
|
||||
}
|
||||
|
||||
return () => { if (isDesktop) window.removeEventListener('resize', calculateHeight); }
|
||||
}, [isDesktop]);
|
||||
return () => {
|
||||
if (isDesktop) window.removeEventListener("resize", calculateHeight)
|
||||
}
|
||||
}, [isDesktop])
|
||||
|
||||
return (
|
||||
<div className="rounded-md border" ref={ref} style={{ height: heightState }}>
|
||||
@@ -158,11 +158,12 @@ export function DataTable<TData, TValue>({
|
||||
{...{
|
||||
style: header.column.getCanSort()
|
||||
? {
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
}
|
||||
cursor: "pointer",
|
||||
userSelect: "none",
|
||||
}
|
||||
: {},
|
||||
onClick: header.column.getToggleSortingHandler(),
|
||||
onClick:
|
||||
header.column.getToggleSortingHandler(),
|
||||
}}
|
||||
>
|
||||
{flexRender(
|
||||
@@ -175,12 +176,12 @@ export function DataTable<TData, TValue>({
|
||||
</div>
|
||||
)}
|
||||
</TableHead>
|
||||
);
|
||||
)
|
||||
})}
|
||||
</TableRow>
|
||||
))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user