diff --git a/docs/mobile/App.jsx b/docs/mobile/App.jsx index cd0ca529..b94972f9 100644 --- a/docs/mobile/App.jsx +++ b/docs/mobile/App.jsx @@ -1,11 +1,11 @@ import React, { lazy, Suspense } from 'react'; -import { HashRouter, Switch, Route, Redirect } from 'react-router-dom'; +import { HashRouter, Switch, Route, Redirect, useHistory } from 'react-router-dom'; import siteConfig from './mobile.config.js'; -import { getRoute } from './utils'; +import { getRoute, getCurrentRoute } from './utils'; +import THeader from './components/Header.jsx'; const docRoutes = getRoute(siteConfig.docs, []); -const renderRouter = () => { - return docRoutes.map((nav, i) => { +const renderRouter = () => docRoutes.map((nav, i) => { const LazyCom = lazy(nav.component); return ( @@ -15,21 +15,35 @@ const renderRouter = () => { component={() => } /> ); - }); +}) + +const getHashName = (hash = '#/') => { + const { length } = hash; + return hash.substring(2, length); } function App() { + + const history = useHistory(); + + const name = getHashName(history?.location?.hash) + + const title = getCurrentRoute(siteConfig.docs, name)?.title + return ( - - - - loading...}> - {renderRouter()} - - - {/* TODO: 404 */} - - + <> + + + + + loading...}> + {renderRouter()} + + + {/* TODO: 404 */} + + + ); } diff --git a/docs/mobile/components/DemoBlock.jsx b/docs/mobile/components/DemoBlock.jsx new file mode 100644 index 00000000..0cd7eaee --- /dev/null +++ b/docs/mobile/components/DemoBlock.jsx @@ -0,0 +1,14 @@ +import React from "react"; + +const TDemoBlock = (prop) => { + const { children, title, summary } = prop; + return <> +
+

{ title }

+

{ summary }

+ { children } +
+ +} + +export default TDemoBlock; diff --git a/docs/mobile/components/Header.jsx b/docs/mobile/components/Header.jsx new file mode 100644 index 00000000..d614aa4e --- /dev/null +++ b/docs/mobile/components/Header.jsx @@ -0,0 +1,17 @@ +import React from "react"; + +const THeader = (prop) => { + + const { title } = prop; + + return <> + { + title ? (
+
{ title }
+ {/* */} +
) : null + } + +} + +export default THeader; diff --git a/docs/mobile/main.jsx b/docs/mobile/main.jsx index e8b028c6..5ffd3860 100644 --- a/docs/mobile/main.jsx +++ b/docs/mobile/main.jsx @@ -1,13 +1,17 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; import App from './App'; +import '../style/mobile/index.less' import '../../src/_common/style/mobile/_reset.less'; import '../../src/_common/style/mobile/index.less'; ReactDOM.render( - + + + , document.getElementById('app'), ); diff --git a/docs/mobile/mobile.config.js b/docs/mobile/mobile.config.js index 801dc5a3..495d7481 100644 --- a/docs/mobile/mobile.config.js +++ b/docs/mobile/mobile.config.js @@ -5,5 +5,10 @@ export default { name: 'button', component: () => import('@examples/button/demos/base.jsx'), }, + { + title: 'Grid 宫格', + name: 'grid', + component: () => import('@examples/grid/demos/base.jsx'), + }, ], }; diff --git a/docs/mobile/utils.js b/docs/mobile/utils.js index 58199b31..bc80a759 100644 --- a/docs/mobile/utils.js +++ b/docs/mobile/utils.js @@ -3,7 +3,15 @@ export function getRoute(list, docRoutes) { if (item.children) { return getRoute(item.children, docRoutes); } - return docRoutes.push(item); + return docRoutes.push({ + ...item, + path: `/${item.name}` + }); }); return docRoutes; } + +export function getCurrentRoute(docRoutes, name) { + const currentRoutes = docRoutes?.filter(item => item.name === name) || []; + return currentRoutes.length > 0 ? currentRoutes[0] : {}; +} diff --git a/docs/site/App.jsx b/docs/site/App.jsx index 027e9c97..65aa9e27 100644 --- a/docs/site/App.jsx +++ b/docs/site/App.jsx @@ -57,7 +57,7 @@ function Components(props) { {/* */} - + loading...}> diff --git a/docs/site/site.config.js b/docs/site/site.config.js index 1886a7dc..96e8c686 100644 --- a/docs/site/site.config.js +++ b/docs/site/site.config.js @@ -26,5 +26,18 @@ export default { }, ], }, + { + title: '信息展示', + name: 'info', + type: 'component', // 组件文档 + children: [ + { + title: 'Grid 宫格', + name: 'grid', + path: '/react-mobile/components/grid', + component: () => import('@examples/grid/grid.md'), + }, + ], + }, ], }; diff --git a/docs/style/mobile/demo.less b/docs/style/mobile/demo.less new file mode 100644 index 00000000..2421f4de --- /dev/null +++ b/docs/style/mobile/demo.less @@ -0,0 +1,43 @@ +.tdesign-mobile-demo { + background-color: #F6F6F6; + font-family: "PingFang SC"; + + .title { + padding: 24px 16px 8px 16px; + opacity: 1; + color: rgba(0, 0, 0, 0.9); + font-size: 20px; + font-weight: 700; + line-height: 28px; + } + + .summary { + opacity: 1; + color: rgba(0, 0, 0, 0.4); + font-size: 12px; + line-height: 22px; + padding: 0 16px; + } + + &-block { + width: 100%; + + &__title { + padding: 24px 16px 0; + font-size: 16px; + font-weight: 700; + } + + &__title + &__summary { + padding: 8px 16px 16px; + } + + &__summary { + opacity: 1; + color: rgba(0, 0, 0, 0.4); + font-size: 12px; + line-height: 22px; + padding: 16px; + } + } +} diff --git a/docs/style/mobile/index.less b/docs/style/mobile/index.less new file mode 100644 index 00000000..c4df4a3d --- /dev/null +++ b/docs/style/mobile/index.less @@ -0,0 +1,25 @@ +@import '../vars.less'; +@import './demo.less'; + +body { + background-color: #F5F5F5; +} + +.tdesign-demo-topnav { + position: relative; + display: flex; + align-items: center; + justify-content: center; + height: 50px; + background-color: #fff; + box-shadow: @shadow-level-1; + &-title{ + font-size: 18px; + } + &__back{ + position: absolute; + left: 16px; + font-size: 24px; + cursor: pointer; + } +} \ No newline at end of file diff --git a/docs/style/vars.less b/docs/style/vars.less new file mode 100644 index 00000000..ab8e77b6 --- /dev/null +++ b/docs/style/vars.less @@ -0,0 +1,125 @@ +@prefix: tdesign; +@primary-color: #0052d9; + +@primary-color-darken-1: darken(@primary-color, (43 - 35) * 1%); +@primary-color-darken-2: darken(@primary-color, (43 - 27) * 1%); +@primary-color-darken-3: darken(@primary-color, (43 - 19) * 1%); +@primary-color-darken-4: darken(@primary-color, (43 - 11) * 1%); + +@primary-color-ligher-1: lighten(@primary-color, (51 - 43) * 1%); +@primary-color-ligher-2: lighten(@primary-color, (59 - 43) * 1%); +@primary-color-ligher-3: lighten(@primary-color, (67 - 43) * 1%); +@primary-color-ligher-4: lighten(@primary-color, (75 - 43) * 1%); +@primary-color-ligher-5: lighten(@primary-color, (83 - 43) * 1%); +@primary-color-ligher-6: lighten(@primary-color, (91 - 43) * 1%); +@primary-color-ligher-7: lighten(@primary-color, (96 - 43) * 1%); +// @primary-color-ligher-6: hsl(217, 100, 91); + +// 状态色 +@success-color: #3ecc36; +@error-color: #ff3e00; +@warning-color: #ffa700; +@info-color: #c7ddf3; + +@disabled-color: #c0c0c0; + +// 文字色 +@text-color: #333; // 黑 80% +@text-color-lighter-1: #666; // 黑 60% +@text-color-lighter-2: #999; // 黑 40% +@text-color-lighter-3: #ccc; // 黑 20% + +// 文字色 +@text-color-white: #fff; +@text-color-white-lighter-1: rgba(255, 255, 255, 0.8); +@text-color-white-lighter-2: rgba(255, 255, 255, 0.6); +@text-color-white-lighter-3: rgba(255, 255, 255, 0.4); + +@placeholder-color: #999; + +@font-size-base: 14px; +@line-height-base: 1.5; +@font-family: "PingFang SC", -apple-system, "Helvetica Neue", Helvetica, BlinkMacSystemFont, "Microsoft YaHei", tahoma, Arial, "Open Sans", "Hiragino Sans GB", "Heiti SC", "WenQuanYi Micro Hei", sans-serif; + +/** + * 文字部分 + * size 大小,line 行高, color 颜色 + */ +// 字体大小 +@font-size-small: 12px; +@font-size: 14px; +@font-size-large: 16px; +@font-size-larger: 18px; +// 行高 +@line-height-small: 30px; +@line-height: 38px; +@line-height-large: 48px; +@line-height-larger: 60px; + +// 标题文字 +@title-level-1-size: 38px; +@title-level-1-line: 53 / 38; +@title-level-1-color: @text-color; +@title-level-2-size: 20px; +@title-level-2-line: 28 / 20; +@title-level-2-color: @text-color; +@title-level-3-size: 16px; +@title-level-3-line: 22 / 16; +@title-level-3-color: @text-color; +@title-level-4-size: 16px; +@title-level-4-line: 22 / 16; +@title-level-4-color: @text-color-lighter-1; + +// 正常文字 +@text-level-1-size: 14px; +@text-level-1-line: 20 / 14; +@text-level-1-color: @text-color; +@text-level-2-size: 14px; +@text-level-2-line: 20 / 14; +@text-level-2-color: @text-color-lighter-1; +@text-level-3-size: 14px; +@text-level-3-line: 20 / 14; +@text-level-3-color: @text-color-lighter-2; + +// 小字 +@smalltext-level-1-size: 12px; +@smalltext-level-1-line: 17 / 14; +@smalltext-level-1-color: @text-color-lighter-1; +@smalltext-level-2-size: 12px; +@smalltext-level-2-line: 17 / 14; +@smalltext-level-2-color: @text-color-lighter-2; + +@link-color: @primary-color; +@link-hover-color: lighten(@primary-color, 20%); +@link-active-color: darken(@primary-color, 20%); +@link-decoration: none; +@link-hover-decoration: none; + +// Shadow +@shadow-level-1: 3px 3px 8px 2px rgba(0, 0, 0, 0.06); +@shadow-level-2: 6px 6px 12px 6px rgba(0, 0, 0, 0.08); + + +// border +@border-color: #d9d9d9; +@border-main: 1px solid @border-color; +@border-color-2: lighten(@border-color, 5.9124%); +@border-main-2: 1px solid @border-color-2; + +// rate +@background-color-disabled: #e9e9e9; +@background-color-active: #ffa700; + +@border-radius-percent: 0; + +@body-background: #ffffff; +@component-background: #ffffff; + +// layout +@layoutMargin: 10px; + +// header +@headerHeight: 81px; + +// footer +@footerHeight: 160px; diff --git a/examples/grid/demos/base.jsx b/examples/grid/demos/base.jsx new file mode 100644 index 00000000..4cf97fcc --- /dev/null +++ b/examples/grid/demos/base.jsx @@ -0,0 +1,110 @@ +import React from 'react'; +import { Grid, GridItem } from 'tdesign-mobile-react'; +import TDemoBlock from '../../../docs/mobile/components/DemoBlock'; +import Layout from './layout'; +import Normal from './normal'; +import './style/index.less' + +const imgUrl = 'https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png' + +export default function Base() { + return ( + <> +
+

Grid 宫格

+

横向分割的点击单元,用作一组次级功能的入口。

+
+ +
+ + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> +
+
+
+ + + + + + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> +
+
+ + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> +
+
+
+ + + + + + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> +
+
+ + ); +} diff --git a/examples/grid/demos/border.jsx b/examples/grid/demos/border.jsx new file mode 100644 index 00000000..f8bd9a16 --- /dev/null +++ b/examples/grid/demos/border.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Grid, GridItem } from 'tdesign-mobile-react'; + +const imgUrl = 'https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png' + +export default function () { + return <> +
+ + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> +
+
+ +} diff --git a/examples/grid/demos/layout.jsx b/examples/grid/demos/layout.jsx new file mode 100644 index 00000000..50115618 --- /dev/null +++ b/examples/grid/demos/layout.jsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { Grid, GridItem } from 'tdesign-mobile-react'; + +const imgUrl = 'https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png' + +export default function () { + return <> + + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> +
+ +} diff --git a/examples/grid/demos/normal.jsx b/examples/grid/demos/normal.jsx new file mode 100644 index 00000000..722e197d --- /dev/null +++ b/examples/grid/demos/normal.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { Grid, GridItem } from 'tdesign-mobile-react'; + +const imgUrl = 'https://tdesign.gtimg.com/mobile/%E5%9B%BE%E7%89%87.png' + +export default function () { + return <> + + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> + } + text={
标题文字
} + /> +
+ +} diff --git a/examples/grid/demos/style/index.less b/examples/grid/demos/style/index.less new file mode 100644 index 00000000..f4ebafc5 --- /dev/null +++ b/examples/grid/demos/style/index.less @@ -0,0 +1,66 @@ +.tdesign-mobile-demo-header { + padding: 24px 16px 0; + .title { + color: rgba(0,0,0,0.9); + font-size: 20px; + font-weight: 700; + } + + .summary { + color: rgba(153,153,153,1); + font-size: 12px; + line-height: 20px; + } +} + + +.img-2 { + width: 48px; + height: 48px; + display: block; +} + +.img-3 { + width: 48px; + height: 48px; + display: block; +} + +.img-4 { + width: 32px; + height: 32px; + display: block; +} + +.img-5 { + width: 28px; + height: 28px; + display: block; +} + +.img-vertical{ + margin-bottom: 10px; +} + +.img-horizontal { + margin-right: 10px; +} + +.text-2 { + font-size: 14px; +} + +.text-3 { + font-size: 14px; +} + +.text-4 { + font-size: 12px; +} + +.tdesign-grid-5-horizontal { + margin: 16px 12px 0; + border-radius: 8px; + overflow: hidden; +} + diff --git a/examples/grid/grid.md b/examples/grid/grid.md new file mode 100644 index 00000000..7a6abfab --- /dev/null +++ b/examples/grid/grid.md @@ -0,0 +1,48 @@ +--- +title: Grid 宫格 +description: 横向分割的点击单元,用作一组次级功能的入口。 +spline: base +isComponent: true +toc: false +--- + +## 基础用法 + +::: demo demos/normal grid +::: + + +### 边框 + +::: demo demos/border grid +::: + +### 内容布局 +::: demo demos/layout grid +::: + + +## API + +### Grid Props + +名称 | 类型 | 默认值 | 说明 | 必传 +-- | -- | -- | -- | -- +className | String | - | 类名 | N +style | Object | - | 样式,TS 类型:`React.CSSProperties` | N +align | String | center | 内容对齐方式。可选项:left/center | N +border | Boolean / Object | false | 边框,默认不显示。值为 true 则显示默认边框,值类型为 object 则表示自定义边框样式。TS 类型:`boolean | { color?: string; width?: string; style?: 'solid' | 'dashed' | 'dotted' | 'double' | 'groove' | 'inset' | 'outset' }` | N +column | Number | 4 | 每一行的列数量 | N +gutter | Number | - | 间隔大小 | N +hover | Boolean | false | 是否开启点击反馈 | N + +### GridItem Props + +名称 | 类型 | 默认值 | 说明 | 必传 +-- | -- | -- | -- | -- +className | String | - | 类名 | N +style | Object | - | 样式,TS 类型:`React.CSSProperties` | N +description | TNode | - | 文本以外的更多描述,辅助信息。可以通过 Props 传入文本,也可以自定义标题节点。TS 类型:`string | TNode`。[通用类型定义](https://github.com/TDesignOteam/tdesign-mobile-react/blob/develop/src/common.ts) | N +image | TNode | - | 图片,可以是图片地址,也可以自定义图片节点。TS 类型:`string | TNode`。[通用类型定义](https://github.com/TDesignOteam/tdesign-mobile-react/blob/develop/src/common.ts) | N +layout | String | vertical | 内容布局方式。可选项:vertical/horizontal | N +text | TNode | - | 文本,可以通过 Props 传入文本,也可以自定义标题节点。TS 类型:`string | TNode`。[通用类型定义](https://github.com/TDesignOteam/tdesign-mobile-react/blob/develop/src/common.ts) | N diff --git a/src/_common b/src/_common index 3f307bf5..198a286b 160000 --- a/src/_common +++ b/src/_common @@ -1 +1 @@ -Subproject commit 3f307bf520ab010feff7ed94d3ab1d68f5b55938 +Subproject commit 198a286b245cd23083d0a5194585efb24849cd62 diff --git a/src/grid/Grid.tsx b/src/grid/Grid.tsx new file mode 100644 index 00000000..f88e1e8d --- /dev/null +++ b/src/grid/Grid.tsx @@ -0,0 +1,24 @@ +import React, { FC } from "react"; +import { TdGridProps } from "./type"; + +const prefix = 't'; +const name = `${prefix}-grid`; + +const Grid: FC = (prop) => { + const { children, align, border, column, gutter, hover } = prop; + + return <> +
+ { + React.Children.map(children, (child: React.ReactElement) => React.cloneElement(child, { + border, + align, + column, + hover, + })) + } +
+ +} + +export default Grid; diff --git a/src/grid/GridItem.tsx b/src/grid/GridItem.tsx new file mode 100644 index 00000000..dce7b729 --- /dev/null +++ b/src/grid/GridItem.tsx @@ -0,0 +1,133 @@ +import React, { FC } from "react"; +import { TdGridItemProps, TdGridProps } from "./type"; + +const prefix = 't'; +const name = `${prefix}-grid-item`; + +enum LAYOUT { + VERTICAL = 'vertical', + HORIZONTAL = 'horizontal' +} + +enum ALIGN { + CENTER = 'center', + LEFT = 'left', +} + +const DEFAULT_COLUMN = 4; + +const DEFAULT_ALIGN = ALIGN.CENTER; + +const DEFAULT_GUTTER = 20; + +const DEFAULT_LAYOUT = LAYOUT.VERTICAL; + +const DEFAULT_HOVER = false; + +const DEFAULT_BORDER = false; + +const getGridItemWidth = (column) => `${100 / column}%`; + +const getBorderStyle = (border) => { + if (typeof border === 'boolean') { + if (border) { + return { + borderColor: '#f6f6f6', + borderWidth: '1px', + borderStyle: 'solid' + }; + } + + return {} + } + const {color, width, style} = border; + return { + borderColor: color || '#f6f6f6', + borderWidth: width || '1px', + borderStyle: style || 'solid', + }; +} + +const getLayout = (layout) => { + const layoutMap = { + [LAYOUT.VERTICAL] : 'column', + [LAYOUT.HORIZONTAL]: 'row', + } + return layoutMap[layout]; +} + +const getGridItemTextAlign = (align) => align === ALIGN.LEFT ? ALIGN.LEFT : ALIGN.CENTER; + +const getGridItemAlign = (align, layout) => { + const contentAlign = getGridItemTextAlign(align); + if (layout === LAYOUT.HORIZONTAL) { + return { + justifyContent: contentAlign, + } + } + return { + alignItems: contentAlign + } +}; + + +export interface GridItemProp extends TdGridItemProps, TdGridProps {} + +const GridItem: FC = (prop) => { + const { + border = DEFAULT_BORDER, + align = DEFAULT_ALIGN, + column = DEFAULT_COLUMN, + gutter = DEFAULT_GUTTER, + description, + image, + hover = DEFAULT_HOVER, + layout = DEFAULT_LAYOUT, + text + } = prop; + + console.log(align); + + + const gridItemWidth = getGridItemWidth(column); + + const gridItemBorder = getBorderStyle(border); + + const gridItemLayout = getLayout(layout); + + const gridItemAlign = getGridItemAlign(align, layout); + + const gridItemTextAlign = getGridItemTextAlign(align); + + console.log(gridItemTextAlign); + + const gridItemImage = typeof image === 'string' ? + () : image + + return <> +
+
+ {gridItemImage} +
+
+
+ { text } +
+
+ { description } +
+
+
+ +} + +export default GridItem; diff --git a/src/grid/index.tsx b/src/grid/index.tsx new file mode 100644 index 00000000..cb713110 --- /dev/null +++ b/src/grid/index.tsx @@ -0,0 +1,16 @@ +import _Grid from './Grid'; +import _GridItem from './GridItem'; + +import './style'; + +export * from './type'; + +export const Grid = _Grid; + +export const GridItem = _GridItem; + +export default { + Grid, + GridItem +}; + diff --git a/src/grid/style/css.js b/src/grid/style/css.js new file mode 100644 index 00000000..6a9a4b13 --- /dev/null +++ b/src/grid/style/css.js @@ -0,0 +1 @@ +import './index.css'; diff --git a/src/grid/style/index.js b/src/grid/style/index.js new file mode 100644 index 00000000..6d358f7a --- /dev/null +++ b/src/grid/style/index.js @@ -0,0 +1,2 @@ +import '../../_common/style/mobile/components/grid/_index.less'; +import '../../_common/style/mobile/components/grid-item/_index.less'; diff --git a/src/grid/type.ts b/src/grid/type.ts new file mode 100644 index 00000000..0daa5ea9 --- /dev/null +++ b/src/grid/type.ts @@ -0,0 +1,54 @@ +/* eslint-disable */ + +/** + * 该文件为脚本自动生成文件,请勿随意修改。如需修改请联系 PMC + * */ + +import { TNode } from '../common'; + +export interface TdGridProps { + /** + * 内容对齐方式 + * @default center + */ + align?: 'left' | 'center'; + /** + * 边框,默认不显示。值为 true 则显示默认边框,值类型为 object 则表示自定义边框样式 + * @default false + */ + border?: boolean | { color?: string; width?: string; style?: 'solid' | 'dashed' | 'dotted' | 'double' | 'groove' | 'inset' | 'outset' }; + /** + * 每一行的列数量 + * @default 4 + */ + column?: number; + /** + * 间隔大小 + */ + gutter?: number; + /** + * 是否开启点击反馈 + * @default false + */ + hover?: boolean; +} + +export interface TdGridItemProps { + /** + * 文本以外的更多描述,辅助信息。可以通过 Props 传入文本,也可以自定义标题节点 + */ + description?: TNode; + /** + * 图片,可以是图片地址,也可以自定义图片节点 + */ + image?: TNode; + /** + * 内容布局方式 + * @default vertical + */ + layout?: 'vertical' | 'horizontal'; + /** + * 文本,可以通过 Props 传入文本,也可以自定义标题节点 + */ + text?: TNode; +} diff --git a/src/index.ts b/src/index.ts index eaf5eea7..2ffd49c7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,2 @@ export * from './button'; +export * from './grid'