feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { type ReactNode, type PropsWithChildren } from 'react';
|
||||
|
||||
import { BotMode } from '@coze-arch/bot-api/playground_api';
|
||||
|
||||
import { SingleSheet, type SingleSheetProps } from './single-sheet';
|
||||
import { MultipleSheet, type MultipleSheetProps } from './multiple-sheet';
|
||||
export { SingleSheet, MultipleSheet };
|
||||
|
||||
type SheetViewProps = MultipleSheetProps &
|
||||
SingleSheetProps & {
|
||||
mode: number;
|
||||
renderContent?: (headerNode: ReactNode) => ReactNode;
|
||||
};
|
||||
|
||||
export const SheetView: React.FC<PropsWithChildren<SheetViewProps>> = ({
|
||||
mode = 1,
|
||||
title,
|
||||
titleNode,
|
||||
children,
|
||||
slideProps,
|
||||
containerClassName,
|
||||
headerClassName,
|
||||
titleClassName,
|
||||
renderContent,
|
||||
}) => {
|
||||
if (mode === BotMode.SingleMode || mode === BotMode.WorkflowMode) {
|
||||
return (
|
||||
<SingleSheet
|
||||
containerClassName={containerClassName}
|
||||
titleClassName={titleClassName}
|
||||
headerClassName={headerClassName}
|
||||
title={title}
|
||||
titleNode={titleNode}
|
||||
renderContent={renderContent}
|
||||
>
|
||||
{children}
|
||||
</SingleSheet>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<MultipleSheet
|
||||
title={title}
|
||||
titleNode={titleNode}
|
||||
containerClassName={containerClassName}
|
||||
titleClassName={titleClassName}
|
||||
headerClassName={headerClassName}
|
||||
slideProps={slideProps}
|
||||
renderContent={renderContent}
|
||||
>
|
||||
{children}
|
||||
</MultipleSheet>
|
||||
);
|
||||
};
|
||||
export default SheetView;
|
||||
@@ -0,0 +1,101 @@
|
||||
.sheet-container {
|
||||
.sheet-wrapper();
|
||||
|
||||
position: relative;
|
||||
transition: width .1s linear 0s;
|
||||
}
|
||||
|
||||
// multi style
|
||||
.button {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.btn-left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.btn-right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.sheet-wrapper {
|
||||
:global {
|
||||
.semi-sidesheet-body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sheet-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
background-color: var(--light-color-grey-grey-0, #f7f7fa);
|
||||
box-shadow: 0 6px 8px 0 rgb(29 28 35 / 6%),
|
||||
0 0 2px 0 rgb(29 28 35 / 18%);
|
||||
}
|
||||
|
||||
.sheet-header {
|
||||
height: 56px;
|
||||
padding: 16px 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid var(--light-usage-border-color-border, rgb(29 28 35 / 8%));
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.sheet-header-arrow {
|
||||
margin-right: 8px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 4px;
|
||||
border: 0.5px solid var(--Light-usage-border---color-border, rgba(29, 28, 35, 8%));
|
||||
transform: rotate(180deg);
|
||||
background: var(--Light-usage-bg---color-bg-0, #FFF);
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 4%), 0 0 1px 0 rgba(0, 0, 0, 8%);
|
||||
}
|
||||
|
||||
.sheet-header-arrow-left {
|
||||
margin-right: 0;
|
||||
margin-left: 8px;
|
||||
transform: rotate(0);
|
||||
}
|
||||
|
||||
.sheet-header-arrow-right {
|
||||
box-shadow: 0 -2px 4px 0 rgba(0, 0, 0, 4%), 0 0 1px 0 rgba(0, 0, 0, 8%);
|
||||
}
|
||||
|
||||
.sheet-header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sheet-header-title {
|
||||
color: var(--light-usage-text-color-text-0, #1c1d23);
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.sheet-header-scope {
|
||||
width: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
height: 64px;
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable @coze-arch/max-line-per-function */
|
||||
import React, { type PropsWithChildren, type ReactNode, useMemo } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { nanoid } from 'nanoid';
|
||||
import classNames from 'classnames';
|
||||
import { useMultiAgentStore } from '@coze-studio/bot-detail-store/multi-agent';
|
||||
import { SideSheet, Tooltip } from '@coze-arch/bot-semi';
|
||||
import { IconCollapse } from '@coze-arch/bot-icons';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface MultipleSheetProps extends PropsWithChildren {
|
||||
containerClassName?: string;
|
||||
headerClassName?: string;
|
||||
titleClassName?: string;
|
||||
title?: string;
|
||||
titleNode?: ReactNode;
|
||||
renderContent?: (headerNode: ReactNode) => ReactNode;
|
||||
slideProps?: {
|
||||
placement?: 'left' | 'right';
|
||||
width?: number;
|
||||
btnClassName?: string;
|
||||
visible?: boolean;
|
||||
btnNode?: ReactNode;
|
||||
openBtnTooltip?: string;
|
||||
closeBtnTooltip?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export function MultipleSheet({
|
||||
titleClassName,
|
||||
containerClassName,
|
||||
headerClassName,
|
||||
title,
|
||||
titleNode,
|
||||
slideProps,
|
||||
children,
|
||||
renderContent,
|
||||
}: MultipleSheetProps) {
|
||||
const {
|
||||
placement,
|
||||
width,
|
||||
btnClassName,
|
||||
btnNode,
|
||||
openBtnTooltip,
|
||||
closeBtnTooltip,
|
||||
} = slideProps || {};
|
||||
const isLeft = useMemo(() => placement === 'left', [placement]);
|
||||
const { setMultiSheetViewOpen, multiSheetViewOpen } = useMultiAgentStore(
|
||||
useShallow(store => ({
|
||||
setMultiSheetViewOpen: store.setMultiSheetViewOpen,
|
||||
multiSheetViewOpen: store.multiSheetViewOpen,
|
||||
})),
|
||||
);
|
||||
|
||||
const containerId = useMemo(() => `container-${nanoid()}`, []);
|
||||
const open = useMemo(() => {
|
||||
if (isLeft) {
|
||||
return multiSheetViewOpen.left;
|
||||
}
|
||||
return multiSheetViewOpen.right;
|
||||
}, [isLeft, multiSheetViewOpen]);
|
||||
|
||||
const setOpen = (_open: boolean) => {
|
||||
if (isLeft) {
|
||||
setMultiSheetViewOpen({ left: _open });
|
||||
} else {
|
||||
setMultiSheetViewOpen({ right: _open });
|
||||
}
|
||||
};
|
||||
const computedStyle = (): React.CSSProperties => {
|
||||
if (open) {
|
||||
return {
|
||||
width,
|
||||
};
|
||||
}
|
||||
return {
|
||||
width: 0,
|
||||
};
|
||||
};
|
||||
|
||||
const header = (
|
||||
<div
|
||||
className={classNames(styles['sheet-header'], headerClassName)}
|
||||
style={{
|
||||
flexDirection: isLeft ? 'row-reverse' : 'row',
|
||||
}}
|
||||
>
|
||||
{/* btn */}
|
||||
<div
|
||||
className={classNames(
|
||||
styles['sheet-header-arrow'],
|
||||
isLeft
|
||||
? styles['sheet-header-arrow-left']
|
||||
: styles['sheet-header-arrow-right'],
|
||||
)}
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
data-testid={
|
||||
isLeft
|
||||
? 'bot-edit-muli-agent-action-arrow-right-button'
|
||||
: 'bot-edit-muli-agent-action-arrow-left-button'
|
||||
}
|
||||
>
|
||||
{closeBtnTooltip && open ? (
|
||||
<Tooltip
|
||||
spacing={20}
|
||||
position={isLeft ? 'right' : 'left'}
|
||||
content={closeBtnTooltip}
|
||||
>
|
||||
<IconCollapse />
|
||||
</Tooltip>
|
||||
) : (
|
||||
<IconCollapse />
|
||||
)}
|
||||
</div>
|
||||
<div className={styles['sheet-header-content']}>
|
||||
{/* title */}
|
||||
<div
|
||||
className={classNames(styles['sheet-header-title'], titleClassName)}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
{/* 头部插槽 */}
|
||||
<div className={styles['sheet-header-scope']}> {titleNode}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div
|
||||
id={containerId}
|
||||
className={styles['sheet-container']}
|
||||
style={computedStyle()}
|
||||
>
|
||||
{!!btnNode && (
|
||||
<div
|
||||
className={classNames(
|
||||
styles.button,
|
||||
isLeft ? styles['btn-left'] : styles['btn-right'],
|
||||
btnClassName,
|
||||
)}
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
}}
|
||||
>
|
||||
{!open && openBtnTooltip ? (
|
||||
<Tooltip
|
||||
position={isLeft ? 'right' : 'left'}
|
||||
content={openBtnTooltip}
|
||||
>
|
||||
{btnNode}
|
||||
</Tooltip>
|
||||
) : (
|
||||
btnNode
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<SideSheet
|
||||
keepDOM
|
||||
width={width}
|
||||
mask={false}
|
||||
placement={placement}
|
||||
visible={open}
|
||||
getPopupContainer={() =>
|
||||
// @tip 不确定这个PopupContainer的实现逻辑,采用ref.current拿不到最新的值,
|
||||
// @tip Semi需更新至2.54.0以上:当SideSheet的visible为true才挂载可以确保`getPopupContainer`使用querySelector可以获取到父组件dom,但兜底值要去掉(https://github.com/DouyinFE/semi-design/pull/2094)
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
document.querySelector(`#${containerId}`)!
|
||||
}
|
||||
className={styles['sheet-wrapper']}
|
||||
headerStyle={{
|
||||
display: 'none',
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={classNames(styles['sheet-content'], containerClassName)}
|
||||
>
|
||||
{/* 浮层头部 */}
|
||||
|
||||
{renderContent ? (
|
||||
renderContent(header)
|
||||
) : (
|
||||
<>
|
||||
{header}
|
||||
{children}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</SideSheet>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
.card {
|
||||
background-color: var(--light-color-grey-grey-0, #f7f7fa);
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sheet-header {
|
||||
height: 64px;
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid theme('colors.stroke.5');
|
||||
}
|
||||
|
||||
.sheet-header-title {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
line-height: 24px;
|
||||
flex-shrink: 0;
|
||||
margin-right: 24px;
|
||||
}
|
||||
|
||||
.sheet-header-scope {
|
||||
width: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
height: 64px;
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Copyright 2025 coze-dev Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { type PropsWithChildren, type ReactNode } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import styles from './index.module.less';
|
||||
|
||||
export interface SingleSheetProps extends PropsWithChildren {
|
||||
containerClassName?: string;
|
||||
headerClassName?: string;
|
||||
title?: string;
|
||||
titleNode?: ReactNode;
|
||||
titleClassName?: string;
|
||||
headerSlotClassName?: string;
|
||||
renderContent?: (headerNode: ReactNode) => ReactNode;
|
||||
}
|
||||
|
||||
export function SingleSheet({
|
||||
containerClassName,
|
||||
headerClassName,
|
||||
titleClassName,
|
||||
title,
|
||||
titleNode,
|
||||
children,
|
||||
headerSlotClassName,
|
||||
renderContent,
|
||||
}: SingleSheetProps) {
|
||||
const headerNode = (
|
||||
<div className={classNames(styles.card, containerClassName)}>
|
||||
{/* 浮层头部 */}
|
||||
<div className={classNames(styles['sheet-header'], headerClassName)}>
|
||||
<div
|
||||
className={classNames(
|
||||
styles['sheet-header-title'],
|
||||
'coz-fg-plus',
|
||||
titleClassName,
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
{/* 头部插槽 */}
|
||||
<div
|
||||
className={classNames(
|
||||
styles['sheet-header-scope'],
|
||||
headerSlotClassName,
|
||||
)}
|
||||
>
|
||||
{titleNode}
|
||||
</div>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
return renderContent ? <>{renderContent(headerNode)}</> : headerNode;
|
||||
}
|
||||
Reference in New Issue
Block a user