diff --git a/package.json b/package.json index e5b7f77a7..ca0d9b6ae 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "author": "tdesign", "license": "MIT", "dependencies": { + "@babel/plugin-proposal-decorators": "^7.23.2", "@babel/plugin-syntax-jsx": "^7.22.5", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", @@ -150,7 +151,7 @@ "omi-twind": "^0.0.4", "prismjs": "^1.24.0", "sortablejs": "^1.15.0", - "tdesign-icons-omi": "^0.0.10", + "tdesign-icons-omi": "^0.0.12", "tinycolor2": "^1.6.0", "twind": "^0.16.16", "validator": "^13.9.0" diff --git a/site/plugin-api-doc/demo.js b/site/plugin-api-doc/demo.js new file mode 100644 index 000000000..625b30ec2 --- /dev/null +++ b/site/plugin-api-doc/demo.js @@ -0,0 +1,39 @@ +/* eslint-disable */ +import path from 'path' + +export default function renderDemo(md, container) { + console.log('renderDemo') + md.use(container, 'demo', { + validate(params) { + return params.trim().match(/^demo\s+([\\/.\w-]+)(\s+(.+?))?(\s+--dev)?$/) + }, + render(tokens, idx) { + if (tokens[idx].nesting === 1) { + const match = tokens[idx].info.trim().match(/^demo\s+([\\/.\w-]+)(\s+(.+?))?(\s+--dev)?$/) + const [, demoPath, componentName = ''] = match + const demoPathOnlyLetters = demoPath.replace(/[^a-zA-Z\d]/g, '') + const demoName = path.basename(demoPath) + const demoDefName = `Demo${demoPathOnlyLetters}` + const demoCodeDefName = `Demo${demoPathOnlyLetters}Code` + const tpl = ` + +
+ +
+
+ <${demoDefName} /> +
+
+ ` + + tokens.tttpl = tpl + + return `
` + } + if (tokens.tttpl) { + return `${tokens.tttpl || ''}
` + } + return '' + }, + }) +} diff --git a/site/plugin-api-doc/index.js b/site/plugin-api-doc/index.js new file mode 100644 index 000000000..e6ce225dc --- /dev/null +++ b/site/plugin-api-doc/index.js @@ -0,0 +1,25 @@ +import vitePluginTdoc from 'vite-plugin-tdoc' +import renderDemo from './demo' +import transforms from './transforms' + +export default () => + vitePluginTdoc({ + transforms, // 解析markdown 数据 + markdown: { + anchor: { + tabIndex: false, + config: (anchor) => ({ + permalink: anchor.permalink.linkInsideHeader({ symbol: '' }), + }), + }, + toc: { + listClass: 'tdesign-toc_list', + itemClass: 'tdesign-toc_list_item', + linkClass: 'tdesign-toc_list_item_a', + containerClass: 'tdesign-toc_container', + }, + container(md, container) { + renderDemo(md, container) + }, + }, + }) diff --git a/site/plugin-api-doc/md-to-omi.js b/site/plugin-api-doc/md-to-omi.js new file mode 100644 index 000000000..73732c9e2 --- /dev/null +++ b/site/plugin-api-doc/md-to-omi.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +import fs from 'fs' +import path from 'path' +import matter from 'gray-matter' +import { getGitTimestamp } from '../../src/_common/docs/compile' +import { transformSync } from '@babel/core' + +const DEFAULT_TABS = [ + { tab: 'demo', name: '示例' }, + { tab: 'api', name: 'API' }, + { tab: 'design', name: '指南' }, +] + +const DEFAULT_EN_TABS = [ + { tab: 'demo', name: 'DEMO' }, + { tab: 'api', name: 'API' }, + { tab: 'design', name: 'Guideline' }, +] + +export default async function mdToOmi(options) { + const mdSegment = await customRender(options) + + const omiSource = ` + import { h, tag, WeElement } from 'omi' + + export default class ButtonMd extends WeElement { + render() { + return
+ } + } + ` + + const result = transformSync(omiSource, { + babelrc: false, + configFile: false, + sourceMaps: true, + generatorOpts: { + decoratorsBeforeExport: true, + }, + plugins: [[require('@babel/plugin-transform-react-jsx'), { isTSX: true }]], + }) + + return { code: result.code, map: result.map } +} + +// 解析 markdown 内容 +async function customRender({ source, file, md }) { + const { content, data } = matter(source) + const lastUpdated = (await getGitTimestamp(file)) || Math.round(fs.statSync(file).mtimeMs) + // console.log('data', data); + const isEn = file.endsWith('en-US.md') + + // md top data + const pageData = { + spline: '', + toc: true, + title: '', + description: '', + isComponent: false, + tdDocHeader: true, + tdDocTabs: !isEn ? DEFAULT_TABS : DEFAULT_EN_TABS, + apiFlag: /#+\s*API/, + docClass: '', + lastUpdated, + designDocLastUpdated: lastUpdated, + ...data, + } + + // md filename + const reg = file.match(/([\w-]+)\.?([\w-]+)?\.md/) + const componentName = reg && reg[1] + + // split md + let [demoMd = '', apiMd = ''] = content.split(pageData.apiFlag) + + const mdSegment = { + ...pageData, + componentName, + apiMd: '', + } + + mdSegment.apiMd = md.render.call(md, `${pageData.toc ? '[toc]\n' : ''}${apiMd.replace(//g, '')}`).html + + return mdSegment +} diff --git a/site/plugin-api-doc/transforms.js b/site/plugin-api-doc/transforms.js new file mode 100644 index 000000000..ef8b9f743 --- /dev/null +++ b/site/plugin-api-doc/transforms.js @@ -0,0 +1,64 @@ +import path from 'path' +import fs from 'fs' + +import mdToOmi from './md-to-omi' + +let demoImports = {} +let demoCodesImports = {} + +export default { + before({ source, file }) { + const resourceDir = path.dirname(file) + const reg = file.match(/([\w-]+)\.?([\w-]+)?\.md/) + const fileName = reg && reg[0] + const componentName = reg && reg[1] + const localeName = reg && reg[2] + + // 统一换成 common 公共文档内容 + if (fileName && source.includes(':: BASE_DOC ::')) { + const localeDocPath = path.resolve(__dirname, `../../src/_common/docs/web/api/${fileName}`) + const defaultDocPath = path.resolve( + __dirname, + `../../src/_common/docs/web/api/${localeName ? `${componentName}.${localeName}` : componentName}.md`, + ) + let baseDoc = '' + + if (fs.existsSync(localeDocPath)) { + // 优先载入语言版本 + baseDoc = fs.readFileSync(localeDocPath, 'utf-8') + } else if (fs.existsSync(defaultDocPath)) { + // 回退中文默认版本 + baseDoc = fs.readFileSync(defaultDocPath, 'utf-8') + } else { + console.error(`未找到 ${defaultDocPath} 文件`) + } + source = source.replace(':: BASE_DOC ::', baseDoc) + } + + return source + }, + render({ source, file, md }) { + // console.log('source: ', source, ' file: ', file, ' md: ', md) + const demoDefsStr = Object.keys(demoImports) + .map((key) => demoImports[key]) + .join(';\n') + const demoCodesDefsStr = Object.keys(demoCodesImports) + .map((key) => demoCodesImports[key]) + .join(';\n') + + const demoInstallStr = Object.keys(demoImports).join(',') + const demoCodeInstallStr = Object.keys(demoCodesImports).join(',') + + const sfc = mdToOmi({ + md, + file, + source, + demoDefsStr, + demoCodesDefsStr, + demoInstallStr, + demoCodeInstallStr, + }) + + return sfc + }, +} diff --git a/site/src/components/web/button/button_md.tsx b/site/src/components/web/button/button_md.tsx new file mode 100644 index 000000000..f529c3290 --- /dev/null +++ b/site/src/components/web/button/button_md.tsx @@ -0,0 +1,185 @@ +import { h, tag, WeElement } from 'omi' +// import tdesign style +import css from '@common/style/web/docs.less' + +// import site web components +import 'tdesign-site-components' +import css1 from 'tdesign-site-components/lib/styles/style.css' +import css2 from 'tdesign-site-components/lib/styles/prism-theme.less' +import css3 from 'tdesign-site-components/lib/styles/prism-theme-dark.less' +@tag('button-md') +export default class ButtonMd extends WeElement { + static css = css + css1 + css2 + css3 + render(props: any) { + return ( +
+
+
    +
  1. + + Button Props + +
  2. +
+ +

+ Button Props +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
名称 类型 默认值 说明 必传
class String - 类名 N
style Object - + 样式 + N
block Boolean false 是否为块级元素 N
children TNode - + 按钮内容,同 content。TS 类型:string | TNode。 + + 通用类型定义 + + N
content TNode - + 按钮内容。TS 类型:string | TNode。 + + 通用类型定义 + + N
disabled Boolean false 禁用状态 N
ghost Boolean false 是否为幽灵按钮(镂空按钮) N
href String - + 跳转地址。href 存在时,按钮标签默认使用 <a> 渲染;如果指定了 tag + 则使用指定的标签渲染 + N
icon TElement - + 按钮内部图标,可完全自定义。TS 类型:TNode。 + + 通用类型定义 + + N
loading Boolean false 是否显示为加载状态 N
shape String rectangle按钮形状,有 4 种:长方形、正方形、圆角长方形、圆形。可选项:rectangle/square/round/circleN
size String medium + 组件尺寸。可选项:small/medium/large。TS 类型:SizeEnum。 + + 通用类型定义 + + N
suffix TElement - + 右侧内容,可用于定义右侧图标。TS 类型:TNode。 + + 通用类型定义 + + N
tag String - + 渲染按钮的 HTML 标签,默认使用标签 <button> 渲染,可以自定义为 <a> + <div> 等。透传全部 HTML 属性,如:href/target/data-* 等。⚠️ 禁用按钮 + <button disabled>无法显示 Popup 浮层信息,可通过修改 tag=div + 解决这个问题。可选项:button/a/div + N
theme String - + 组件风格,依次为默认色、品牌色、危险色、警告色、成功色。可选项:default/primary/danger/warning/success + N
type String button 按钮类型。可选项:submit/reset/button N
variant String base按钮形式,基础、线框、虚线、文字。可选项:base/outline/dashed/text N
onClick Function + TS 类型:(e: MouseEvent) => void +
+ 点击时触发 +
N
`, + }} + >
+
+ ) + } +} diff --git a/site/src/components/web/button/index.tsx b/site/src/components/web/button/index.tsx index 2e7dca5b2..c9f17a1fd 100644 --- a/site/src/components/web/button/index.tsx +++ b/site/src/components/web/button/index.tsx @@ -5,6 +5,8 @@ import './variant-checkbox' import './size-checkbox' import './shape-checkbox' import '../common/index' +import buttonMd from '../../../../../src/button/button.md' +import './button_md' import '../../../../../src/button' import '../../../../../src/button/_example/base' import '../../../../../src/button/_example/block' @@ -16,6 +18,14 @@ import '../../../../../src/button/_example/size' import '../../../../../src/button/_example/status' import '../../../../../src/button/_example/theme' import * as marked from 'marked' +// import tdesign style +import css from '@common/style/web/docs.less' + +// import site web components +import 'tdesign-site-components' +import css1 from 'tdesign-site-components/lib/styles/style.css' +import css2 from 'tdesign-site-components/lib/styles/prism-theme.less' +import css3 from 'tdesign-site-components/lib/styles/prism-theme-dark.less' const docsHTML = marked.parse(` :: BASE_DOC :: @@ -58,6 +68,7 @@ export const buttonState = signal({ define( 'page-button', class extends WeElement { + static css = css + css1 + css2 + css3 static defaultProps = { tab: 'demo', } @@ -89,7 +100,7 @@ define( render(props: {} | OmiProps<{}, any>, store: any) { return ( - <> +
@@ -200,15 +211,18 @@ define(
-
+ {/* {buttonMd} */} + {/*
+ >
*/}
- +
) } }, diff --git a/site/vite.config.js b/site/vite.config.js index 73518fe5f..b432e4e44 100644 --- a/site/vite.config.js +++ b/site/vite.config.js @@ -1,14 +1,15 @@ -import { defineConfig } from 'vite'; -import { VitePWA } from 'vite-plugin-pwa'; -import tDocPlugin from './plugin-doc'; -import pwaConfig from './pwaConfig'; -import { resolveConfig, basePlugin } from '../script/vite.base.config'; +import { defineConfig } from 'vite' +import { VitePWA } from 'vite-plugin-pwa' +import tDocPlugin from './plugin-doc' +import apiTDocPlugin from './plugin-api-doc' +import pwaConfig from './pwaConfig' +import { resolveConfig, basePlugin } from '../script/vite.base.config' const publicPathMap = { preview: '/', intranet: '/omi/', production: './', -}; +} export default ({ mode }) => { return defineConfig({ @@ -26,15 +27,15 @@ export default ({ mode }) => { }, build: { outDir: '../tdesign', - chunkSizeWarningLimit: 10000 + chunkSizeWarningLimit: 10000, }, esbuild: { jsxFactory: 'h', - jsxFragment: 'h.f' + jsxFragment: 'h.f', }, - plugins: [...basePlugin, tDocPlugin()], // VitePWA(pwaConfig) + plugins: [...basePlugin, apiTDocPlugin()], // VitePWA(pwaConfig) optimizeDeps: { include: ['prismjs', 'prismjs/components/prism-bash.js'], }, - }); -}; + }) +} diff --git a/src/button/button.md b/src/button/button.md new file mode 100644 index 000000000..66db3c0f7 --- /dev/null +++ b/src/button/button.md @@ -0,0 +1,25 @@ +:: BASE_DOC :: + +## API +### Button Props + +名称 | 类型 | 默认值 | 说明 | 必传 +-- | -- | -- | -- | -- +className | String | - | 类名 | N +style | Object | - | 样式,TS 类型:`React.CSSProperties` | N +block | Boolean | false | 是否为块级元素 | N +children | TNode | - | 按钮内容,同 content。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/omijs/tdesign/blob/main/src/common.ts) | N +content | TNode | - | 按钮内容。TS 类型:`string \| TNode`。[通用类型定义](https://github.com/omijs/tdesign/blob/main/src/common.ts) | N +disabled | Boolean | false | 禁用状态 | N +ghost | Boolean | false | 是否为幽灵按钮(镂空按钮) | N +href | String | - | 跳转地址。href 存在时,按钮标签默认使用 `` 渲染;如果指定了 `tag` 则使用指定的标签渲染 | N +icon | TElement | - | 按钮内部图标,可完全自定义。TS 类型:`TNode`。[通用类型定义](https://github.com/omijs/tdesign/blob/main/src/common.ts) | N +loading | Boolean | false | 是否显示为加载状态 | N +shape | String | rectangle | 按钮形状,有 4 种:长方形、正方形、圆角长方形、圆形。可选项:rectangle/square/round/circle | N +size | String | medium | 组件尺寸。可选项:small/medium/large。TS 类型:`SizeEnum`。[通用类型定义](https://github.com/omijs/tdesign/blob/main/src/common.ts) | N +suffix | TElement | - | 右侧内容,可用于定义右侧图标。TS 类型:`TNode`。[通用类型定义](https://github.com/omijs/tdesign/blob/main/src/common.ts) | N +tag | String | - | 渲染按钮的 HTML 标签,默认使用标签 `