Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize: evm params #244

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deploy-dapp-example-web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
# Next.js environment variables
echo "NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=${{ secrets.WALLET_CONNECT_PROJECT_ID }}" > ./packages/example/.env
echo "NEXT_PUBLIC_BLOCKFROST_CARDANO_PROJECT_ID=${{ secrets.BLOCKFROST_CARDANO_PROJECT_ID }}" >> ./packages/example/.env
echo "NEXT_PUBLIC_OKLINK_API_KEY=${{ secrets.OKLINK_API_KEY }}" >> ./packages/example/.env
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

环境变量添加正确且安全!

环境变量的添加遵循了最佳实践:

  • 使用 GitHub Secrets 存储敏感信息
  • 正确使用追加模式写入 .env 文件

建议在代码注释中说明这个 API key 的用途,方便其他开发者理解。

 echo "NEXT_PUBLIC_BLOCKFROST_CARDANO_PROJECT_ID=${{ secrets.BLOCKFROST_CARDANO_PROJECT_ID }}" >> ./packages/example/.env
+# OKLink API key for blockchain data queries
 echo "NEXT_PUBLIC_OKLINK_API_KEY=${{ secrets.OKLINK_API_KEY }}" >> ./packages/example/.env
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
echo "NEXT_PUBLIC_OKLINK_API_KEY=${{ secrets.OKLINK_API_KEY }}" >> ./packages/example/.env
echo "NEXT_PUBLIC_BLOCKFROST_CARDANO_PROJECT_ID=${{ secrets.BLOCKFROST_CARDANO_PROJECT_ID }}" >> ./packages/example/.env
# OKLink API key for blockchain data queries
echo "NEXT_PUBLIC_OKLINK_API_KEY=${{ secrets.OKLINK_API_KEY }}" >> ./packages/example/.env


- name: Install Dependency
env:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/publish-npm-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ jobs:
# Next.js environment variables
echo "NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=${{ secrets.WALLET_CONNECT_PROJECT_ID }}" > ./packages/example/.env
echo "NEXT_PUBLIC_BLOCKFROST_CARDANO_PROJECT_ID=${{ secrets.BLOCKFROST_CARDANO_PROJECT_ID }}" >> ./packages/example/.env
echo "NEXT_PUBLIC_OKLINK_API_KEY=${{ secrets.OKLINK_API_KEY }}" >> ./packages/example/.env
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

环境变量配置正确且安全!

环境变量的添加方式符合最佳实践,使用了 GitHub secrets 来存储敏感信息。建议在项目文档中说明此 API 密钥的用途,以便其他开发者理解。


- name: Build Example Web
run: |
Expand Down
4 changes: 3 additions & 1 deletion packages/example/.env.simple
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxxx
NEXT_PUBLIC_WALLET_CONNECT_RELAY_URL=wss://relay.walletconnect.com
# block frost project id
NEXT_PUBLIC_BLOCKFROST_CARDANO_PROJECT_ID=xxxx
NEXT_PUBLIC_BLOCKFROST_CARDANO_PROJECT_ID=xxxx
# oklink api key
Comment on lines +5 to +7
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

建议:改进环境变量的文档说明和安全性

建议对每个 API 密钥添加更详细的说明,包括:

  • 用途说明
  • 获取方式
  • 是否必填
  • 相关文档链接

同时建议:

  1. 添加一个示例 .env.example 文件用于版本控制
  2. 将实际的 .env 文件添加到 .gitignore
  3. 在 README 中说明环境变量的配置步骤
# wallet connect v2 project id
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxxx
NEXT_PUBLIC_WALLET_CONNECT_RELAY_URL=wss://relay.walletconnect.com

# Blockfrost API key for Cardano integration
# Required for Cardano network operations
# Get your key at: https://blockfrost.io
NEXT_PUBLIC_BLOCKFROST_CARDANO_PROJECT_ID=xxxx

