@lobehub/lobehub 2.0.0-next.244 → 2.0.0-next.245
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/package.json +1 -1
- package/src/app/[variants]/(main)/chat/_layout/Sidebar/Header/Agent/SwitchPanel.tsx +1 -0
- package/src/app/[variants]/(main)/chat/features/Conversation/Header/HeaderActions/index.tsx +1 -1
- package/src/features/ChatInput/ActionBar/Knowledge/useControls.tsx +1 -1
- package/src/features/ChatInput/ActionBar/Tools/PopoverContent.tsx +92 -0
- package/src/features/ChatInput/ActionBar/Tools/ToolItem.tsx +2 -1
- package/src/features/ChatInput/ActionBar/Tools/ToolsList.tsx +107 -0
- package/src/features/ChatInput/ActionBar/Tools/index.tsx +17 -74
- package/src/features/ChatInput/ActionBar/Tools/useControls.tsx +2 -20
- package/src/features/ChatInput/ActionBar/Upload/ServerMode.tsx +1 -1
- package/src/features/ChatInput/ActionBar/components/ActionPopover.tsx +1 -0
- package/src/features/ChatInput/ActionBar/components/CheckboxWithLoading.tsx +60 -0
- package/src/features/ModelSwitchPanel/index.tsx +1 -0
- package/src/features/ChatInput/ActionBar/components/CheckbokWithLoading.tsx +0 -53
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
# Changelog
|
|
4
4
|
|
|
5
|
+
## [Version 2.0.0-next.245](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.244...v2.0.0-next.245)
|
|
6
|
+
|
|
7
|
+
<sup>Released on **2026-01-09**</sup>
|
|
8
|
+
|
|
9
|
+
#### ♻ Code Refactoring
|
|
10
|
+
|
|
11
|
+
- **misc**: Improve Tools popover component structure and fix UI consistency.
|
|
12
|
+
|
|
13
|
+
<br/>
|
|
14
|
+
|
|
15
|
+
<details>
|
|
16
|
+
<summary><kbd>Improvements and Fixes</kbd></summary>
|
|
17
|
+
|
|
18
|
+
#### Code refactoring
|
|
19
|
+
|
|
20
|
+
- **misc**: Improve Tools popover component structure and fix UI consistency, closes [#11356](https://github.com/lobehub/lobe-chat/issues/11356) ([f46837a](https://github.com/lobehub/lobe-chat/commit/f46837a))
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
|
|
24
|
+
<div align="right">
|
|
25
|
+
|
|
26
|
+
[](#readme-top)
|
|
27
|
+
|
|
28
|
+
</div>
|
|
29
|
+
|
|
5
30
|
## [Version 2.0.0-next.244](https://github.com/lobehub/lobe-chat/compare/v2.0.0-next.243...v2.0.0-next.244)
|
|
6
31
|
|
|
7
32
|
<sup>Released on **2026-01-08**</sup>
|
package/changelog/v1.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobehub/lobehub",
|
|
3
|
-
"version": "2.0.0-next.
|
|
3
|
+
"version": "2.0.0-next.245",
|
|
4
4
|
"description": "LobeHub - an open-source,comprehensive AI Agent framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"framework",
|
|
@@ -12,7 +12,7 @@ const HeaderActions = memo(() => {
|
|
|
12
12
|
const { menuItems } = useMenu();
|
|
13
13
|
|
|
14
14
|
return (
|
|
15
|
-
<DropdownMenu items={menuItems}>
|
|
15
|
+
<DropdownMenu items={menuItems} nativeButton={false}>
|
|
16
16
|
<ActionIcon icon={MoreHorizontal} size={DESKTOP_HEADER_ICON_SIZE} />
|
|
17
17
|
</DropdownMenu>
|
|
18
18
|
);
|
|
@@ -9,7 +9,7 @@ import { useAgentStore } from '@/store/agent';
|
|
|
9
9
|
import { agentByIdSelectors } from '@/store/agent/selectors';
|
|
10
10
|
|
|
11
11
|
import { useAgentId } from '../../hooks/useAgentId';
|
|
12
|
-
import CheckboxItem from '../components/
|
|
12
|
+
import CheckboxItem from '../components/CheckboxWithLoading';
|
|
13
13
|
|
|
14
14
|
export const useControls = ({
|
|
15
15
|
setModalOpen,
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { Flexbox, Icon, type ItemType, Segmented, usePopoverContext } from '@lobehub/ui';
|
|
2
|
+
import { createStaticStyles, cssVar } from 'antd-style';
|
|
3
|
+
import { ChevronRight, Store } from 'lucide-react';
|
|
4
|
+
import { memo } from 'react';
|
|
5
|
+
import { useTranslation } from 'react-i18next';
|
|
6
|
+
|
|
7
|
+
import ToolsList, { toolsListStyles } from './ToolsList';
|
|
8
|
+
|
|
9
|
+
const styles = createStaticStyles(({ css }) => ({
|
|
10
|
+
footer: css`
|
|
11
|
+
padding: 4px;
|
|
12
|
+
border-block-start: 1px solid ${cssVar.colorBorderSecondary};
|
|
13
|
+
`,
|
|
14
|
+
header: css`
|
|
15
|
+
padding: 8px;
|
|
16
|
+
border-block-end: 1px solid ${cssVar.colorBorderSecondary};
|
|
17
|
+
`,
|
|
18
|
+
trailingIcon: css`
|
|
19
|
+
opacity: 0.5;
|
|
20
|
+
`,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
type TabType = 'all' | 'installed';
|
|
24
|
+
|
|
25
|
+
interface PopoverContentProps {
|
|
26
|
+
activeTab: TabType;
|
|
27
|
+
currentItems: ItemType[];
|
|
28
|
+
enableKlavis: boolean;
|
|
29
|
+
onOpenStore: () => void;
|
|
30
|
+
onTabChange: (tab: TabType) => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const PopoverContent = memo<PopoverContentProps>(
|
|
34
|
+
({ activeTab, currentItems, enableKlavis, onTabChange, onOpenStore }) => {
|
|
35
|
+
const { t } = useTranslation('setting');
|
|
36
|
+
|
|
37
|
+
const { close: closePopover } = usePopoverContext();
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Flexbox gap={0}>
|
|
41
|
+
<div className={styles.header}>
|
|
42
|
+
<Segmented
|
|
43
|
+
block
|
|
44
|
+
onChange={(v) => onTabChange(v as TabType)}
|
|
45
|
+
options={[
|
|
46
|
+
{
|
|
47
|
+
label: t('tools.tabs.all', { defaultValue: 'all' }),
|
|
48
|
+
value: 'all',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
label: t('tools.tabs.installed', { defaultValue: 'Installed' }),
|
|
52
|
+
value: 'installed',
|
|
53
|
+
},
|
|
54
|
+
]}
|
|
55
|
+
size="small"
|
|
56
|
+
value={activeTab}
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
<div
|
|
60
|
+
style={{
|
|
61
|
+
maxHeight: 500,
|
|
62
|
+
minHeight: enableKlavis ? 500 : undefined,
|
|
63
|
+
overflowY: 'auto',
|
|
64
|
+
}}
|
|
65
|
+
>
|
|
66
|
+
<ToolsList items={currentItems} />
|
|
67
|
+
</div>
|
|
68
|
+
<div className={styles.footer}>
|
|
69
|
+
<div
|
|
70
|
+
className={toolsListStyles.item}
|
|
71
|
+
onClick={() => {
|
|
72
|
+
closePopover();
|
|
73
|
+
onOpenStore();
|
|
74
|
+
}}
|
|
75
|
+
role="button"
|
|
76
|
+
tabIndex={0}
|
|
77
|
+
>
|
|
78
|
+
<div className={toolsListStyles.itemIcon}>
|
|
79
|
+
<Icon icon={Store} size={20} />
|
|
80
|
+
</div>
|
|
81
|
+
<div className={toolsListStyles.itemContent}>{t('tools.plugins.store')}</div>
|
|
82
|
+
<Icon className={styles.trailingIcon} icon={ChevronRight} size={16} />
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</Flexbox>
|
|
86
|
+
);
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
PopoverContent.displayName = 'PopoverContent';
|
|
91
|
+
|
|
92
|
+
export default PopoverContent;
|
|
@@ -5,7 +5,7 @@ import PluginTag from '@/components/Plugins/PluginTag';
|
|
|
5
5
|
import { useToolStore } from '@/store/tool';
|
|
6
6
|
import { customPluginSelectors } from '@/store/tool/selectors';
|
|
7
7
|
|
|
8
|
-
import CheckboxItem, { type CheckboxItemProps } from '../components/
|
|
8
|
+
import CheckboxItem, { type CheckboxItemProps } from '../components/CheckboxWithLoading';
|
|
9
9
|
|
|
10
10
|
const ToolItem = memo<CheckboxItemProps>(({ id, onUpdate, label, checked }) => {
|
|
11
11
|
const isCustom = useToolStore((s) => customPluginSelectors.isCustomPlugin(id)(s));
|
|
@@ -13,6 +13,7 @@ const ToolItem = memo<CheckboxItemProps>(({ id, onUpdate, label, checked }) => {
|
|
|
13
13
|
return (
|
|
14
14
|
<CheckboxItem
|
|
15
15
|
checked={checked}
|
|
16
|
+
hasPadding={false}
|
|
16
17
|
id={id}
|
|
17
18
|
label={
|
|
18
19
|
<Flexbox align={'center'} gap={8} horizontal>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Flexbox, Icon, type ItemType, Text } from '@lobehub/ui';
|
|
2
|
+
import { Divider } from 'antd';
|
|
3
|
+
import { createStaticStyles, cssVar } from 'antd-style';
|
|
4
|
+
import type { ReactNode } from 'react';
|
|
5
|
+
import { Fragment, isValidElement, memo } from 'react';
|
|
6
|
+
|
|
7
|
+
export const toolsListStyles = createStaticStyles(({ css }) => ({
|
|
8
|
+
groupLabel: css`
|
|
9
|
+
padding-block: 4px;
|
|
10
|
+
padding-inline: 12px;
|
|
11
|
+
`,
|
|
12
|
+
item: css`
|
|
13
|
+
cursor: pointer;
|
|
14
|
+
|
|
15
|
+
display: flex;
|
|
16
|
+
gap: 12px;
|
|
17
|
+
align-items: center;
|
|
18
|
+
|
|
19
|
+
padding-block: 8px;
|
|
20
|
+
padding-inline: 12px;
|
|
21
|
+
border-radius: 6px;
|
|
22
|
+
|
|
23
|
+
transition: background-color 0.2s;
|
|
24
|
+
|
|
25
|
+
&:hover {
|
|
26
|
+
background: ${cssVar.colorFillTertiary};
|
|
27
|
+
}
|
|
28
|
+
`,
|
|
29
|
+
itemContent: css`
|
|
30
|
+
flex: 1;
|
|
31
|
+
min-width: 0;
|
|
32
|
+
`,
|
|
33
|
+
itemIcon: css`
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-shrink: 0;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
|
|
39
|
+
width: 24px;
|
|
40
|
+
height: 24px;
|
|
41
|
+
`,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
interface ToolItemData {
|
|
45
|
+
children?: ToolItemData[];
|
|
46
|
+
extra?: ReactNode;
|
|
47
|
+
icon?: ReactNode;
|
|
48
|
+
key?: string;
|
|
49
|
+
label?: ReactNode;
|
|
50
|
+
onClick?: () => void;
|
|
51
|
+
type?: 'group' | 'divider';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface ToolsListProps {
|
|
55
|
+
items: ItemType[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const ToolsList = memo<ToolsListProps>(({ items }) => {
|
|
59
|
+
const renderItem = (item: ToolItemData, index: number) => {
|
|
60
|
+
if (item.type === 'divider') {
|
|
61
|
+
return <Divider key={`divider-${index}`} style={{ margin: '4px 0' }} />;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (item.type === 'group') {
|
|
65
|
+
return (
|
|
66
|
+
<Fragment key={item.key || `group-${index}`}>
|
|
67
|
+
<Text className={toolsListStyles.groupLabel} fontSize={12} type="secondary">
|
|
68
|
+
{item.label}
|
|
69
|
+
</Text>
|
|
70
|
+
{item.children?.map((child, childIndex) => renderItem(child, childIndex))}
|
|
71
|
+
</Fragment>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Regular item
|
|
76
|
+
// icon can be: ReactNode (already rendered), LucideIcon/ForwardRef (needs Icon wrapper), or undefined
|
|
77
|
+
const iconNode = item.icon ? (
|
|
78
|
+
isValidElement(item.icon) ? (
|
|
79
|
+
item.icon
|
|
80
|
+
) : (
|
|
81
|
+
<Icon icon={item.icon as any} size={20} />
|
|
82
|
+
)
|
|
83
|
+
) : null;
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
className={toolsListStyles.item}
|
|
88
|
+
key={item.key || `item-${index}`}
|
|
89
|
+
onClick={item.onClick}
|
|
90
|
+
role="button"
|
|
91
|
+
tabIndex={0}
|
|
92
|
+
>
|
|
93
|
+
{iconNode && <div className={toolsListStyles.itemIcon}>{iconNode}</div>}
|
|
94
|
+
<div className={toolsListStyles.itemContent}>{item.label}</div>
|
|
95
|
+
{item.extra}
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
return (
|
|
101
|
+
<Flexbox gap={0} padding={4}>
|
|
102
|
+
{items.map((item, index) => renderItem(item as ToolItemData, index))}
|
|
103
|
+
</Flexbox>
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
export default ToolsList;
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { Segmented } from '@lobehub/ui';
|
|
2
|
-
import { createStaticStyles, cssVar } from 'antd-style';
|
|
3
1
|
import { Blocks } from 'lucide-react';
|
|
4
2
|
import { Suspense, memo, useEffect, useRef, useState } from 'react';
|
|
5
3
|
import { useTranslation } from 'react-i18next';
|
|
@@ -12,46 +10,17 @@ import { serverConfigSelectors, useServerConfigStore } from '@/store/serverConfi
|
|
|
12
10
|
|
|
13
11
|
import { useAgentId } from '../../hooks/useAgentId';
|
|
14
12
|
import Action from '../components/Action';
|
|
13
|
+
import PopoverContent from './PopoverContent';
|
|
15
14
|
import { useControls } from './useControls';
|
|
16
15
|
|
|
17
16
|
type TabType = 'all' | 'installed';
|
|
18
17
|
|
|
19
|
-
const prefixCls = 'ant';
|
|
20
|
-
|
|
21
|
-
const styles = createStaticStyles(({ css }) => ({
|
|
22
|
-
dropdown: css`
|
|
23
|
-
overflow: hidden;
|
|
24
|
-
|
|
25
|
-
width: 100%;
|
|
26
|
-
border: 1px solid ${cssVar.colorBorderSecondary};
|
|
27
|
-
border-radius: ${cssVar.borderRadiusLG};
|
|
28
|
-
|
|
29
|
-
background: ${cssVar.colorBgElevated};
|
|
30
|
-
box-shadow: ${cssVar.boxShadowSecondary};
|
|
31
|
-
|
|
32
|
-
.${prefixCls}-dropdown-menu {
|
|
33
|
-
border-radius: 0 !important;
|
|
34
|
-
background: transparent !important;
|
|
35
|
-
box-shadow: none !important;
|
|
36
|
-
}
|
|
37
|
-
`,
|
|
38
|
-
header: css`
|
|
39
|
-
padding: ${cssVar.paddingXS};
|
|
40
|
-
border-block-end: 1px solid ${cssVar.colorBorderSecondary};
|
|
41
|
-
background: transparent;
|
|
42
|
-
`,
|
|
43
|
-
scroller: css`
|
|
44
|
-
overflow: hidden auto;
|
|
45
|
-
`,
|
|
46
|
-
}));
|
|
47
|
-
|
|
48
18
|
const Tools = memo(() => {
|
|
49
19
|
const { t } = useTranslation('setting');
|
|
50
20
|
const [modalOpen, setModalOpen] = useState(false);
|
|
51
21
|
const [updating, setUpdating] = useState(false);
|
|
52
22
|
const [activeTab, setActiveTab] = useState<TabType | null>(null);
|
|
53
23
|
const { marketItems, installedPluginItems } = useControls({
|
|
54
|
-
setModalOpen,
|
|
55
24
|
setUpdating,
|
|
56
25
|
});
|
|
57
26
|
|
|
@@ -82,52 +51,26 @@ const Tools = memo(() => {
|
|
|
82
51
|
return (
|
|
83
52
|
<Suspense fallback={<Action disabled icon={Blocks} title={t('tools.title')} />}>
|
|
84
53
|
<Action
|
|
85
|
-
|
|
54
|
+
icon={Blocks}
|
|
55
|
+
loading={updating}
|
|
56
|
+
popover={{
|
|
57
|
+
content: (
|
|
58
|
+
<PopoverContent
|
|
59
|
+
activeTab={effectiveTab}
|
|
60
|
+
currentItems={currentItems}
|
|
61
|
+
enableKlavis={enableKlavis}
|
|
62
|
+
onOpenStore={() => setModalOpen(true)}
|
|
63
|
+
onTabChange={setActiveTab}
|
|
64
|
+
/>
|
|
65
|
+
),
|
|
86
66
|
maxWidth: 320,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
maxHeight: 'unset',
|
|
92
|
-
overflowY: 'visible',
|
|
67
|
+
minWidth: 320,
|
|
68
|
+
styles: {
|
|
69
|
+
content: {
|
|
70
|
+
padding: 0,
|
|
93
71
|
},
|
|
94
72
|
},
|
|
95
|
-
minHeight: enableKlavis ? 500 : undefined,
|
|
96
|
-
minWidth: 320,
|
|
97
|
-
popupRender: (menu) => (
|
|
98
|
-
<div className={styles.dropdown}>
|
|
99
|
-
<div className={styles.header}>
|
|
100
|
-
<Segmented
|
|
101
|
-
block
|
|
102
|
-
onChange={(v) => setActiveTab(v as TabType)}
|
|
103
|
-
options={[
|
|
104
|
-
{
|
|
105
|
-
label: t('tools.tabs.all', { defaultValue: 'all' }),
|
|
106
|
-
value: 'all',
|
|
107
|
-
},
|
|
108
|
-
{
|
|
109
|
-
label: t('tools.tabs.installed', { defaultValue: 'Installed' }),
|
|
110
|
-
value: 'installed',
|
|
111
|
-
},
|
|
112
|
-
]}
|
|
113
|
-
size="small"
|
|
114
|
-
value={effectiveTab}
|
|
115
|
-
/>
|
|
116
|
-
</div>
|
|
117
|
-
<div
|
|
118
|
-
className={styles.scroller}
|
|
119
|
-
style={{
|
|
120
|
-
maxHeight: 500,
|
|
121
|
-
minHeight: enableKlavis ? 500 : undefined,
|
|
122
|
-
}}
|
|
123
|
-
>
|
|
124
|
-
{menu}
|
|
125
|
-
</div>
|
|
126
|
-
</div>
|
|
127
|
-
),
|
|
128
73
|
}}
|
|
129
|
-
icon={Blocks}
|
|
130
|
-
loading={updating}
|
|
131
74
|
showTooltip={false}
|
|
132
75
|
title={t('tools.title')}
|
|
133
76
|
/>
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { Avatar, Flexbox, Icon, Image, type ItemType } from '@lobehub/ui';
|
|
8
8
|
import { cssVar } from 'antd-style';
|
|
9
9
|
import isEqual from 'fast-deep-equal';
|
|
10
|
-
import {
|
|
10
|
+
import { ToyBrick } from 'lucide-react';
|
|
11
11
|
import { memo, useMemo } from 'react';
|
|
12
12
|
import { useTranslation } from 'react-i18next';
|
|
13
13
|
|
|
@@ -61,13 +61,7 @@ const LobehubSkillIcon = memo<Pick<LobehubSkillProviderType, 'icon' | 'label'>>(
|
|
|
61
61
|
|
|
62
62
|
LobehubSkillIcon.displayName = 'LobehubSkillIcon';
|
|
63
63
|
|
|
64
|
-
export const useControls = ({
|
|
65
|
-
setModalOpen,
|
|
66
|
-
setUpdating,
|
|
67
|
-
}: {
|
|
68
|
-
setModalOpen: (open: boolean) => void;
|
|
69
|
-
setUpdating: (updating: boolean) => void;
|
|
70
|
-
}) => {
|
|
64
|
+
export const useControls = ({ setUpdating }: { setUpdating: (updating: boolean) => void }) => {
|
|
71
65
|
const { t } = useTranslation('setting');
|
|
72
66
|
const agentId = useAgentId();
|
|
73
67
|
const list = useToolStore(pluginSelectors.installedPluginMetaList, isEqual);
|
|
@@ -234,18 +228,6 @@ export const useControls = ({
|
|
|
234
228
|
),
|
|
235
229
|
type: 'group',
|
|
236
230
|
},
|
|
237
|
-
{
|
|
238
|
-
type: 'divider',
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
extra: <Icon icon={ArrowRight} />,
|
|
242
|
-
icon: Store,
|
|
243
|
-
key: 'plugin-store',
|
|
244
|
-
label: t('tools.plugins.store'),
|
|
245
|
-
onClick: () => {
|
|
246
|
-
setModalOpen(true);
|
|
247
|
-
},
|
|
248
|
-
},
|
|
249
231
|
];
|
|
250
232
|
|
|
251
233
|
// 已安装 tab 的 items - 只显示已安装的插件
|
|
@@ -21,7 +21,7 @@ import { preferenceSelectors } from '@/store/user/selectors';
|
|
|
21
21
|
|
|
22
22
|
import { useAgentId } from '../../hooks/useAgentId';
|
|
23
23
|
import Action from '../components/Action';
|
|
24
|
-
import CheckboxItem from '../components/
|
|
24
|
+
import CheckboxItem from '../components/CheckboxWithLoading';
|
|
25
25
|
|
|
26
26
|
const hotArea = css`
|
|
27
27
|
&::before {
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Center, Checkbox, Flexbox, Icon } from '@lobehub/ui';
|
|
2
|
+
import { Loader2 } from 'lucide-react';
|
|
3
|
+
import { type ReactNode, memo, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
export interface CheckboxItemProps {
|
|
6
|
+
checked?: boolean;
|
|
7
|
+
hasPadding?: boolean;
|
|
8
|
+
id: string;
|
|
9
|
+
label?: ReactNode;
|
|
10
|
+
onUpdate: (id: string, enabled: boolean) => Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const CheckboxItem = memo<CheckboxItemProps>(
|
|
14
|
+
({ id, onUpdate, label, checked, hasPadding = true }) => {
|
|
15
|
+
const [loading, setLoading] = useState(false);
|
|
16
|
+
|
|
17
|
+
const updateState = async () => {
|
|
18
|
+
setLoading(true);
|
|
19
|
+
await onUpdate(id, !checked);
|
|
20
|
+
setLoading(false);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Flexbox
|
|
25
|
+
align={'center'}
|
|
26
|
+
gap={24}
|
|
27
|
+
horizontal
|
|
28
|
+
justify={'space-between'}
|
|
29
|
+
onClick={async (e) => {
|
|
30
|
+
e.stopPropagation();
|
|
31
|
+
updateState();
|
|
32
|
+
}}
|
|
33
|
+
style={
|
|
34
|
+
hasPadding
|
|
35
|
+
? {
|
|
36
|
+
paddingLeft: 8,
|
|
37
|
+
}
|
|
38
|
+
: void 0
|
|
39
|
+
}
|
|
40
|
+
>
|
|
41
|
+
{label || id}
|
|
42
|
+
{loading ? (
|
|
43
|
+
<Center width={18}>
|
|
44
|
+
<Icon icon={Loader2} spin />
|
|
45
|
+
</Center>
|
|
46
|
+
) : (
|
|
47
|
+
<Checkbox
|
|
48
|
+
checked={checked}
|
|
49
|
+
onClick={async (e) => {
|
|
50
|
+
e.stopPropagation();
|
|
51
|
+
await updateState();
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
)}
|
|
55
|
+
</Flexbox>
|
|
56
|
+
);
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export default CheckboxItem;
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { Center, Flexbox, Icon , Checkbox } from '@lobehub/ui';
|
|
2
|
-
import { Loader2 } from 'lucide-react';
|
|
3
|
-
import { type ReactNode, memo, useState } from 'react';
|
|
4
|
-
|
|
5
|
-
export interface CheckboxItemProps {
|
|
6
|
-
checked?: boolean;
|
|
7
|
-
id: string;
|
|
8
|
-
label?: ReactNode;
|
|
9
|
-
onUpdate: (id: string, enabled: boolean) => Promise<void>;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const CheckboxItem = memo<CheckboxItemProps>(({ id, onUpdate, label, checked }) => {
|
|
13
|
-
const [loading, setLoading] = useState(false);
|
|
14
|
-
|
|
15
|
-
const updateState = async () => {
|
|
16
|
-
setLoading(true);
|
|
17
|
-
await onUpdate(id, !checked);
|
|
18
|
-
setLoading(false);
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<Flexbox
|
|
23
|
-
align={'center'}
|
|
24
|
-
gap={24}
|
|
25
|
-
horizontal
|
|
26
|
-
justify={'space-between'}
|
|
27
|
-
onClick={async (e) => {
|
|
28
|
-
e.stopPropagation();
|
|
29
|
-
updateState();
|
|
30
|
-
}}
|
|
31
|
-
style={{
|
|
32
|
-
paddingLeft: 8,
|
|
33
|
-
}}
|
|
34
|
-
>
|
|
35
|
-
{label || id}
|
|
36
|
-
{loading ? (
|
|
37
|
-
<Center width={18}>
|
|
38
|
-
<Icon icon={Loader2} spin />
|
|
39
|
-
</Center>
|
|
40
|
-
) : (
|
|
41
|
-
<Checkbox
|
|
42
|
-
checked={checked}
|
|
43
|
-
onClick={async (e) => {
|
|
44
|
-
e.stopPropagation();
|
|
45
|
-
await updateState();
|
|
46
|
-
}}
|
|
47
|
-
/>
|
|
48
|
-
)}
|
|
49
|
-
</Flexbox>
|
|
50
|
-
);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
export default CheckboxItem;
|