diff --git a/.vscode/settings.json b/.vscode/settings.json index b89a916..8d2eafa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,7 +6,7 @@ "editor.formatOnSaveMode": "file", "files.eol": "\n", "[typescriptreact]": { - "editor.defaultFormatter": "dbaeumer.vscode-eslint" + "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" }, "[scss]": { "editor.defaultFormatter": "esbenp.prettier-vscode" diff --git a/package.json b/package.json index c01f418..2fa6b13 100644 --- a/package.json +++ b/package.json @@ -16,11 +16,13 @@ "@babel/parser": "^7.21.2", "@commitlint/cli": "^17.3.0", "@commitlint/config-conventional": "^17.3.0", + "@types/babel__core": "^7.20.2", + "@typescript-eslint/parser": "^6.4.1", "babel-plugin-jsx": "^1.2.0", "es-dirname": "^0.1.0", "eslint-config-psfe": "1.0.5", - "@typescript-eslint/parser": "^6.4.1", - "husky": "^8.0.2" + "husky": "^8.0.2", + "prettier": "^2.8.8" }, "dependencies": { "core-js": "^3.25.1", diff --git a/packages/demo/package.json b/packages/demo/package.json index bce830d..be93a05 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -39,6 +39,7 @@ "@mdx-js/loader": "^2.1.5", "babel-plugin-prismjs": "^2.1.0", "gsap": "^3.11.5", + "node-polyfill-webpack-plugin": "^2.0.1", "pivot-design": "workspace:*", "pivot-design-icon": "workspace:*", "prismjs": "^1.29.0", diff --git a/packages/demo/src/components/Editor/.catalog.ts b/packages/demo/src/components/Editor/.catalog.ts index a63628d..dd53dd6 100644 --- a/packages/demo/src/components/Editor/.catalog.ts +++ b/packages/demo/src/components/Editor/.catalog.ts @@ -1 +1 @@ -export const list = [] \ No newline at end of file +export const list = [{"h2":"基本用法","h3":[]},{"h2":"多文件模型","h3":[]}] \ No newline at end of file diff --git a/packages/demo/src/components/Editor/demo/Base.tsx b/packages/demo/src/components/Editor/demo/Base.tsx index c1c81e0..29c60ac 100644 --- a/packages/demo/src/components/Editor/demo/Base.tsx +++ b/packages/demo/src/components/Editor/demo/Base.tsx @@ -1,10 +1,10 @@ -import { Editor } from 'pivot-design'; +import { MonacoEditor } from 'pivot-design'; import React, { useState } from 'react'; const App: React.FC = () => { return ( <> - + ); }; diff --git a/packages/demo/src/components/Editor/demo/Multiple.tsx b/packages/demo/src/components/Editor/demo/Multiple.tsx new file mode 100644 index 0000000..a9323a6 --- /dev/null +++ b/packages/demo/src/components/Editor/demo/Multiple.tsx @@ -0,0 +1,11 @@ +import { MultipleEditor } from 'pivot-design'; +import React, { useState } from 'react'; + +const App: React.FC = () => { + return ( + <> + + + ); +}; +export default App; diff --git a/packages/demo/src/components/Editor/index.mdx b/packages/demo/src/components/Editor/index.mdx index 237561f..ef6e696 100644 --- a/packages/demo/src/components/Editor/index.mdx +++ b/packages/demo/src/components/Editor/index.mdx @@ -4,8 +4,15 @@ import Props from './props.mdx'; ## 基本用法 - +{/* + */} + + +## 多文件模型 + + + diff --git a/packages/demo/src/components/Editor/props.mdx b/packages/demo/src/components/Editor/props.mdx index ca0a4dc..5484e84 100644 --- a/packages/demo/src/components/Editor/props.mdx +++ b/packages/demo/src/components/Editor/props.mdx @@ -1,7 +1,4 @@ -import Table from '../table.tsx'; - -export const params = []; - -## API 列表 - - +import Table from '../table.tsx' + export const params = [] + ## API 列表 +
\ No newline at end of file diff --git a/packages/demo/src/components/Tabs/.catalog.ts b/packages/demo/src/components/Tabs/.catalog.ts new file mode 100644 index 0000000..ede4d6f --- /dev/null +++ b/packages/demo/src/components/Tabs/.catalog.ts @@ -0,0 +1 @@ +export const list = [{"h2":"基本用法","h3":[]},{"h2":"卡片样式","h3":[]}] \ No newline at end of file diff --git a/packages/demo/src/components/Tabs/demo/Base.tsx b/packages/demo/src/components/Tabs/demo/Base.tsx new file mode 100644 index 0000000..d3a01b7 --- /dev/null +++ b/packages/demo/src/components/Tabs/demo/Base.tsx @@ -0,0 +1,28 @@ +import { Tabs } from 'pivot-design'; +import React, { useState } from 'react'; + +const App: React.FC = () => { + const items = [ + { + key: '1', + label: 'Tab 1', + children: 'Content of Tab Pane 1', + }, + { + key: '2', + label: 'Tab Second', + children: 'Content of Tab Pane 2', + }, + { + key: '3', + label: 'Tab 3', + children: 'Content of Tab Pane 3', + }, + ]; + return ( + <> + + + ); +}; +export default App; diff --git a/packages/demo/src/components/Tabs/demo/CardType.tsx b/packages/demo/src/components/Tabs/demo/CardType.tsx new file mode 100644 index 0000000..bfe602f --- /dev/null +++ b/packages/demo/src/components/Tabs/demo/CardType.tsx @@ -0,0 +1,28 @@ +import { Tabs } from 'pivot-design'; +import React, { useState } from 'react'; + +const App: React.FC = () => { + const items = [ + { + key: '1', + label: 'Tab 1', + children: 'Content of Tab Pane 1', + }, + { + key: '2', + label: 'Tab Second', + children: 'Content of Tab Pane 2', + }, + { + key: '3', + label: 'Tab 3', + children: 'Content of Tab Pane 3', + }, + ]; + return ( + <> + + + ); +}; +export default App; diff --git a/packages/demo/src/components/Tabs/index.mdx b/packages/demo/src/components/Tabs/index.mdx new file mode 100644 index 0000000..b262a39 --- /dev/null +++ b/packages/demo/src/components/Tabs/index.mdx @@ -0,0 +1,17 @@ +import Props from './props.mdx'; + +# Tabs + +## 基本用法 + + + + + +## 卡片样式 type = `'card'` + + + + + + diff --git a/packages/demo/src/components/Tabs/props.mdx b/packages/demo/src/components/Tabs/props.mdx new file mode 100644 index 0000000..868cc5a --- /dev/null +++ b/packages/demo/src/components/Tabs/props.mdx @@ -0,0 +1,4 @@ +import Table from '../table.tsx' + export const params = [{"key":"className","value":": string","optional":true,"version":"1.0.0","description":"自定义类名"},{"key":"style","value":": React.CSSProperties","optional":true,"version":"1.0.0","description":"自定义样式"},{"key":"contentStyle","value":": CSSProperties","optional":true,"version":"1.0.0","description":"自定义Tabs内容容器样式"},{"key":"renderCommonContent","value":": (item: TabsItemProps) => ReactNode","optional":true,"version":"1.0.0","description":"自定义渲染Tabs公共内容"},{"key":"items","value":": TabsItemProps[]","version":"1.0.0","description":"标签页项最主要的数据,用于渲染标签页","default":"[]"},{"key":"type","value":": 'default' | 'card'","optional":true,"version":"1.0.0","description":"标签种类样式","default":"\"default\""},{"key":"value","value":": UniqueId","optional":true,"version":"1.0.0","description":"当前激活的标签页索引","default":"0"},{"key":"onChange","value":": (tab: UniqueId) => void","optional":true,"version":"1.0.0","description":"标签页发生改变时触发的监听函数","default":"undefined"}] + ## API 列表 +
\ No newline at end of file diff --git a/packages/demo/src/components/_CodeBlock/index.scss b/packages/demo/src/components/_CodeBlock/index.scss index e93ba3c..1140cf1 100644 --- a/packages/demo/src/components/_CodeBlock/index.scss +++ b/packages/demo/src/components/_CodeBlock/index.scss @@ -1,5 +1,5 @@ .pivot-code-box { - max-width: 900px; + max-width: 1000px; position: relative; margin: 30px 0; overflow: hidden; @@ -20,10 +20,7 @@ flex-wrap: wrap; justify-content: center; align-items: center; - background-color: var(--semi-color-fill-0); - > :not(.PIVOT-draggable-item) { - margin: 0 24px; - } + background-color: var(--semi-color-bg-2); } .line-numbers { diff --git a/packages/demo/src/pages/components/index.scss b/packages/demo/src/pages/components/index.scss index 6d8137d..4a03d89 100644 --- a/packages/demo/src/pages/components/index.scss +++ b/packages/demo/src/pages/components/index.scss @@ -12,7 +12,6 @@ @media only screen and (min-width: 1900px) { &-content { - justify-content: center; padding: 50px 12vw; } } @@ -30,7 +29,7 @@ padding: 8px 0.6rem; padding-top: 50px; min-width: 240px; - background: var(--semi-color-bg-1); + background: var(--semi-color-bg-2); font-size: 14px; .demo-item { diff --git a/packages/demo/src/pages/components/tabs.tsx b/packages/demo/src/pages/components/tabs.tsx new file mode 100644 index 0000000..a26d84b --- /dev/null +++ b/packages/demo/src/pages/components/tabs.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { Tabs } from 'pivot-design'; +import TabsMdx from '@/components/Tabs/index.mdx'; +import CodeBlock from '@/components/_CodeBlock/codeBlock'; +import { list } from '@/components/Input/.catalog'; +import { renderCatalog } from '@/utils'; +const TabsPage: React.FC = () => { + return ( + <> + +
{renderCatalog(list)}
+ + ); +}; +export default TabsPage; diff --git a/packages/demo/src/routers/index.tsx b/packages/demo/src/routers/index.tsx index 85ba0f2..eb5aac8 100644 --- a/packages/demo/src/routers/index.tsx +++ b/packages/demo/src/routers/index.tsx @@ -13,6 +13,7 @@ import Transition from '@/pages/components/transition'; import Skeleton from '@/pages/components/skeleton'; import Modal from '@/pages/components/modal'; import Editor from '@/pages/components/editor'; +import Tabs from '@/pages/components/tabs'; import { generateComponentRouter } from '@/utils'; const router: RouteObject[] = [ @@ -39,6 +40,7 @@ const router: RouteObject[] = [ skeleton: , modal: , editor: , + tabs: , }), }, ], diff --git a/packages/demo/src/utils/path.ts b/packages/demo/src/utils/path.ts index 41ee887..764cf45 100644 --- a/packages/demo/src/utils/path.ts +++ b/packages/demo/src/utils/path.ts @@ -11,10 +11,6 @@ export const ComponentPath = [ path: 'card', title: 'Card 卡片', }, - { - path: 'button', - title: 'Card 卡片', - }, { path: 'draggable', title: 'Draggable 拖拽列表', @@ -43,6 +39,10 @@ export const ComponentPath = [ path: 'transition', title: 'Transition 元素动画', }, + { + path: 'tabs', + title: 'Tabs 标签页', + }, { path: 'editor', title: 'Editor 代码编辑器', diff --git a/packages/demo/webpack.config.js b/packages/demo/webpack.config.js index b368935..3e74ac4 100644 --- a/packages/demo/webpack.config.js +++ b/packages/demo/webpack.config.js @@ -1,5 +1,6 @@ const path = require('path'); //node环境当前路径 const HtmlWebpackPlugin = require('html-webpack-plugin'); //模板文件插件,能够自动将打包的css和js加入到模板文件中 +const NodePolyfillPlugin = require('node-polyfill-webpack-plugin'); module.exports = { entry: { app: './index.tsx', //找到咱们刚才在src下面的入口文件 @@ -14,6 +15,11 @@ module.exports = { alias: { '@': path.resolve(__dirname, './src'), }, + fallback: { + fs: false, + assert: require.resolve('assert'), + path: require.resolve('path-browserify'), + }, }, devServer: { port: 8080, @@ -86,5 +92,6 @@ module.exports = { removeComments: true, // 移除注释 }, }), + new NodePolyfillPlugin(), ], }; diff --git a/packages/design-props/components/editor.ts b/packages/design-props/components/editor.ts new file mode 100644 index 0000000..afbe222 --- /dev/null +++ b/packages/design-props/components/editor.ts @@ -0,0 +1,5 @@ +import React from 'react'; +import { PivotDesignProps } from './'; +export interface MultipleEditorProps extends PivotDesignProps { + +} diff --git a/packages/design-props/components/tabs.ts b/packages/design-props/components/tabs.ts new file mode 100644 index 0000000..36c0b8c --- /dev/null +++ b/packages/design-props/components/tabs.ts @@ -0,0 +1,47 @@ +import { CSSProperties, ReactNode } from 'react'; +import { PivotDesignProps } from './'; + +type UniqueId = string | number; + +export interface TabsItemProps { + label: UniqueId; + key: UniqueId; + children?: ReactNode; + [x: string]: any; +} +export interface TabsProps extends PivotDesignProps { + /** + * @version 1.0.0 + * @description 自定义Tabs内容容器样式 + */ + contentStyle?: CSSProperties; + /** + * @version 1.0.0 + * @description 自定义渲染Tabs公共内容 + */ + renderCommonContent?: (item: TabsItemProps) => ReactNode; + /** + * @version 1.0.0 + * @description 标签页项最主要的数据,用于渲染标签页 + * @default [] + */ + items: TabsItemProps[]; + /** + * @version 1.0.0 + * @description 标签种类样式 + * @default "default" + */ + type?: 'default' | 'card'; + /** + * @version 1.0.0 + * @description 当前激活的标签页索引 + * @default 0 + */ + value?: UniqueId; + /** + * @version 1.0.0 + * @description 标签页发生改变时触发的监听函数 + * @default undefined + */ + onChange?: (tab: UniqueId) => void; +} diff --git a/packages/design-props/index.ts b/packages/design-props/index.ts index 5f2f2f6..541fb72 100644 --- a/packages/design-props/index.ts +++ b/packages/design-props/index.ts @@ -7,3 +7,5 @@ export * from './components/skeleton'; export * from './components/popover'; export * from './components/modal'; export * from './components/transition'; +export * from './components/tabs'; +export * from './components/editor'; diff --git a/packages/design/.prettierrc b/packages/design/.prettierrc index f65aabc..40b6472 100644 --- a/packages/design/.prettierrc +++ b/packages/design/.prettierrc @@ -1,4 +1,4 @@ { "singleQuote": true, - "printWidth": 120 + "printWidth": 80 } \ No newline at end of file diff --git a/packages/design/babel.config.js b/packages/design/babel.config.js new file mode 100644 index 0000000..5afba9f --- /dev/null +++ b/packages/design/babel.config.js @@ -0,0 +1,19 @@ +module.exports = { + // 执行顺序由右往左,所以先处理ts,再处理jsx,最后再试一下babel转换为低版本语法 + presets: [ + [ + '@babel/preset-env', + { + // 设置兼容目标浏览器版本,这里可以不写,babel-loader会自动寻找上面配置好的文件.browserslistrc + // "targets": { + // "chrome": 35, + // "ie": 9 + // }, + useBuiltIns: 'usage', // 根据配置的浏览器兼容,以及代码中使用到的api进行引入polyfill按需添加 + corejs: 3, // 配置使用core-js低版本 + }, + ], + ['@babel/preset-react', { runtime: 'automatic' }], + '@babel/preset-typescript', + ], +}; diff --git a/packages/design/components/Modal/index.scss b/packages/design/components/Modal/index.scss index d8dc4d7..f302340 100644 --- a/packages/design/components/Modal/index.scss +++ b/packages/design/components/Modal/index.scss @@ -31,7 +31,7 @@ width: 448px; border-radius: 16px; color: var(--semi-color-text-0); - background-color: var(--semi-color-bg-3); + background-color: var(--semi-color-bg-4); .#{$prefix}-modal-cancel { } diff --git a/packages/design/components/MonacoEditor/Editor.tsx b/packages/design/components/MonacoEditor/Editor.tsx new file mode 100644 index 0000000..404ca6b --- /dev/null +++ b/packages/design/components/MonacoEditor/Editor.tsx @@ -0,0 +1,73 @@ +import Editor, { EditorProps, OnChange, OnMount } from '@monaco-editor/react'; +import { useRef } from 'react'; +import { CodeType, MessageChangeType, Module, ThemeType } from './types'; +import InitPlugin from './plugins/initPlugin'; +import { useDebounce } from '../hooks'; +import Preview from './components/Preview'; +import { useWorkers } from './workers/useWorkers'; +interface MocacoEditorProps extends EditorProps { + modules?: Module[]; +} + +const FILENAME = 'index.tsx'; + +const MonacoEditor = (props: MocacoEditorProps) => { + const { compilerWorker } = useWorkers(); + const { + height = 400, + defaultLanguage = CodeType.ts, + path = FILENAME, + theme = ThemeType['Visual Studio Dark'], + modules, + onChange, + defaultValue, + ...rest + } = props; + + const EditorRef = useRef['0']>(); + const MonacoRef = useRef['1']>(); + + const handleEditorDidMount: OnMount = (editor, monaco) => { + EditorRef.current = editor; + MonacoRef.current = monaco; + compilerWorker.postMessage({ + type: MessageChangeType.Compile, + data: { + filename: path, + code: defaultValue, + modules, + }, + }); + InitPlugin(monaco); + }; + + const handleEditorChange: OnChange = useDebounce((value, e) => { + onChange?.(value ?? '', e); + compilerWorker.postMessage({ + type: MessageChangeType.Compile, + data: { + filename: path, + code: value, + modules, + }, + }); + }, 300); + + return ( +
+ + +
+ ); +}; + +export default MonacoEditor; diff --git a/packages/design/components/MonacoEditor/MultipleEditor.tsx b/packages/design/components/MonacoEditor/MultipleEditor.tsx new file mode 100644 index 0000000..428e0a6 --- /dev/null +++ b/packages/design/components/MonacoEditor/MultipleEditor.tsx @@ -0,0 +1,66 @@ +import { FC } from 'react'; +import { CodeType, Module } from './types'; +import { testCode } from './code'; +import Tabs from '../Tabs'; +import MonacoEditor from './Editor'; +import { useLocalStorage } from '../hooks'; +import { MultipleEditorProps } from 'pivot-design-props'; + +const FILENAME1 = 'index.tsx'; +const FILENAME2 = 'index.scss'; +const items: Module[] = [ + { + key: FILENAME1, + label: FILENAME1, + language: CodeType.ts, + value: testCode, + entry: true, + }, + { + key: FILENAME2, + label: FILENAME2, + language: CodeType.scss, + value: `section { + h1 { + color: white; + } + }`, + }, +]; +const MultipleEditor: FC = ({ style }) => { + const [tabsValue, setTabsValue] = useLocalStorage('tabs', { + defaultValue: items, + }); + + return ( +
+ { + return ( + { + if (!value) return; + const updatedTabs: Module[] = tabsValue.map((tab) => { + if (tab.key === item.key) { + return { ...tab, value }; + } + return tab; + }); + setTabsValue(updatedTabs); + }} + /> + ); + }} + type="card" + contentStyle={{ paddingTop: 0 }} + /> +
+ ); +}; + +export default MultipleEditor; diff --git a/packages/design/components/MonacoEditor/code/index.ts b/packages/design/components/MonacoEditor/code/index.ts new file mode 100644 index 0000000..17b7b2b --- /dev/null +++ b/packages/design/components/MonacoEditor/code/index.ts @@ -0,0 +1,3 @@ +export { default as useEventCode } from './useEvent'; +export { default as usePreviousCode } from './usePrevious'; +export { default as testCode } from './test'; diff --git a/packages/design/components/MonacoEditor/code/test.ts b/packages/design/components/MonacoEditor/code/test.ts new file mode 100644 index 0000000..9d40cff --- /dev/null +++ b/packages/design/components/MonacoEditor/code/test.ts @@ -0,0 +1,13 @@ +export default `import ReactDom from 'react-dom/client'; +import React from 'react'; +import './index.scss'; +function App() { + return ( +
+

Pivot Design Editor

+
+ ); +} + +ReactDom.createRoot(document.getElementById('root')).render() +`; diff --git a/packages/design/components/MonacoEditor/code/useEvent.ts b/packages/design/components/MonacoEditor/code/useEvent.ts new file mode 100644 index 0000000..6babb4f --- /dev/null +++ b/packages/design/components/MonacoEditor/code/useEvent.ts @@ -0,0 +1,61 @@ +export default `import ReactDom from 'react-dom/client'; +import React, { + useEffect, + useCallback, + useLayoutEffect, + useRef, + useState +} from "react"; +function getRandomColor() { + const colors = ["green", "blue", "purple", "red", "pink"]; + return colors[Math.floor(Math.random() * colors.length)]; +} +function useEvent(handler) { + // todo + const handlerRef = useRef(handler); + useEffect(() => { + handlerRef.current = handler; + }); + return useCallback(() => { + const fn = handlerRef.current; + fn(); + }, []); +} +export default function App() { + const [color, setColor] = useState(getRandomColor()); + const ButtonRef = useRef(null); + const handleClick = () => { + function getNewColor() { + const newColor = getRandomColor(); + if (color === newColor) { + getNewColor(); + } else { + setColor(newColor); + } + } + getNewColor(); + console.log("===", color); + }; + // todo + const handleEventClick = useEvent(handleClick); + useEffect(() => { + ButtonRef.current.addEventListener("click", handleEventClick); + }, []); + return ( +
+

useEvent

+ +
+
+

+

Current: {color}
+
+
+
+ ); +} + +ReactDom.createRoot(document.getElementById('root')).render() +`; diff --git a/packages/design/components/MonacoEditor/code/usePrevious.ts b/packages/design/components/MonacoEditor/code/usePrevious.ts new file mode 100644 index 0000000..95908e4 --- /dev/null +++ b/packages/design/components/MonacoEditor/code/usePrevious.ts @@ -0,0 +1,45 @@ +export default `import { useEffect, useRef, useState } from "react"; +import "./index.scss"; +function getRandomColor() { + const colors = ["green", "blue", "purple", "red", "pink"]; + return colors[Math.floor(Math.random() * colors.length)]; +} +function usePrevious(color: string) { + // todo +} +export default function App() { + const [color, setColor] = useState(getRandomColor()); + const previousColor = usePrevious(color); + + const handleClick = () => { + function getNewColor() { + const newColor = getRandomColor(); + if (color === newColor) { + getNewColor(); + } else { + setColor(newColor); + } + } + getNewColor(); + }; + + return ( +
+

usePrevious

+ +
+
+

+

Previous: {previousColor}
+
+
+

+

Current: {color}
+
+
+
+ ); +} +`; diff --git a/packages/design/components/MonacoEditor/components/Preview/index.html b/packages/design/components/MonacoEditor/components/Preview/index.html new file mode 100644 index 0000000..d2b5e66 --- /dev/null +++ b/packages/design/components/MonacoEditor/components/Preview/index.html @@ -0,0 +1,52 @@ + + + + + + preview + + + +
+ + + + diff --git a/packages/design/components/MonacoEditor/components/Preview/index.tsx b/packages/design/components/MonacoEditor/components/Preview/index.tsx new file mode 100644 index 0000000..686c7f4 --- /dev/null +++ b/packages/design/components/MonacoEditor/components/Preview/index.tsx @@ -0,0 +1,28 @@ +import iframe from '!!raw-loader!./index.html'; +import { useRef } from 'react'; +import { MessageChangeType } from '../../types'; +const src = URL.createObjectURL(new Blob([iframe], { type: 'text/html' })); + +const Preview = ({ compiler }: { compiler: Worker }) => { + const iframeRef = useRef(null); + compiler.addEventListener('message', (e) => { + const { data, type } = e.data; + if (type === MessageChangeType.Compile) { + iframeRef.current?.contentWindow?.postMessage({ + type, + data, + }); + } + }); + + return ( +