# Oklink API key for blockchain data
# Required for asset management features
# Get your key at: https://www.oklink.com
NEXT_PUBLIC_OKLINK_API_KEY=xxxx
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
NEXT_PUBLIC_BLOCKFROST_CARDANO_PROJECT_ID=xxxx
# oklink api key
NEXT_PUBLIC_OKLINK_API_KEY=xxxx
# wallet connect v2 project id
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxxx
NEXT_PUBLIC_WALLET_CONNECT_RELAY_URL=wss://relay.walletconnect.com
# Blockfrost API key for Cardano integration
# Required for Cardano network operations
# Get your key at: https://blockfrost.io
NEXT_PUBLIC_BLOCKFROST_CARDANO_PROJECT_ID=xxxx
# Oklink API key for blockchain data
# Required for asset management features
# Get your key at: https://www.oklink.com
NEXT_PUBLIC_OKLINK_API_KEY=xxxx

NEXT_PUBLIC_OKLINK_API_KEY=xxxx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type ApiPayloadAction =
| { type: 'SET_PRESUPPOSE_PARAMS'; payload: IPresupposeParam[] };

// 优化 JSON 格式化函数
const tryFormatJson = (json: string) => {
export const tryFormatJson = (json: string) => {
try {
return JSON.stringify(JSON.parse(json), null, 2);
} catch {
Expand Down
69 changes: 69 additions & 0 deletions packages/example/components/ApiForm/ApiAutoTextArea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React, { memo, useContext, useEffect } from 'react';
import { useAtom } from 'jotai';
import { Label } from '../ui/label';
import { ApiFormContext } from './ApiForm';
import { AutoHeightTextarea } from '../ui/textarea';


interface AutoTextAreaProps {
id: string;
placeholder?: string;
label?: string;
required?: boolean;
}

const TextArea = memo(({
id,
placeholder,
label,
required
}: AutoTextAreaProps) => {
const context = useContext(ApiFormContext);
if (!context) throw new Error('ApiField must be used within ApiForm');

Comment on lines +21 to +23
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

建议优化错误提示信息

当前的错误信息过于简单。建议添加更多上下文信息,以帮助开发者快速定位问题。

-  if (!context) throw new Error('ApiField must be used within ApiForm');
+  if (!context) throw new Error('ApiAutoTextArea 组件必须在 ApiForm 组件内使用。请检查组件层级结构。');
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const context = useContext(ApiFormContext);
if (!context) throw new Error('ApiField must be used within ApiForm');
const context = useContext(ApiFormContext);
if (!context) throw new Error('ApiAutoTextArea 组件必须在 ApiForm 组件内使用。请检查组件层级结构。');

const { store } = context;
const [field, setField] = useAtom(store.fieldsAtom(id));

useEffect(() => {
field.name = label;
field.required = required;
}, []);

return <>
<AutoHeightTextarea
id={id}
value={field.value}
onChange={(e) => setField({ ...field, value: e.target.value })}
placeholder={placeholder}
disabled={field.disabled}
/>
Comment on lines +32 to +39
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

需要修复类型安全问题

onChange 事件处理器中的值需要明确类型定义。

-      onChange={(e) => setField({ ...field, value: e.target.value })}
+      onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setField({ ...field, value: e.target.value })}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return <>
<AutoHeightTextarea
id={id}
value={field.value}
onChange={(e) => setField({ ...field, value: e.target.value })}
placeholder={placeholder}
disabled={field.disabled}
/>
return <>
<AutoHeightTextarea
id={id}
value={field.value}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setField({ ...field, value: e.target.value })}
placeholder={placeholder}
disabled={field.disabled}
/>
🧰 Tools
🪛 eslint

[error] 36-36: Unsafe return of an any typed value.

(@typescript-eslint/no-unsafe-return)

{field.error && (
<div className="text-sm text-red-500">{field.error}</div>
)}
</>
});

export interface ApiAutoTextAreaProps extends AutoTextAreaProps {
id: string;
}

export const ApiAutoTextArea = memo(({
id,
label,
placeholder,
required
}: ApiAutoTextAreaProps) => {
return (
<div>
{label && (
<Label htmlFor={id}>
{label}
{required && <span className="text-red-500">*</span>}
</Label>
)}
<TextArea id={id} placeholder={placeholder} label={label} required={required} />
</div>
);
});
Comment on lines +50 to +67
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

建议增加无障碍访问支持

组件缺少必要的 ARIA 属性,这可能影响屏幕阅读器用户的使用体验。

       <Label htmlFor={id}>
         {label}
-         {required && <span className="text-red-500">*</span>}
+         {required && <span className="text-red-500" aria-label="必填字段">*</span>}
       </Label>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const ApiAutoTextArea = memo(({
id,
label,
placeholder,
required
}: ApiAutoTextAreaProps) => {
return (
<div>
{label && (
<Label htmlFor={id}>
{label}
{required && <span className="text-red-500">*</span>}
</Label>
)}
<TextArea id={id} placeholder={placeholder} label={label} required={required} />
</div>
);
});
export const ApiAutoTextArea = memo(({
id,
label,
placeholder,
required
}: ApiAutoTextAreaProps) => {
return (
<div>
{label && (
<Label htmlFor={id}>
{label}
{required && <span className="text-red-500" aria-label="必填字段">*</span>}
</Label>
)}
<TextArea id={id} placeholder={placeholder} label={label} required={required} />
</div>
);
});


ApiAutoTextArea.displayName = 'ApiAutoTextArea';
114 changes: 114 additions & 0 deletions packages/example/components/ApiForm/ApiButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { memo, useContext, useCallback, useMemo, useEffect } from 'react';
import { useAtom } from 'jotai';
import { Button } from '../ui/button';
import { ApiFormContext } from './ApiForm';
import { useValidation } from './hooks/useValidation';
import { get, isEmpty } from 'lodash';
import { toast } from '../ui/use-toast';

export interface ApiButtonProps {
id: string;
label: string;
onClick: () => Promise<void>;
validation?: {
fields: string[];
validator?: (values: Record<string, { id: string; value: string; required: boolean }>) => string | undefined;
};
availableDependencyFields?: string[];
}

export const ApiButton = memo(({
id,
label,
onClick,
validation,
availableDependencyFields,
}: ApiButtonProps) => {
const context = useContext(ApiFormContext);
if (!context) throw new Error('ApiButton must be used within ApiForm');

const { store } = context;
const [field, setField] = useAtom(store.fieldsAtom(id));

const loading = field.extra?.loading ?? false;
const result = field.extra?.result;

useEffect(() => {
field.name = label;
}, []);

const dependencyStates = availableDependencyFields?.map(fieldId => {
const [field] = useAtom(store.fieldsAtom(fieldId));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

修复在循环中使用 Hook 的问题。

不能在循环或条件中调用 useAtom。将 Hook 调用移到组件顶层,或使用自定义 Hook 重构逻辑。

提供修复建议:

- const dependencyStates = availableDependencyFields?.map(fieldId => {
-     const [field] = useAtom(store.fieldsAtom(fieldId));
-     return {
-         id: fieldId,
-         value: field.value,
-         name: field.name
-     };
- }) ?? [];
+ const dependencyAtoms = availableDependencyFields?.map(fieldId => store.fieldsAtom(fieldId)) ?? [];
+ const dependencyFields = dependencyAtoms.map(atom => {
+     const [field] = useAtom(atom);
+     return {
+         id: field.id,
+         value: field.value,
+         name: field.name
+     };
+ });

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 eslint

[error] 41-41: React Hook "useAtom" cannot be called inside a callback. React Hooks must be called in a React function component or a custom React Hook function.

(react-hooks/rules-of-hooks)

return {
id: fieldId,
value: field.value,
name: field.name
};
}) ?? [];

const disabledTooltip = useMemo(() => {
const filterFields = dependencyStates.filter(field =>
(field.value == null || isEmpty(field.value))
);

if (filterFields.length > 0) {
return `请填写 ${filterFields.map(field => field.name ?? field.id).join(', ')}`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

修复返回未安全类型值的问题。

第 55 行可能返回 any 类型的值。请确保所有字段都有明确的类型定义,避免类型不安全。

提供修复建议:

- return `请填写 ${filterFields.map(field => field.name ?? field.id).join(', ')}`;
+ return `请填写 ${filterFields.map((field: { name: string; id: string }) => field.name ?? field.id).join(', ')}`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return `请填写 ${filterFields.map(field => field.name ?? field.id).join(', ')}`;
return `请填写 ${filterFields.map((field: { name: string; id: string }) => field.name ?? field.id).join(', ')}`;
🧰 Tools
🪛 eslint

[error] 55-55: Unsafe return of an any typed value.

(@typescript-eslint/no-unsafe-return)

}
return null;
}, [dependencyStates]);

const setResult = (value: string) => {
setField({ ...field, extra: { ...field.extra, result: value } });
}

const setLoading = (value: boolean) => {
setField({ ...field, extra: { ...field.extra, loading: value } });
}

const { validate } = useValidation({
store,
validation
});

const handleClick = useCallback(async () => {
setResult(undefined);

const { isValid, error } = validate();
if (!isValid) {
setResult(error || '验证失败');
return;
}

try {
setLoading(true);
await onClick();
} catch (error) {
const errorMessage = get(error, 'message', 'error') ?? JSON.stringify(error);
toast({
title: '执行失败',
description: errorMessage,
variant: 'destructive',
});
setResult(errorMessage);
} finally {
setLoading(false);
}
}, [onClick, validate, setLoading, setResult]);

return (
<div className="flex flex-col gap-1">
<Button
key={id}
onClick={handleClick}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

避免将异步函数直接赋给 onClick。

onClick 期望一个返回 void 的函数,但 handleClick 是异步的,返回了 Promise。请将其包装在同步函数中。

提供修复建议:

- onClick={handleClick}
+ onClick={() => { void handleClick(); }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onClick={handleClick}
onClick={() => { void handleClick(); }}
🧰 Tools
🪛 eslint

[error] 102-102: Promise-returning function provided to attribute where a void return was expected.

(@typescript-eslint/no-misused-promises)

disabled={disabledTooltip != null}
loading={loading}
>
{label}
</Button>
{disabledTooltip && <div className="text-red-500 text-sm">{disabledTooltip}</div>}
{result && <div className="text-red-500 text-sm">{result}</div>}
</div>
);
});

ApiButton.displayName = 'ApiButton';
56 changes: 56 additions & 0 deletions packages/example/components/ApiForm/ApiCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { memo, useContext, useEffect } from 'react';
import { useAtom } from 'jotai';
import { ApiFormContext } from './ApiForm';
import { Checkbox } from '../ui/checkbox';

export interface ApiCheckboxProps {
id: string;
label?: string;
defaultChecked?: boolean;
}
Comment on lines +6 to +10
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

建议增强类型安全性

建议对 ApiCheckboxProps 接口进行以下改进:

  • label 属性在组件中被直接使用,建议设为必选项
  • 可以为 id 添加更具体的类型约束,比如使用模板字面量类型
 export interface ApiCheckboxProps {
   id: string;
-  label?: string;
+  label: string;
   defaultChecked?: boolean;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export interface ApiCheckboxProps {
id: string;
label?: string;
defaultChecked?: boolean;
}
export interface ApiCheckboxProps {
id: string;
label: string;
defaultChecked?: boolean;
}


export const ApiCheckbox = memo(({
id,
label,
defaultChecked
}: ApiCheckboxProps) => {
const context = useContext(ApiFormContext);
if (!context) throw new Error('ApiField must be used within ApiForm');

const { store } = context;
const [field, setField] = useAtom(store.fieldsAtom(id));
Comment on lines +20 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

需要修复类型安全问题

store.fieldsAtom(id) 的返回值类型为 any,这可能导致运行时错误。建议定义明确的类型。

+ type FieldState = {
+   name?: string;
+   value: boolean;
+   error?: string;
+   required?: boolean;
+   disabled?: boolean;
+ };

- const [field, setField] = useAtom(store.fieldsAtom(id));
+ const [field, setField] = useAtom<FieldState>(store.fieldsAtom(id));

Committable suggestion skipped: line range outside the PR's diff.


useEffect(() => {
field.name = label;
}, []);

useEffect(() => {
if (defaultChecked) {
setField({ ...field, value: defaultChecked });
}
}, []);
Comment on lines +23 to +31
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

建议优化 useEffect 逻辑

当前实现存在以下问题:

  • 两个 useEffect 可以合并
  • 缺少对 label 变化的响应
  • 可能存在竞态条件
- useEffect(() => {
-   field.name = label;
- }, []);
-
- useEffect(() => {
-   if (defaultChecked) {
-     setField({ ...field, value: defaultChecked });
-   }
- }, []);

+ useEffect(() => {
+   setField(prev => ({
+     ...prev,
+     name: label,
+     value: defaultChecked ?? prev.value
+   }));
+ }, [label, defaultChecked]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
field.name = label;
}, []);
useEffect(() => {
if (defaultChecked) {
setField({ ...field, value: defaultChecked });
}
}, []);
useEffect(() => {
setField(prev => ({
...prev,
name: label,
value: defaultChecked ?? prev.value
}));
}, [label, defaultChecked]);


return <div className="flex items-center gap-2">
<Checkbox
id={id}
defaultChecked={defaultChecked}
required={field.required}
checked={field.value}
onCheckedChange={(e) => setField({ ...field, value: e })}
disabled={field.disabled}
/>

<label
htmlFor={id}
className="p-0 m-0 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{label}
</label>

{field.error && (
<div className="text-sm text-red-500">{field.error}</div>
)}
</div>
Comment on lines +33 to +53
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

建议改进可访问性和用户体验

当前实现可以通过以下方式改进:

  • 添加 aria 属性以提升可访问性
  • 错误信息的展示方式可以更友好
  • 可以添加必填项标记
 <div className="flex items-center gap-2">
   <Checkbox
     id={id}
+    aria-describedby={`${id}-error`}
     defaultChecked={defaultChecked}
     required={field.required}
     checked={field.value}
     onCheckedChange={(e) => setField({ ...field, value: e })}
     disabled={field.disabled}
   />

   <label
     htmlFor={id}
     className="p-0 m-0 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
   >
     {label}
+    {field.required && <span className="text-red-500 ml-1">*</span>}
   </label>

   {field.error && (
-    <div className="text-sm text-red-500">{field.error}</div>
+    <div id={`${id}-error`} className="text-sm text-red-500 mt-1" role="alert">
+      {field.error}
+    </div>
   )}
 </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return <div className="flex items-center gap-2">
<Checkbox
id={id}
defaultChecked={defaultChecked}
required={field.required}
checked={field.value}
onCheckedChange={(e) => setField({ ...field, value: e })}
disabled={field.disabled}
/>
<label
htmlFor={id}
className="p-0 m-0 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{label}
</label>
{field.error && (
<div className="text-sm text-red-500">{field.error}</div>
)}
</div>
return <div className="flex items-center gap-2">
<Checkbox
id={id}
aria-describedby={`${id}-error`}
defaultChecked={defaultChecked}
required={field.required}
checked={field.value}
onCheckedChange={(e) => setField({ ...field, value: e })}
disabled={field.disabled}
/>
<label
htmlFor={id}
className="p-0 m-0 text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{label}
{field.required && <span className="text-red-500 ml-1">*</span>}
</label>
{field.error && (
<div id={`${id}-error`} className="text-sm text-red-500 mt-1" role="alert">
{field.error}
</div>
)}
</div>
🧰 Tools
🪛 eslint

[error] 39-39: Unsafe return of an any typed value.

(@typescript-eslint/no-unsafe-return)

});

ApiCheckbox.displayName = 'ApiCheckbox';
Loading
Loading