feat: manually mirror opencoze's code from bytedance
Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
31
frontend/packages/foundation/layout/.storybook/main.js
Normal file
31
frontend/packages/foundation/layout/.storybook/main.js
Normal file
@@ -0,0 +1,31 @@
|
||||
import { mergeConfig } from 'vite';
|
||||
import svgr from 'vite-plugin-svgr';
|
||||
|
||||
/** @type { import('@storybook/react-vite').StorybookConfig } */
|
||||
const config = {
|
||||
stories: ['../stories/**/*.mdx', '../stories/**/*.stories.tsx'],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-onboarding',
|
||||
'@storybook/addon-interactions',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: 'tag',
|
||||
},
|
||||
viteFinal: config =>
|
||||
mergeConfig(config, {
|
||||
plugins: [
|
||||
svgr({
|
||||
svgrOptions: {
|
||||
native: false,
|
||||
},
|
||||
}),
|
||||
],
|
||||
}),
|
||||
};
|
||||
export default config;
|
||||
14
frontend/packages/foundation/layout/.storybook/preview.js
Normal file
14
frontend/packages/foundation/layout/.storybook/preview.js
Normal file
@@ -0,0 +1,14 @@
|
||||
/** @type { import('@storybook/react').Preview } */
|
||||
const preview = {
|
||||
parameters: {
|
||||
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
5
frontend/packages/foundation/layout/.stylelintrc.js
Normal file
5
frontend/packages/foundation/layout/.stylelintrc.js
Normal file
@@ -0,0 +1,5 @@
|
||||
const { defineConfig } = require('@coze-arch/stylelint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
extends: [],
|
||||
});
|
||||
16
frontend/packages/foundation/layout/README.md
Normal file
16
frontend/packages/foundation/layout/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# @coze-foundation/layout
|
||||
|
||||
> Project template for react component with storybook.
|
||||
|
||||
## Features
|
||||
|
||||
- [x] eslint & ts
|
||||
- [x] esm bundle
|
||||
- [x] umd bundle
|
||||
- [x] storybook
|
||||
|
||||
## Commands
|
||||
|
||||
- init: `rush update`
|
||||
- dev: `npm run dev`
|
||||
- build: `npm run build`
|
||||
30
frontend/packages/foundation/layout/__tests__/mobile.test.ts
Normal file
30
frontend/packages/foundation/layout/__tests__/mobile.test.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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 './setup-vitest';
|
||||
import { useSignMobileStore } from '../src/store';
|
||||
|
||||
describe('useSignMobileStore', () => {
|
||||
it('should init with default state', () => {
|
||||
const state = useSignMobileStore.getState();
|
||||
expect(state.mobileTips).toEqual(false);
|
||||
});
|
||||
|
||||
it('setMobileTips', () => {
|
||||
useSignMobileStore.getState().setMobileTips(true);
|
||||
expect(useSignMobileStore.getState().mobileTips).toEqual(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
vi.stubGlobal('IS_DEV_MODE', false);
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 { type Mock } from 'vitest';
|
||||
import { renderHook } from '@testing-library/react-hooks';
|
||||
import { useUIModal } from '@coze-arch/bot-semi';
|
||||
|
||||
vi.mock('@coze-arch/bot-semi', () => ({
|
||||
useUIModal: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@coze-arch/i18n', () => ({
|
||||
I18n: {
|
||||
t: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
import { useMobileTips } from '../src/hooks';
|
||||
|
||||
describe('useMobileTips', () => {
|
||||
test('should return correctly', () => {
|
||||
const mockOpen = vi.fn();
|
||||
const mockClose = vi.fn();
|
||||
const mockModal = vi.fn().mockReturnValue({ test: 'foo' });
|
||||
|
||||
(useUIModal as Mock).mockReturnValue({
|
||||
open: mockOpen,
|
||||
close: mockClose,
|
||||
modal: mockModal,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useMobileTips());
|
||||
expect(useUIModal).toBeCalled();
|
||||
useUIModal.mock.calls[0][0].onOk();
|
||||
expect(mockClose).toBeCalled();
|
||||
|
||||
expect(typeof result.current.open).toEqual('function');
|
||||
expect(typeof result.current.close).toEqual('function');
|
||||
expect(mockModal).toBeCalled();
|
||||
const contentShape = mockModal.mock.calls[0][0];
|
||||
expect(contentShape.props.className).toContain('mobile-tips-span');
|
||||
expect(result.current.node).toEqual({ test: 'foo' });
|
||||
|
||||
expect(mockOpen).not.toBeCalled();
|
||||
result.current.open();
|
||||
expect(mockOpen).toBeCalled();
|
||||
|
||||
result.current.close();
|
||||
expect(mockClose).toBeCalledTimes(2);
|
||||
});
|
||||
});
|
||||
12
frontend/packages/foundation/layout/config/rush-project.json
Normal file
12
frontend/packages/foundation/layout/config/rush-project.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"operationSettings": [
|
||||
{
|
||||
"operationName": "test:cov",
|
||||
"outputFolderNames": ["coverage"]
|
||||
},
|
||||
{
|
||||
"operationName": "ts-check",
|
||||
"outputFolderNames": ["./dist"]
|
||||
}
|
||||
]
|
||||
}
|
||||
7
frontend/packages/foundation/layout/eslint.config.js
Normal file
7
frontend/packages/foundation/layout/eslint.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const { defineConfig } = require('@coze-arch/eslint-config');
|
||||
|
||||
module.exports = defineConfig({
|
||||
packageRoot: __dirname,
|
||||
preset: 'web',
|
||||
rules: {},
|
||||
});
|
||||
80
frontend/packages/foundation/layout/package.json
Normal file
80
frontend/packages/foundation/layout/package.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"name": "@coze-foundation/layout",
|
||||
"version": "0.0.1",
|
||||
"description": "基座框架布局package",
|
||||
"license": "Apache-2.0",
|
||||
"author": "yuwenbinjie@bytedance.com",
|
||||
"maintainers": [],
|
||||
"exports": {
|
||||
".": "./src/index.tsx"
|
||||
},
|
||||
"main": "src/index.tsx",
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
".": [
|
||||
"./src/index.tsx"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "exit 0",
|
||||
"lint": "eslint ./ --cache",
|
||||
"test": "vitest --run --passWithNoTests",
|
||||
"test:cov": "npm run test -- --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^5.1.5",
|
||||
"@coze-arch/bot-error": "workspace:*",
|
||||
"@coze-arch/bot-hooks": "workspace:*",
|
||||
"@coze-arch/bot-icons": "workspace:*",
|
||||
"@coze-arch/bot-semi": "workspace:*",
|
||||
"@coze-arch/bot-tea": "workspace:*",
|
||||
"@coze-arch/bot-utils": "workspace:*",
|
||||
"@coze-arch/coze-design": "0.0.6-alpha.346d77",
|
||||
"@coze-arch/i18n": "workspace:*",
|
||||
"@coze-arch/idl": "workspace:*",
|
||||
"@coze-arch/web-context": "workspace:*",
|
||||
"@coze-foundation/account-adapter": "workspace:*",
|
||||
"@coze-foundation/space-store": "workspace:*",
|
||||
"@douyinfe/semi-illustrations": "^2.36.0",
|
||||
"classnames": "^2.3.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"zustand": "^4.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@coze-arch/bot-typings": "workspace:*",
|
||||
"@coze-arch/eslint-config": "workspace:*",
|
||||
"@coze-arch/foundation-sdk": "workspace:*",
|
||||
"@coze-arch/logger": "workspace:*",
|
||||
"@coze-arch/stylelint-config": "workspace:*",
|
||||
"@coze-arch/ts-config": "workspace:*",
|
||||
"@coze-arch/vitest-config": "workspace:*",
|
||||
"@rsbuild/core": "1.1.13",
|
||||
"@testing-library/jest-dom": "^6.1.5",
|
||||
"@testing-library/react": "^14.1.2",
|
||||
"@testing-library/react-hooks": "^8.0.1",
|
||||
"@types/lodash-es": "^4.17.10",
|
||||
"@types/node": "18.18.9",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/react": "18.2.37",
|
||||
"@types/react-dom": "18.2.15",
|
||||
"@vitest/coverage-v8": "~3.0.5",
|
||||
"react": "~18.2.0",
|
||||
"react-dom": "~18.2.0",
|
||||
"react-is": ">= 16.8.0",
|
||||
"react-router-dom": "^6.22.0",
|
||||
"styled-components": ">= 2",
|
||||
"stylelint": "^15.11.0",
|
||||
"typescript": "5.7.2",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-svgr": "~3.3.0",
|
||||
"vitest": "~3.0.5",
|
||||
"webpack": "~5.91.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18.2.0",
|
||||
"react-dom": ">=18.2.0"
|
||||
},
|
||||
"// deps": "immer@^10.0.3 为脚本自动补齐,请勿改动"
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 8.3 KiB |
@@ -0,0 +1,13 @@
|
||||
@common-box-shadow: 0px 2px 8px 0px rgba(31, 35, 41, 0.02),
|
||||
0px 2px 4px 0px rgba(31, 35, 41, 0.02), 0px 2px 2px 0px rgba(31, 35, 41, 0.02);
|
||||
|
||||
.common-svg-icon(@size:14px, @color:#3370ff) {
|
||||
> svg {
|
||||
width: @size;
|
||||
height: @size;
|
||||
|
||||
> path {
|
||||
fill: @color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
.menu {
|
||||
:global {
|
||||
.coz-item-text {
|
||||
overflow: hidden;
|
||||
flex: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* 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 {
|
||||
type FC,
|
||||
isValidElement,
|
||||
type ReactNode,
|
||||
type PropsWithChildren,
|
||||
} from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Avatar, Badge, Dropdown } from '@coze-arch/coze-design';
|
||||
import { useUserInfo } from '@coze-foundation/account-adapter';
|
||||
|
||||
import { reportNavClick } from '../global-layout/utils';
|
||||
import { type LayoutAccountMenuItem } from '../global-layout/types';
|
||||
|
||||
import style from './index.module.less';
|
||||
|
||||
function isReactNode(value: unknown): value is ReactNode {
|
||||
if (
|
||||
value === null ||
|
||||
typeof value === 'string' ||
|
||||
typeof value === 'number' ||
|
||||
typeof value === 'boolean' ||
|
||||
isValidElement(value) ||
|
||||
Array.isArray(value)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export const GlobalLayoutAccountDropdown: FC<
|
||||
PropsWithChildren<{
|
||||
menus?: LayoutAccountMenuItem[];
|
||||
userBadge?: ReactNode;
|
||||
userTips?: ReactNode;
|
||||
disableVisibleChange?: boolean;
|
||||
visible?: boolean;
|
||||
onVisibleChange?: (visible: boolean) => void;
|
||||
}>
|
||||
> = ({
|
||||
menus,
|
||||
userBadge = null,
|
||||
userTips = null,
|
||||
children,
|
||||
disableVisibleChange,
|
||||
visible,
|
||||
onVisibleChange,
|
||||
}) => {
|
||||
const userInfo = useUserInfo();
|
||||
|
||||
if (!userInfo) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Dropdown
|
||||
trigger="custom"
|
||||
position={'rightBottom'}
|
||||
visible={visible}
|
||||
onVisibleChange={onVisibleChange}
|
||||
onClickOutSide={() => {
|
||||
if (!disableVisibleChange) {
|
||||
onVisibleChange?.(false);
|
||||
}
|
||||
}}
|
||||
render={
|
||||
<Dropdown.Menu
|
||||
className={classNames(style.menu, 'w-[250px]')}
|
||||
mode="menu"
|
||||
>
|
||||
{menus?.map(item =>
|
||||
isReactNode(item) ? (
|
||||
item
|
||||
) : (
|
||||
<Dropdown.Item
|
||||
key={item.title}
|
||||
onClick={e => {
|
||||
reportNavClick(item.title);
|
||||
onVisibleChange?.(false);
|
||||
item.onClick();
|
||||
}}
|
||||
data-testid={item.dataTestId}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<div className="mr-[8px] flex items-center">
|
||||
{item.prefixIcon}
|
||||
</div>
|
||||
<div>{item.title}</div>
|
||||
</div>
|
||||
<div className="flex items-center">{item.extra}</div>
|
||||
</div>
|
||||
</Dropdown.Item>
|
||||
),
|
||||
)}
|
||||
</Dropdown.Menu>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'relative',
|
||||
'p-[4px] rounded-[8px] transition-colors hover:coz-mg-secondary-hovered',
|
||||
'leading-none',
|
||||
visible && 'coz-mg-secondary-hovered',
|
||||
)}
|
||||
onClick={() => {
|
||||
if (!disableVisibleChange) {
|
||||
onVisibleChange?.(!visible);
|
||||
}
|
||||
}}
|
||||
data-testid="layout_avatar-menu-button"
|
||||
>
|
||||
<Badge
|
||||
position="rightBottom"
|
||||
countStyle={{
|
||||
right: 6,
|
||||
bottom: 6,
|
||||
}}
|
||||
count={userBadge}
|
||||
>
|
||||
<Avatar
|
||||
src={userInfo.avatar_url}
|
||||
className={classNames('w-[32px] h-[32px] rounded-full')}
|
||||
/>
|
||||
</Badge>
|
||||
{userTips}
|
||||
</div>
|
||||
</Dropdown>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
.bot-exit-btn {
|
||||
:global {
|
||||
.semi-button.semi-button-with-icon-only {
|
||||
width: 32px;
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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 from 'react';
|
||||
|
||||
import { type BackButtonProps } from '@coze-arch/foundation-sdk';
|
||||
import { IconButton } from '@coze-arch/coze-design';
|
||||
import { IconArrowLeft } from '@coze-arch/bot-icons';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export const BackButton = ({ onClickBack }: BackButtonProps) => (
|
||||
<div className={s['bot-exit-btn']}>
|
||||
<IconButton
|
||||
color="secondary"
|
||||
icon={<IconArrowLeft />}
|
||||
onClick={onClickBack}
|
||||
data-testid="bot-exit-button"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,30 @@
|
||||
/* stylelint-disable declaration-no-important -- 历史代码,为避免引入新BUG暂不修复 */
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
margin: auto;
|
||||
padding-top: 35.39vh;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-top: 24px !important;
|
||||
margin-bottom: 4px !important;
|
||||
|
||||
font-size: 16px !important;
|
||||
font-weight: 600 !important;
|
||||
line-height: 22px !important;
|
||||
}
|
||||
|
||||
.paragraph {
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
color: #1d1c2399;
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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 { useNavigate, useRouteError } from 'react-router-dom';
|
||||
import { useMemo, useState, type FC } from 'react';
|
||||
|
||||
import { useShallow } from 'zustand/react/shallow';
|
||||
import { escape } from 'lodash-es';
|
||||
import { BaseEnum } from '@coze-arch/web-context';
|
||||
import { getSlardarInstance } from '@coze-arch/logger';
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { Typography, UIButton } from '@coze-arch/bot-semi';
|
||||
import { useRouteConfig } from '@coze-arch/bot-hooks';
|
||||
import { isCustomError, useRouteErrorCatch } from '@coze-arch/bot-error';
|
||||
import { IllustrationNoAccess } from '@douyinfe/semi-illustrations';
|
||||
import { useSpaceStore, useSpaceApp } from '@coze-foundation/space-store';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
// i18n 的配置,对齐 starling 文案后再替换
|
||||
export const GlobalError: FC = () => {
|
||||
const navigate = useNavigate();
|
||||
const spaceApp = useSpaceApp();
|
||||
const { menuKey: base } = useRouteConfig();
|
||||
const { id, getPersonalSpaceID } = useSpaceStore(
|
||||
useShallow(spaceStore => ({
|
||||
id: spaceStore.space.id,
|
||||
getPersonalSpaceID: spaceStore.getPersonalSpaceID,
|
||||
})),
|
||||
);
|
||||
const error = useRouteError();
|
||||
useRouteErrorCatch(error);
|
||||
|
||||
const isLazyLoadError = useMemo(() => {
|
||||
if (hasErrorMessage(error)) {
|
||||
return /Minified\sReact\serror\s\#306/i.test(error.message);
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
const customGlobalErrorConfig = useMemo(() => {
|
||||
if (isCustomError(error)) {
|
||||
return error.ext?.customGlobalErrorConfig;
|
||||
}
|
||||
}, [error]);
|
||||
|
||||
const [sessionId] = useState(() => getSlardarInstance()?.config()?.sessionId);
|
||||
|
||||
return (
|
||||
<div className={s.wrapper}>
|
||||
<div className={s.content}>
|
||||
<IllustrationNoAccess width={140} height={140} />
|
||||
<Typography.Title className={s.title}>
|
||||
{customGlobalErrorConfig?.title ??
|
||||
I18n.t('errorpage_bot_title', {}, `Failed to view the ${spaceApp}`)}
|
||||
</Typography.Title>
|
||||
<Typography.Paragraph className={s.paragraph}>
|
||||
{customGlobalErrorConfig?.subtitle ??
|
||||
I18n.t(
|
||||
'errorpage_subtitle',
|
||||
{},
|
||||
"Please check your link or try again after joining the bot's team.",
|
||||
)}
|
||||
</Typography.Paragraph>
|
||||
{!!sessionId && (
|
||||
<div className="leading-[12px] mb-[24px] text-[12px] text-gray-400">
|
||||
{sessionId}
|
||||
</div>
|
||||
)}
|
||||
<UIButton
|
||||
theme="solid"
|
||||
onClick={() => {
|
||||
let url = '';
|
||||
if (BaseEnum.Space === base) {
|
||||
const spaceId =
|
||||
id ??
|
||||
getPersonalSpaceID() ??
|
||||
// 企业下无个人空间,缺省跳转到第一个空间
|
||||
useSpaceStore.getState().spaceList[0]?.id;
|
||||
url = spaceId ? `/space/${spaceId}/${spaceApp}` : '/space';
|
||||
} else if (base && base in BaseEnum) {
|
||||
url = `/${base}`;
|
||||
} else {
|
||||
url = '/';
|
||||
}
|
||||
|
||||
if (!isLazyLoadError) {
|
||||
navigate(url);
|
||||
} else {
|
||||
window.location.href = escape(url);
|
||||
}
|
||||
}}
|
||||
>
|
||||
{I18n.t('errorpage_bot_btn', {}, 'Go to Bot Platform')}
|
||||
</UIButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function hasErrorMessage(e: unknown): e is { message: string } {
|
||||
if (!e || typeof e !== 'object') {
|
||||
return false;
|
||||
}
|
||||
if ('message' in e && typeof e.message === 'string') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 { useState, type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { IconButton, Tooltip } from '@coze-arch/coze-design';
|
||||
|
||||
import { reportNavClick } from '../utils';
|
||||
import { type LayoutButtonItem } from '../types';
|
||||
|
||||
export const GlobalLayoutActionBtn: FC<LayoutButtonItem> = ({
|
||||
icon,
|
||||
iconClass,
|
||||
onClick,
|
||||
tooltip,
|
||||
dataTestId,
|
||||
className,
|
||||
portal,
|
||||
renderButton,
|
||||
}) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
|
||||
const onButtonClick = () => {
|
||||
setVisible(false);
|
||||
reportNavClick(tooltip);
|
||||
onClick?.();
|
||||
};
|
||||
|
||||
const btn = renderButton ? (
|
||||
renderButton({
|
||||
onClick: onButtonClick,
|
||||
icon,
|
||||
dataTestId,
|
||||
})
|
||||
) : (
|
||||
<IconButton
|
||||
color="secondary"
|
||||
size="large"
|
||||
className={classNames(className, { '!h-full': !!iconClass })}
|
||||
icon={
|
||||
<div
|
||||
className={classNames(
|
||||
'text-[20px] coz-fg-primary h-[20px]',
|
||||
iconClass,
|
||||
)}
|
||||
>
|
||||
{icon}
|
||||
</div>
|
||||
}
|
||||
onClick={onButtonClick}
|
||||
data-testid={dataTestId}
|
||||
/>
|
||||
);
|
||||
// 如果 tooltip 为空,则不显示 tooltip
|
||||
return (
|
||||
<>
|
||||
{tooltip ? (
|
||||
<Tooltip
|
||||
content={tooltip}
|
||||
position="right"
|
||||
clickToHide
|
||||
visible={visible}
|
||||
onVisibleChange={setVisible}
|
||||
>
|
||||
{btn}
|
||||
</Tooltip>
|
||||
) : (
|
||||
btn
|
||||
)}
|
||||
{portal}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 { NavLink, useLocation } from 'react-router-dom';
|
||||
import { type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { reportNavClick } from '../utils';
|
||||
import { type LayoutMenuItem } from '../types';
|
||||
|
||||
const menuStyle = classNames(
|
||||
'w-[60px] h-[48px]',
|
||||
'flex flex-col items-center justify-center',
|
||||
'rounded-[6px]',
|
||||
'transition-all',
|
||||
'hover:coz-mg-primary-hovered',
|
||||
);
|
||||
|
||||
export const GLobalLayoutMenuItem: FC<LayoutMenuItem> = ({
|
||||
title,
|
||||
icon,
|
||||
activeIcon,
|
||||
path,
|
||||
dataTestId,
|
||||
}) => {
|
||||
const location = useLocation();
|
||||
|
||||
let isActive = false;
|
||||
let newPath = '';
|
||||
// 如果 path 是数组,则取第一个匹配的路径
|
||||
if (Array.isArray(path)) {
|
||||
isActive = path.some(p => location.pathname.startsWith(p));
|
||||
newPath = path.find(p => location.pathname.startsWith(p)) || path[0];
|
||||
} else {
|
||||
isActive = location.pathname.startsWith(path);
|
||||
newPath = path;
|
||||
}
|
||||
|
||||
// cp-disable-next-line
|
||||
const isLink = newPath.startsWith('https://');
|
||||
|
||||
const navId = `primary-menu-${newPath.startsWith('/') ? newPath.slice(1) : newPath}`;
|
||||
return (
|
||||
<NavLink
|
||||
to={newPath}
|
||||
target={isLink ? '_blank' : undefined}
|
||||
className="no-underline"
|
||||
onClick={() => {
|
||||
reportNavClick(title);
|
||||
}}
|
||||
data-testid={dataTestId}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
menuStyle,
|
||||
isActive
|
||||
? 'coz-mg-primary coz-fg-plus'
|
||||
: 'coz-bg-max coz-fg-secondary',
|
||||
)}
|
||||
id={navId}
|
||||
>
|
||||
<div className="text-[20px] leading-none">
|
||||
{isActive ? activeIcon : icon}
|
||||
</div>
|
||||
<div className="mt-[2px] h-[14px] font-[500] flex items-center justify-center overflow-hidden leading-none overflow-hidden w-full">
|
||||
<span className="text-[20px] scale-50 whitespace-nowrap">
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</NavLink>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* 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 { type FC } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { Divider, Space } from '@coze-arch/coze-design';
|
||||
import { IconMenuLogo } from '@coze-arch/bot-icons';
|
||||
import { useRouteConfig } from '@coze-arch/bot-hooks';
|
||||
|
||||
import { type LayoutProps } from '../types';
|
||||
import { SubMenu } from './sub-menu';
|
||||
import { GLobalLayoutMenuItem } from './menu-item';
|
||||
import { GlobalLayoutActionBtn } from './action-btn';
|
||||
|
||||
const siderStyle = classNames(
|
||||
'relative',
|
||||
'h-full',
|
||||
'border-[1px] border-solid coz-stroke-primary rounded-[14px]',
|
||||
'coz-bg-max',
|
||||
'flex flex-row items-stretch',
|
||||
);
|
||||
|
||||
const mainMenuStyle = classNames(
|
||||
'px-[6px] py-[16px]',
|
||||
'flex flex-col h-full items-center',
|
||||
);
|
||||
|
||||
export const GlobalLayoutSider: FC<Omit<LayoutProps, 'hasSider'>> = ({
|
||||
actions,
|
||||
menus,
|
||||
extras,
|
||||
onClickLogo,
|
||||
footer = null,
|
||||
}) => {
|
||||
const config = useRouteConfig();
|
||||
const { subMenu: SubMenuComponent } = config;
|
||||
const hasSubNav = Boolean(SubMenuComponent);
|
||||
|
||||
return (
|
||||
<div className="pl-8px py-8px h-full">
|
||||
<div className={siderStyle}>
|
||||
{/* 主导航 */}
|
||||
<div
|
||||
className={classNames(
|
||||
mainMenuStyle,
|
||||
hasSubNav &&
|
||||
'border-0 border-r-[1px] border-solid coz-stroke-primary',
|
||||
)}
|
||||
>
|
||||
<IconMenuLogo
|
||||
onClick={onClickLogo}
|
||||
className="cursor-pointer w-[40px] h-[40px]"
|
||||
/>
|
||||
<div className="mt-[16px]">
|
||||
{actions?.map((action, index) => (
|
||||
<GlobalLayoutActionBtn {...action} key={index} />
|
||||
))}
|
||||
</div>
|
||||
<Divider className="my-12px w-[24px]" />
|
||||
<Space spacing={4} vertical className="flex-1 overflow-auto">
|
||||
{menus?.map((menu, index) => (
|
||||
<GLobalLayoutMenuItem {...menu} key={index} />
|
||||
))}
|
||||
</Space>
|
||||
<Space spacing={4} vertical className="mt-[12px]">
|
||||
{extras?.map((extra, index) => (
|
||||
<GlobalLayoutActionBtn {...extra} key={index} />
|
||||
))}
|
||||
{footer}
|
||||
</Space>
|
||||
</div>
|
||||
{/* 二级导航 */}
|
||||
<SubMenu />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
GlobalLayoutSider.displayName = 'GlobalLayoutSider';
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 { type FC, Suspense, useState, useCallback } from 'react';
|
||||
|
||||
import { useRouteConfig } from '@coze-arch/bot-hooks';
|
||||
|
||||
import styles from '../side-sheet.module.less';
|
||||
|
||||
const STORAGE_KEY = 'submenu-width';
|
||||
const MIN_WIDTH = 200;
|
||||
const MAX_WIDTH = 380;
|
||||
|
||||
export const SubMenu: FC = () => {
|
||||
const config = useRouteConfig();
|
||||
const { subMenu: SubMenuComponent } = config;
|
||||
const [width, setWidth] = useState(() => {
|
||||
const savedWidth = localStorage.getItem(STORAGE_KEY);
|
||||
return savedWidth
|
||||
? Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, Number(savedWidth)))
|
||||
: MIN_WIDTH;
|
||||
});
|
||||
|
||||
const handleMouseDown = useCallback(
|
||||
(event: React.MouseEvent) => {
|
||||
event.preventDefault();
|
||||
const startX = event.pageX;
|
||||
const startWidth = width;
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
const newWidth = Math.min(
|
||||
MAX_WIDTH,
|
||||
Math.max(MIN_WIDTH, startWidth + e.pageX - startX),
|
||||
);
|
||||
setWidth(newWidth);
|
||||
localStorage.setItem(STORAGE_KEY, String(newWidth));
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
document.removeEventListener('mousemove', handleMouseMove);
|
||||
document.removeEventListener('mouseup', handleMouseUp);
|
||||
};
|
||||
|
||||
document.addEventListener('mousemove', handleMouseMove);
|
||||
document.addEventListener('mouseup', handleMouseUp);
|
||||
},
|
||||
[width],
|
||||
);
|
||||
|
||||
if (!SubMenuComponent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-row">
|
||||
<div
|
||||
className="overflow-auto flex flex-col box-border px-[6px] py-[12px]"
|
||||
style={{ width: `${width}px` }}
|
||||
>
|
||||
<Suspense>
|
||||
<SubMenuComponent />
|
||||
</Suspense>
|
||||
</div>
|
||||
<div className={styles['sub-menu-resize']} onMouseDown={handleMouseDown}>
|
||||
<div className={styles['sub-menu-resize-line']}></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 { createContext, useContext } from 'react';
|
||||
|
||||
import { noop } from 'lodash-es';
|
||||
|
||||
import { type GlobalLayoutContext } from './types';
|
||||
|
||||
export const globalLayoutContext = createContext<GlobalLayoutContext>({
|
||||
sideSheetVisible: false,
|
||||
setSideSheetVisible: noop,
|
||||
});
|
||||
export const GlobalLayoutProvider = globalLayoutContext.Provider;
|
||||
|
||||
export const useGlobalLayoutContext = () => useContext(globalLayoutContext);
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 { useEffect } from 'react';
|
||||
|
||||
import { isMobile, setMobileBody, setPCBody } from '@coze-arch/bot-utils';
|
||||
import {
|
||||
useIsResponsive,
|
||||
useIsResponsiveByRouteConfig,
|
||||
useRouteConfig,
|
||||
} from '@coze-arch/bot-hooks';
|
||||
|
||||
import { useSignMobileStore } from '../../store';
|
||||
import { useMobileTips } from '../../hooks';
|
||||
import { useGlobalLayoutContext } from './context';
|
||||
|
||||
export const useLayoutResponsive = () => {
|
||||
const { mobileTips, setMobileTips } = useSignMobileStore();
|
||||
const { node: mobileTipsModal, open: openMobileTipsModal } = useMobileTips();
|
||||
const config = useRouteConfig();
|
||||
const isResponsiveOld = useIsResponsive();
|
||||
const isResponsiveByRouteConfig = useIsResponsiveByRouteConfig();
|
||||
const isResponsive = isResponsiveOld || isResponsiveByRouteConfig;
|
||||
|
||||
useEffect(() => {
|
||||
if (config.showMobileTips) {
|
||||
if (!mobileTips && isMobile()) {
|
||||
openMobileTipsModal(); // 不适配移动端弹窗提示
|
||||
setMobileTips(true);
|
||||
}
|
||||
|
||||
if (isResponsive) {
|
||||
setMobileBody();
|
||||
} else {
|
||||
setPCBody();
|
||||
}
|
||||
}
|
||||
}, [config.showMobileTips, isResponsive]);
|
||||
return {
|
||||
isResponsive,
|
||||
mobileTipsModal: config.showMobileTips ? mobileTipsModal : null,
|
||||
};
|
||||
};
|
||||
|
||||
export const useOpenGlobalLayoutSideSheet = () => {
|
||||
const { setSideSheetVisible } = useGlobalLayoutContext();
|
||||
return () => {
|
||||
setSideSheetVisible(true);
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* 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 { useLocation } from 'react-router-dom';
|
||||
import { type FC, type PropsWithChildren, useState, useEffect } from 'react';
|
||||
|
||||
import cls from 'classnames';
|
||||
import { Layout, SideSheet } from '@coze-arch/coze-design';
|
||||
|
||||
import { type LayoutProps } from './types';
|
||||
import { useLayoutResponsive } from './hooks';
|
||||
import { GlobalLayoutProvider } from './context';
|
||||
import { GlobalLayoutSider } from './component/sider';
|
||||
|
||||
import sideSheetStyle from './side-sheet.module.less';
|
||||
|
||||
export const GlobalLayout: FC<PropsWithChildren<LayoutProps>> = ({
|
||||
hasSider,
|
||||
children,
|
||||
banner,
|
||||
...props
|
||||
}) => {
|
||||
const [sideSheetVisible, setSideSheetVisible] = useState(false);
|
||||
const { isResponsive, mobileTipsModal } = useLayoutResponsive();
|
||||
const location = useLocation();
|
||||
useEffect(() => {
|
||||
setSideSheetVisible(false);
|
||||
}, [location.pathname, location.search, isResponsive]);
|
||||
const siderContent = isResponsive ? (
|
||||
<SideSheet
|
||||
placement="left"
|
||||
visible={sideSheetVisible}
|
||||
className={sideSheetStyle['side-sheet']}
|
||||
closeOnEsc
|
||||
onCancel={() => {
|
||||
setSideSheetVisible(false);
|
||||
}}
|
||||
>
|
||||
<GlobalLayoutSider {...props} key="GlobalLayoutSider" />
|
||||
</SideSheet>
|
||||
) : (
|
||||
<GlobalLayoutSider {...props} key="GlobalLayoutSider" />
|
||||
);
|
||||
|
||||
return (
|
||||
<GlobalLayoutProvider
|
||||
value={{
|
||||
sideSheetVisible,
|
||||
setSideSheetVisible,
|
||||
}}
|
||||
>
|
||||
{banner}
|
||||
<Layout
|
||||
className={cls(
|
||||
'flex !flex-row items-stretch w-full coz-bg-plus',
|
||||
banner ? 'h-[calc(100%_-_30px)]' : 'h-full',
|
||||
)}
|
||||
>
|
||||
{hasSider ? siderContent : null}
|
||||
<Layout className="flex-1 relative flex flex-col overflow-x-hidden coz-bg-plus">
|
||||
{children}
|
||||
</Layout>
|
||||
{mobileTipsModal}
|
||||
</Layout>
|
||||
</GlobalLayoutProvider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
.side-sheet {
|
||||
:global {
|
||||
.semi-sidesheet-inner {
|
||||
width: fit-content;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.semi-sidesheet-header {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.semi-sidesheet-body {
|
||||
width: fit-content;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sub-menu-resize {
|
||||
cursor: col-resize;
|
||||
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
||||
width: 12px;
|
||||
height: 100%;
|
||||
margin-right: -6px;
|
||||
|
||||
.sub-menu-resize-line {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 6px;
|
||||
|
||||
width: 1px;
|
||||
height: calc(100% - 24px);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.sub-menu-resize-line {
|
||||
background-color: var(--coz-stroke-plus);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 { type ReactNode } from 'react';
|
||||
|
||||
export interface RenderButtonProps {
|
||||
onClick?: () => void;
|
||||
icon: ReactNode;
|
||||
dataTestId?: string;
|
||||
}
|
||||
export interface LayoutButtonItem {
|
||||
icon: ReactNode;
|
||||
tooltip: string;
|
||||
portal?: ReactNode;
|
||||
onClick?: () => void;
|
||||
dataTestId?: string;
|
||||
className?: string;
|
||||
iconClass?: string;
|
||||
renderButton?: (props: RenderButtonProps) => ReactNode;
|
||||
}
|
||||
|
||||
export interface LayoutMenuItem {
|
||||
title: string;
|
||||
icon: ReactNode;
|
||||
activeIcon: ReactNode;
|
||||
path: string | string[];
|
||||
dataTestId?: string;
|
||||
}
|
||||
|
||||
export type LayoutAccountMenuItem =
|
||||
| {
|
||||
prefixIcon?: ReactNode;
|
||||
title: string;
|
||||
extra?: ReactNode;
|
||||
onClick: () => void;
|
||||
dataTestId?: string;
|
||||
}
|
||||
| ReactNode;
|
||||
|
||||
export interface LayoutOverrides {
|
||||
feedbackUrl?: string;
|
||||
}
|
||||
|
||||
export interface LayoutProps {
|
||||
hasSider: boolean;
|
||||
actions?: LayoutButtonItem[];
|
||||
menus?: LayoutMenuItem[];
|
||||
extras?: LayoutButtonItem[];
|
||||
onClickLogo?: () => void;
|
||||
banner?: ReactNode;
|
||||
footer?: ReactNode;
|
||||
}
|
||||
|
||||
export interface GlobalLayoutContext {
|
||||
sideSheetVisible: boolean;
|
||||
setSideSheetVisible: (visible: boolean) => void;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 { EVENT_NAMES, sendTeaEvent } from '@coze-arch/bot-tea';
|
||||
|
||||
export const reportNavClick = (title: string) => {
|
||||
sendTeaEvent(EVENT_NAMES.tab_click, { content: title });
|
||||
sendTeaEvent(EVENT_NAMES.coze_space_sidenavi_ck, {
|
||||
item: title,
|
||||
navi_type: 'prime',
|
||||
need_login: true,
|
||||
have_access: true,
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 { IconButton } from '@coze-arch/coze-design';
|
||||
import { IconSideFoldOutlined } from '@coze-arch/bot-icons';
|
||||
|
||||
import { useOpenGlobalLayoutSideSheet } from './global-layout/hooks';
|
||||
|
||||
// 用于在移动端模式开启侧边栏
|
||||
export const SideSheetMenu = () => {
|
||||
const open = useOpenGlobalLayoutSideSheet();
|
||||
|
||||
return (
|
||||
<IconButton
|
||||
color="secondary"
|
||||
icon={<IconSideFoldOutlined className="coz-fg-primary text-base" />}
|
||||
onClick={open}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SideSheetMenu;
|
||||
18
frontend/packages/foundation/layout/src/hooks/index.ts
Normal file
18
frontend/packages/foundation/layout/src/hooks/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**store */
|
||||
export { useMobileTips } from './use-mobile-tips';
|
||||
@@ -0,0 +1,9 @@
|
||||
.mobile-tips-span {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 400;
|
||||
line-height: 1rem;
|
||||
color: rgb(29 28 35 / 80%);
|
||||
text-align: center;
|
||||
|
||||
/* 133.333% */
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 from 'react';
|
||||
|
||||
import { I18n } from '@coze-arch/i18n';
|
||||
import { useUIModal } from '@coze-arch/bot-semi';
|
||||
|
||||
import s from './index.module.less';
|
||||
|
||||
export interface UseMobileTipsReturnType {
|
||||
open: () => void;
|
||||
close: () => void;
|
||||
node: JSX.Element;
|
||||
}
|
||||
|
||||
export const useMobileTips = (): UseMobileTipsReturnType => {
|
||||
const { open, close, modal } = useUIModal({
|
||||
title: I18n.t('landing_mobile_popup_title'),
|
||||
okText: I18n.t('landing_mobile_popup_button'),
|
||||
// width: 456,
|
||||
centered: true,
|
||||
hideCancelButton: true,
|
||||
isMobile: true,
|
||||
onOk: () => {
|
||||
close();
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
node: modal(
|
||||
<span className={s['mobile-tips-span']}>
|
||||
{I18n.t('landing_mobile_popup_context')}
|
||||
</span>,
|
||||
),
|
||||
open: () => {
|
||||
open();
|
||||
},
|
||||
close: () => {
|
||||
close();
|
||||
},
|
||||
};
|
||||
};
|
||||
23
frontend/packages/foundation/layout/src/index.tsx
Normal file
23
frontend/packages/foundation/layout/src/index.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/** 布局框架 */
|
||||
export { SideSheetMenu } from './components/side-sheet-menu';
|
||||
export { GlobalError } from './components/global-error';
|
||||
export { BackButton } from './components/back-button';
|
||||
export { GlobalLayout } from './components/global-layout';
|
||||
export { GlobalLayoutAccountDropdown } from './components/account-dropdown';
|
||||
export { reportNavClick } from './components/global-layout/utils';
|
||||
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 { devtools } from 'zustand/middleware';
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface SignMobileStore {
|
||||
/** 标识有没有弹出过提示 */
|
||||
mobileTips: boolean;
|
||||
}
|
||||
|
||||
interface SignMobileAction {
|
||||
setMobileTips: (tipsFlag: boolean) => void;
|
||||
}
|
||||
|
||||
export const useSignMobileStore = create<SignMobileStore & SignMobileAction>()(
|
||||
devtools(
|
||||
set => ({
|
||||
mobileTips: false,
|
||||
setMobileTips: flag => {
|
||||
set({ mobileTips: flag });
|
||||
},
|
||||
}),
|
||||
{
|
||||
enabled: IS_DEV_MODE,
|
||||
name: 'botStudio.signMobile',
|
||||
},
|
||||
),
|
||||
);
|
||||
17
frontend/packages/foundation/layout/src/store/index.ts
Normal file
17
frontend/packages/foundation/layout/src/store/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { useSignMobileStore } from './bot-mobile';
|
||||
17
frontend/packages/foundation/layout/src/typings.d.ts
vendored
Normal file
17
frontend/packages/foundation/layout/src/typings.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/// <reference types='@coze-arch/bot-typings' />
|
||||
69
frontend/packages/foundation/layout/tsconfig.build.json
Normal file
69
frontend/packages/foundation/layout/tsconfig.build.json
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"compilerOptions": {
|
||||
"types": [],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true,
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"tsBuildInfoFile": "./dist/tsconfig.build.tsbuildinfo"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [
|
||||
{
|
||||
"path": "../account-adapter/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/bot-error/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/bot-hooks/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/bot-tea/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/bot-typings/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/bot-utils/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/foundation-sdk/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/i18n/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/idl/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/logger/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../arch/web-context/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../components/bot-icons/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../components/bot-semi/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../config/eslint-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../config/stylelint-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../config/ts-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../../../config/vitest-config/tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "../space-store/tsconfig.build.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
15
frontend/packages/foundation/layout/tsconfig.json
Normal file
15
frontend/packages/foundation/layout/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"composite": true
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.misc.json"
|
||||
}
|
||||
],
|
||||
"exclude": ["**/*"]
|
||||
}
|
||||
24
frontend/packages/foundation/layout/tsconfig.misc.json
Normal file
24
frontend/packages/foundation/layout/tsconfig.misc.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"extends": "@coze-arch/ts-config/tsconfig.web.json",
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"include": [
|
||||
"__tests__",
|
||||
"stories",
|
||||
"vitest.config.ts",
|
||||
"tailwind.config.ts",
|
||||
"../../studio/coze-pro-volcano/src/components/coze-pro-user-rights"
|
||||
],
|
||||
"exclude": ["./dist"],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.build.json"
|
||||
}
|
||||
],
|
||||
"compilerOptions": {
|
||||
"rootDir": "./",
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/globals"],
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": true
|
||||
}
|
||||
}
|
||||
22
frontend/packages/foundation/layout/vitest.config.ts
Normal file
22
frontend/packages/foundation/layout/vitest.config.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 { defineConfig } from '@coze-arch/vitest-config';
|
||||
|
||||
export default defineConfig({
|
||||
dirname: __dirname,
|
||||
preset: 'web',
|
||||
});
|
||||
Reference in New Issue
Block a user