mirror of
https://github.com/woodchen-ink/certimate.git
synced 2025-07-18 17:31:55 +08:00
refactor(ui): useTriggerElement
This commit is contained in:
parent
77537e7005
commit
75cf552e72
@ -1,8 +1,9 @@
|
|||||||
import { cloneElement, useMemo, useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useControllableValue } from "ahooks";
|
import { useControllableValue } from "ahooks";
|
||||||
import { Modal, notification } from "antd";
|
import { Modal, notification } from "antd";
|
||||||
|
|
||||||
|
import { useTriggerElement } from "@/hooks";
|
||||||
import { type AccessModel } from "@/domain/access";
|
import { type AccessModel } from "@/domain/access";
|
||||||
import { useAccessStore } from "@/stores/access";
|
import { useAccessStore } from "@/stores/access";
|
||||||
import { getErrMsg } from "@/utils/error";
|
import { getErrMsg } from "@/utils/error";
|
||||||
@ -13,7 +14,7 @@ export type AccessEditModalProps = {
|
|||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
preset: AccessEditFormProps["preset"];
|
preset: AccessEditFormProps["preset"];
|
||||||
trigger?: React.ReactElement;
|
trigger?: React.ReactNode;
|
||||||
onOpenChange?: (open: boolean) => void;
|
onOpenChange?: (open: boolean) => void;
|
||||||
onSubmit?: (record: AccessModel) => void;
|
onSubmit?: (record: AccessModel) => void;
|
||||||
};
|
};
|
||||||
@ -31,19 +32,7 @@ const AccessEditModal = ({ data, loading, trigger, preset, onSubmit, ...props }:
|
|||||||
trigger: "onOpenChange",
|
trigger: "onOpenChange",
|
||||||
});
|
});
|
||||||
|
|
||||||
const triggerEl = useMemo(() => {
|
const triggerDom = useTriggerElement(trigger, { onClick: () => setOpen(true) });
|
||||||
if (!trigger) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cloneElement(trigger, {
|
|
||||||
...trigger.props,
|
|
||||||
onClick: () => {
|
|
||||||
setOpen(true);
|
|
||||||
trigger.props?.onClick?.();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, [trigger, setOpen]);
|
|
||||||
|
|
||||||
const formRef = useRef<AccessEditFormInstance>(null);
|
const formRef = useRef<AccessEditFormInstance>(null);
|
||||||
const [formPending, setFormPending] = useState(false);
|
const [formPending, setFormPending] = useState(false);
|
||||||
@ -94,7 +83,7 @@ const AccessEditModal = ({ data, loading, trigger, preset, onSubmit, ...props }:
|
|||||||
<>
|
<>
|
||||||
{NotificationContextHolder}
|
{NotificationContextHolder}
|
||||||
|
|
||||||
{triggerEl}
|
{triggerDom}
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
afterClose={() => setOpen(false)}
|
afterClose={() => setOpen(false)}
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import { cloneElement, useMemo } from "react";
|
|
||||||
import { useControllableValue } from "ahooks";
|
import { useControllableValue } from "ahooks";
|
||||||
import { Drawer } from "antd";
|
import { Drawer } from "antd";
|
||||||
|
|
||||||
import Show from "@/components/Show";
|
import Show from "@/components/Show";
|
||||||
import CertificateDetail from "./CertificateDetail";
|
import CertificateDetail from "./CertificateDetail";
|
||||||
|
import { useTriggerElement } from "@/hooks";
|
||||||
import { type CertificateModel } from "@/domain/certificate";
|
import { type CertificateModel } from "@/domain/certificate";
|
||||||
|
|
||||||
export type CertificateDetailDrawerProps = {
|
export type CertificateDetailDrawerProps = {
|
||||||
data?: CertificateModel;
|
data?: CertificateModel;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
trigger?: React.ReactElement;
|
trigger?: React.ReactNode;
|
||||||
onOpenChange?: (open: boolean) => void;
|
onOpenChange?: (open: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -21,23 +21,11 @@ const CertificateDetailDrawer = ({ data, loading, trigger, ...props }: Certifica
|
|||||||
trigger: "onOpenChange",
|
trigger: "onOpenChange",
|
||||||
});
|
});
|
||||||
|
|
||||||
const triggerEl = useMemo(() => {
|
const triggerDom = useTriggerElement(trigger, { onClick: () => setOpen(true) });
|
||||||
if (!trigger) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cloneElement(trigger, {
|
|
||||||
...trigger.props,
|
|
||||||
onClick: () => {
|
|
||||||
setOpen(true);
|
|
||||||
trigger.props?.onClick?.();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, [trigger, setOpen]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{triggerEl}
|
{triggerDom}
|
||||||
|
|
||||||
<Drawer closable destroyOnClose open={open} loading={loading} placement="right" title={data?.id} width={640} onClose={() => setOpen(false)}>
|
<Drawer closable destroyOnClose open={open} loading={loading} placement="right" title={data?.id} width={640} onClose={() => setOpen(false)}>
|
||||||
<Show when={!!data}>
|
<Show when={!!data}>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { memo, useCallback, useEffect, useState } from "react";
|
import { memo, useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useControllableValue } from "ahooks";
|
import { useControllableValue } from "ahooks";
|
||||||
import { AutoComplete, Button, Divider, Form, Input, InputNumber, Select, Switch, Tooltip, Typography, type AutoCompleteProps } from "antd";
|
import { AutoComplete, Button, Divider, Form, Input, Select, Switch, Tooltip, Typography, type AutoCompleteProps } from "antd";
|
||||||
import { createSchemaFieldRule } from "antd-zod";
|
import { createSchemaFieldRule } from "antd-zod";
|
||||||
import { PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons";
|
import { PlusOutlined as PlusOutlinedIcon, QuestionCircleOutlined as QuestionCircleOutlinedIcon } from "@ant-design/icons";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
@ -61,7 +61,12 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
|||||||
{ message: t("common.errmsg.host_invalid") }
|
{ message: t("common.errmsg.host_invalid") }
|
||||||
)
|
)
|
||||||
.nullish(),
|
.nullish(),
|
||||||
timeout: z.number().gte(1, t("workflow.nodes.apply.form.propagation_timeout.placeholder")).nullish(),
|
timeout: z
|
||||||
|
.number()
|
||||||
|
.int()
|
||||||
|
.gte(1, t("workflow.nodes.apply.form.propagation_timeout.placeholder"))
|
||||||
|
.transform((v) => +v)
|
||||||
|
.nullish(),
|
||||||
disableFollowCNAME: z.boolean().nullish(),
|
disableFollowCNAME: z.boolean().nullish(),
|
||||||
});
|
});
|
||||||
const formRule = createSchemaFieldRule(formSchema);
|
const formRule = createSchemaFieldRule(formSchema);
|
||||||
@ -161,7 +166,7 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
|||||||
rules={[formRule]}
|
rules={[formRule]}
|
||||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.nameservers.tooltip") }}></span>}
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.nameservers.tooltip") }}></span>}
|
||||||
>
|
>
|
||||||
<Input placeholder={t("workflow.nodes.apply.form.nameservers.placeholder")} />
|
<Input allowClear placeholder={t("workflow.nodes.apply.form.nameservers.placeholder")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
@ -170,8 +175,9 @@ const ApplyNodeForm = ({ data }: ApplyNodeFormProps) => {
|
|||||||
rules={[formRule]}
|
rules={[formRule]}
|
||||||
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.propagation_timeout.tooltip") }}></span>}
|
tooltip={<span dangerouslySetInnerHTML={{ __html: t("workflow.nodes.apply.form.propagation_timeout.tooltip") }}></span>}
|
||||||
>
|
>
|
||||||
<InputNumber
|
<Input
|
||||||
className="w-full"
|
type="number"
|
||||||
|
allowClear
|
||||||
min={0}
|
min={0}
|
||||||
max={3600}
|
max={3600}
|
||||||
placeholder={t("workflow.nodes.apply.form.propagation_timeout.placeholder")}
|
placeholder={t("workflow.nodes.apply.form.propagation_timeout.placeholder")}
|
||||||
|
@ -4,13 +4,14 @@ import { useControllableValue } from "ahooks";
|
|||||||
import { Alert, Drawer } from "antd";
|
import { Alert, Drawer } from "antd";
|
||||||
|
|
||||||
import Show from "@/components/Show";
|
import Show from "@/components/Show";
|
||||||
|
import { useTriggerElement } from "@/hooks";
|
||||||
import { type WorkflowRunModel } from "@/domain/workflowRun";
|
import { type WorkflowRunModel } from "@/domain/workflowRun";
|
||||||
|
|
||||||
export type WorkflowRunDetailDrawerProps = {
|
export type WorkflowRunDetailDrawerProps = {
|
||||||
data?: WorkflowRunModel;
|
data?: WorkflowRunModel;
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
open?: boolean;
|
open?: boolean;
|
||||||
trigger?: React.ReactElement;
|
trigger?: React.ReactNode;
|
||||||
onOpenChange?: (open: boolean) => void;
|
onOpenChange?: (open: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -23,23 +24,11 @@ const WorkflowRunDetailDrawer = ({ data, loading, trigger, ...props }: WorkflowR
|
|||||||
trigger: "onOpenChange",
|
trigger: "onOpenChange",
|
||||||
});
|
});
|
||||||
|
|
||||||
const triggerEl = useMemo(() => {
|
const triggerDom = useTriggerElement(trigger, { onClick: () => setOpen(true) });
|
||||||
if (!trigger) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cloneElement(trigger, {
|
|
||||||
...trigger.props,
|
|
||||||
onClick: () => {
|
|
||||||
setOpen(true);
|
|
||||||
trigger.props?.onClick?.();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, [trigger, setOpen]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{triggerEl}
|
{triggerDom}
|
||||||
|
|
||||||
<Drawer closable destroyOnClose open={open} loading={loading} placement="right" title={data?.id} width={640} onClose={() => setOpen(false)}>
|
<Drawer closable destroyOnClose open={open} loading={loading} placement="right" title={data?.id} width={640} onClose={() => setOpen(false)}>
|
||||||
<Show when={!!data}>
|
<Show when={!!data}>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import useAntdForm from "./useAntdForm";
|
import useAntdForm from "./useAntdForm";
|
||||||
import useBrowserTheme from "./useBrowserTheme";
|
import useBrowserTheme from "./useBrowserTheme";
|
||||||
|
import useTriggerElement from "./useTriggerElement";
|
||||||
import useZustandShallowSelector from "./useZustandShallowSelector";
|
import useZustandShallowSelector from "./useZustandShallowSelector";
|
||||||
|
|
||||||
export { useAntdForm, useBrowserTheme, useZustandShallowSelector };
|
export { useAntdForm, useBrowserTheme, useTriggerElement, useZustandShallowSelector };
|
||||||
|
32
ui/src/hooks/useTriggerElement.ts
Normal file
32
ui/src/hooks/useTriggerElement.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { cloneElement, createElement, Fragment, isValidElement, useMemo } from "react";
|
||||||
|
|
||||||
|
export type UseTriggerElementOptions = {
|
||||||
|
onClick?: (e: MouseEvent) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个触发器元素。通常为配合 Drawer、Modal 等组件使用。
|
||||||
|
* @param {React.ReactNode} trigger
|
||||||
|
* @param {UseTriggerElementOptions} [options]
|
||||||
|
* @returns {React.ReactElement}
|
||||||
|
*/
|
||||||
|
const useTriggerElement = (trigger: React.ReactNode, options?: UseTriggerElementOptions) => {
|
||||||
|
const onClick = options?.onClick;
|
||||||
|
const triggerDom = useMemo(() => {
|
||||||
|
if (!trigger) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const temp = isValidElement(trigger) ? trigger : createElement(Fragment, null, trigger);
|
||||||
|
return cloneElement(temp, {
|
||||||
|
...temp.props,
|
||||||
|
onClick: (e: MouseEvent) => {
|
||||||
|
onClick?.(e);
|
||||||
|
temp.props?.onClick?.(e);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [trigger, onClick]);
|
||||||
|
return triggerDom;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useTriggerElement;
|
@ -1,4 +1,4 @@
|
|||||||
import { cloneElement, memo, useEffect, useMemo, useState } from "react";
|
import { memo, useEffect, useMemo, useState } from "react";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Button, Card, Dropdown, Form, Input, message, Modal, notification, Tabs, Typography } from "antd";
|
import { Button, Card, Dropdown, Form, Input, message, Modal, notification, Tabs, Typography } from "antd";
|
||||||
@ -12,7 +12,7 @@ import End from "@/components/workflow/End";
|
|||||||
import NodeRender from "@/components/workflow/NodeRender";
|
import NodeRender from "@/components/workflow/NodeRender";
|
||||||
import WorkflowRuns from "@/components/workflow/run/WorkflowRuns";
|
import WorkflowRuns from "@/components/workflow/run/WorkflowRuns";
|
||||||
import WorkflowProvider from "@/components/workflow/WorkflowProvider";
|
import WorkflowProvider from "@/components/workflow/WorkflowProvider";
|
||||||
import { useAntdForm, useZustandShallowSelector } from "@/hooks";
|
import { useAntdForm, useTriggerElement, useZustandShallowSelector } from "@/hooks";
|
||||||
import { allNodesValidated, type WorkflowModel, type WorkflowNode } from "@/domain/workflow";
|
import { allNodesValidated, type WorkflowModel, type WorkflowNode } from "@/domain/workflow";
|
||||||
import { useWorkflowStore } from "@/stores/workflow";
|
import { useWorkflowStore } from "@/stores/workflow";
|
||||||
import { remove as removeWorkflow } from "@/repository/workflow";
|
import { remove as removeWorkflow } from "@/repository/workflow";
|
||||||
@ -189,26 +189,14 @@ const WorkflowBaseInfoModalForm = memo(
|
|||||||
onFinish,
|
onFinish,
|
||||||
}: {
|
}: {
|
||||||
initialValues: Pick<WorkflowModel, "name" | "description">;
|
initialValues: Pick<WorkflowModel, "name" | "description">;
|
||||||
trigger?: React.ReactElement;
|
trigger?: React.ReactNode;
|
||||||
onFinish?: (values: Pick<WorkflowModel, "name" | "description">) => Promise<void | boolean>;
|
onFinish?: (values: Pick<WorkflowModel, "name" | "description">) => Promise<void | boolean>;
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
const triggerEl = useMemo(() => {
|
const triggerDom = useTriggerElement(trigger, { onClick: () => setOpen(true) });
|
||||||
if (!trigger) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return cloneElement(trigger, {
|
|
||||||
...trigger.props,
|
|
||||||
onClick: () => {
|
|
||||||
setOpen(true);
|
|
||||||
trigger.props?.onClick?.();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}, [trigger, setOpen]);
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z
|
name: z
|
||||||
@ -251,7 +239,7 @@ const WorkflowBaseInfoModalForm = memo(
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{triggerEl}
|
{triggerDom}
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
afterClose={() => setOpen(false)}
|
afterClose={() => setOpen(false)}
|
||||||
|
@ -10,13 +10,13 @@ export const validDomainName = (value: string, wildcard = false) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const validEmailAddress = (value: string) => {
|
export const validEmailAddress = (value: string) => {
|
||||||
const re = /^[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?$/;
|
const re = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||||
return re.test(value);
|
return re.test(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const validIPv4Address = (value: string) => {
|
export const validIPv4Address = (value: string) => {
|
||||||
const re =
|
const re =
|
||||||
/^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
/^(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])$/;
|
||||||
return re.test(value);
|
return re.test(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user