From 1f6220298a1c9726ff336e7d1e748c2d395eeffb Mon Sep 17 00:00:00 2001 From: WB01081293 Date: Thu, 25 Jan 2024 10:52:16 +0800 Subject: [PATCH 1/2] chore(Box): rename to ts --- components/box/__docs__/theme/index.jsx | 76 ---------------- components/box/__docs__/theme/index.tsx | 87 +++++++++++++++++++ .../__tests__/{a11y-spec.js => a11y-spec.tsx} | 0 .../{index-spec.js => index-spec.tsx} | 21 +++-- components/box/{index.jsx => index.tsx} | 16 +++- .../box/mobile/{index.jsx => index.tsx} | 0 components/box/{style.js => style.ts} | 0 components/box/{index.d.ts => types.ts} | 0 8 files changed, 114 insertions(+), 86 deletions(-) delete mode 100644 components/box/__docs__/theme/index.jsx create mode 100644 components/box/__docs__/theme/index.tsx rename components/box/__tests__/{a11y-spec.js => a11y-spec.tsx} (100%) rename components/box/__tests__/{index-spec.js => index-spec.tsx} (83%) rename components/box/{index.jsx => index.tsx} (92%) rename components/box/mobile/{index.jsx => index.tsx} (100%) rename components/box/{style.js => style.ts} (100%) rename components/box/{index.d.ts => types.ts} (100%) diff --git a/components/box/__docs__/theme/index.jsx b/components/box/__docs__/theme/index.jsx deleted file mode 100644 index 7e8f9ee6e8..0000000000 --- a/components/box/__docs__/theme/index.jsx +++ /dev/null @@ -1,76 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import '../../../demo-helper/style'; -import { Demo, DemoGroup, initDemo } from '../../../demo-helper'; -import ConfigProvider from '../../../config-provider'; -import zhCN from '../../../locale/zh-cn'; -import enUS from '../../../locale/en-us'; -import '../../style'; -import Box from '../../index'; - -const i18nMap = { - 'zh-cn': { - 'box': '弹性布局', - normal: '正常' - }, - 'en-us': { - 'box': 'Box', - normal: 'Normal', - }, -}; - -class RenderBox extends React.Component { - constructor(props) { - super(props); - this.state = { - demoFunction: { - hasChildren: { - label: 'Box使用', - value: 'false', - enum: [{ - label: '不独立使用', - value: false - }, { - label: '独立使用', - value: true - }] - } - } - }; - } - - onFunctionChange = (demoFunction) => { - this.setState({ demoFunction }); - } - - render() { - const { i18nMap } = this.props; - const { demoFunction } = this.state; - const hasChildren = demoFunction.hasChildren.value === 'true'; - - return ( - - - - - - ); - } - -} - -function render(i18nMap, lang) { - ReactDOM.render( -
- -
-
, document.getElementById('container')); -} - -window.renderDemo = function(lang = 'en-us') { - render(i18nMap[lang], lang); -}; - -renderDemo(); - -initDemo('box'); diff --git a/components/box/__docs__/theme/index.tsx b/components/box/__docs__/theme/index.tsx new file mode 100644 index 0000000000..835949ad55 --- /dev/null +++ b/components/box/__docs__/theme/index.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import '../../../demo-helper/style'; +import { Demo, DemoGroup, initDemo } from '../../../demo-helper'; +import ConfigProvider from '../../../config-provider'; +import zhCN from '../../../locale/zh-cn'; +import enUS from '../../../locale/en-us'; +import '../../style'; +import Box from '../../index'; + +const i18nMap = { + 'zh-cn': { + box: '弹性布局', + normal: '正常', + }, + 'en-us': { + box: 'Box', + normal: 'Normal', + }, +}; + +class RenderBox extends React.Component { + constructor(props) { + super(props); + this.state = { + demoFunction: { + hasChildren: { + label: 'Box使用', + value: 'false', + enum: [ + { + label: '不独立使用', + value: false, + }, + { + label: '独立使用', + value: true, + }, + ], + }, + }, + }; + } + + onFunctionChange = demoFunction => { + this.setState({ demoFunction }); + }; + + render() { + const { i18nMap } = this.props; + const { demoFunction } = this.state; + const hasChildren = demoFunction.hasChildren.value === 'true'; + + return ( + + + + + + + + ); + } +} + +function render(i18nMap, lang) { + ReactDOM.render( + +
+ +
+
, + document.getElementById('container') + ); +} + +window.renderDemo = function (lang = 'en-us') { + render(i18nMap[lang], lang); +}; + +renderDemo(); + +initDemo('box'); diff --git a/components/box/__tests__/a11y-spec.js b/components/box/__tests__/a11y-spec.tsx similarity index 100% rename from components/box/__tests__/a11y-spec.js rename to components/box/__tests__/a11y-spec.tsx diff --git a/components/box/__tests__/index-spec.js b/components/box/__tests__/index-spec.tsx similarity index 83% rename from components/box/__tests__/index-spec.js rename to components/box/__tests__/index-spec.tsx index ec9c15452e..ee973c485a 100644 --- a/components/box/__tests__/index-spec.js +++ b/components/box/__tests__/index-spec.tsx @@ -12,7 +12,7 @@ const render = element => { let inc; const container = document.createElement('div'); document.body.appendChild(container); - ReactDOM.render(element, container, function() { + ReactDOM.render(element, container, function () { inc = this; }); return { @@ -53,12 +53,22 @@ describe('Box', () => { it('should render', () => { wrapper = render( - + - + @@ -85,10 +95,7 @@ describe('Box', () => { ); - const style = wrapper - .find('.test') - .at(2) - .prop('style'); + const style = wrapper.find('.test').at(2).prop('style'); const { justifyContent } = style; assert(justifyContent === 'center'); }); diff --git a/components/box/index.jsx b/components/box/index.tsx similarity index 92% rename from components/box/index.jsx rename to components/box/index.tsx index d7f8dab85f..cc1b6fd663 100644 --- a/components/box/index.jsx +++ b/components/box/index.tsx @@ -30,7 +30,8 @@ const createChildren = (children, { spacing, direction, wrap, device }) => { if (!wrap) { // 不折行 const isNone = [index === 0, index === array.length - 1]; - const props = direction === 'row' ? ['marginLeft', 'marginRight'] : ['marginTop', 'marginBottom']; + const props = + direction === 'row' ? ['marginLeft', 'marginRight'] : ['marginTop', 'marginBottom']; ['marginTop', 'marginRight', 'marginBottom', 'marginLeft'].forEach(prop => { if (prop in spacingMargin && props.indexOf(prop) === -1) { @@ -50,7 +51,10 @@ const createChildren = (children, { spacing, direction, wrap, device }) => { const childPropsMargin = getMargin(propsMargin); let gridProps = {}; - if (['function', 'object'].indexOf(typeof child.type) > -1 && child.type._typeMark === 'responsive_grid') { + if ( + ['function', 'object'].indexOf(typeof child.type) > -1 && + child.type._typeMark === 'responsive_grid' + ) { gridProps = createStyle({ display: 'grid', ...child.props }); } @@ -136,7 +140,13 @@ class Box extends Component { /** * 沿着主轴方向,子元素们的排布关系 (兼容性同 justify-content ) */ - justify: PropTypes.oneOf(['flex-start', 'center', 'flex-end', 'space-between', 'space-around']), + justify: PropTypes.oneOf([ + 'flex-start', + 'center', + 'flex-end', + 'space-between', + 'space-around', + ]), /** * 垂直主轴方向,子元素们的排布关系 (兼容性同 align-items ) */ diff --git a/components/box/mobile/index.jsx b/components/box/mobile/index.tsx similarity index 100% rename from components/box/mobile/index.jsx rename to components/box/mobile/index.tsx diff --git a/components/box/style.js b/components/box/style.ts similarity index 100% rename from components/box/style.js rename to components/box/style.ts diff --git a/components/box/index.d.ts b/components/box/types.ts similarity index 100% rename from components/box/index.d.ts rename to components/box/types.ts From a5a3569f7ebdd5b242ca2fe8f63215d3907f7abf Mon Sep 17 00:00:00 2001 From: WB01081293 Date: Thu, 25 Jan 2024 11:06:42 +0800 Subject: [PATCH 2/2] refactor(Box): convert to TypeScript, impove docs and tests --- components/box/__docs__/demo/wrap/index.tsx | 2 +- components/box/__docs__/theme/index.tsx | 24 ++++--- components/box/__tests__/a11y-spec.tsx | 24 ++----- components/box/__tests__/index-spec.tsx | 59 ++-------------- components/box/index.tsx | 39 ++++++----- components/box/mobile/index.tsx | 1 + components/box/types.ts | 77 +++++++++++++++++---- components/demo-helper/index.tsx | 4 +- 8 files changed, 112 insertions(+), 118 deletions(-) diff --git a/components/box/__docs__/demo/wrap/index.tsx b/components/box/__docs__/demo/wrap/index.tsx index 0282972532..87eaf02363 100644 --- a/components/box/__docs__/demo/wrap/index.tsx +++ b/components/box/__docs__/demo/wrap/index.tsx @@ -6,7 +6,7 @@ class BoxDemo extends React.Component { state = { wrap: true, }; - onSwitchChange = checked => { + onSwitchChange = (checked: boolean) => { this.setState({ wrap: checked, }); diff --git a/components/box/__docs__/theme/index.tsx b/components/box/__docs__/theme/index.tsx index 835949ad55..4c9d2d1779 100644 --- a/components/box/__docs__/theme/index.tsx +++ b/components/box/__docs__/theme/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import '../../../demo-helper/style'; -import { Demo, DemoGroup, initDemo } from '../../../demo-helper'; +import { Demo, DemoGroup, initDemo, DemoFunctionDefineForObject } from '../../../demo-helper'; import ConfigProvider from '../../../config-provider'; import zhCN from '../../../locale/zh-cn'; import enUS from '../../../locale/en-us'; @@ -18,9 +18,15 @@ const i18nMap = { normal: 'Normal', }, }; +interface RenderBoxState { + demoFunction: Record; +} -class RenderBox extends React.Component { - constructor(props) { +interface RenderBoxProps { + i18nMap: { [index: string]: string }; +} +class RenderBox extends React.Component { + constructor(props: RenderBoxProps) { super(props); this.state = { demoFunction: { @@ -42,24 +48,24 @@ class RenderBox extends React.Component { }; } - onFunctionChange = demoFunction => { + onFunctionChange = (demoFunction: RenderBoxState['demoFunction']) => { this.setState({ demoFunction }); }; render() { const { i18nMap } = this.props; const { demoFunction } = this.state; - const hasChildren = demoFunction.hasChildren.value === 'true'; + const hasChildren = demoFunction ? demoFunction.hasChildren.value === 'true' : null; return ( - + - + @@ -67,7 +73,7 @@ class RenderBox extends React.Component { } } -function render(i18nMap, lang) { +function render(i18nMap: { [index: string]: string }, lang: string) { ReactDOM.render(
diff --git a/components/box/__tests__/a11y-spec.tsx b/components/box/__tests__/a11y-spec.tsx index 4d0e504ed6..0ddf27f76b 100644 --- a/components/box/__tests__/a11y-spec.tsx +++ b/components/box/__tests__/a11y-spec.tsx @@ -1,26 +1,12 @@ import React from 'react'; -import Enzyme from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; import Box from '../index'; import '../style'; -import { unmount, testReact } from '../../util/__tests__/legacy/a11y/validate'; +import { testReact } from '../../util/__tests__/a11y/validate'; -Enzyme.configure({ adapter: new Adapter() }); - -/* eslint-disable no-undef, react/jsx-filename-extension */ describe('Box A11y', () => { - let wrapper; - - afterEach(() => { - if (wrapper) { - wrapper.unmount(); - wrapper = null; - } - unmount(); - }); - - it('should render', async () => { - wrapper = await testReact(); - return wrapper; + describe('Box A11y', () => { + it('should render', async () => { + await testReact(); + }); }); }); diff --git a/components/box/__tests__/index-spec.tsx b/components/box/__tests__/index-spec.tsx index ee973c485a..94917de8ed 100644 --- a/components/box/__tests__/index-spec.tsx +++ b/components/box/__tests__/index-spec.tsx @@ -1,57 +1,10 @@ import React from 'react'; -import ReactDOM from 'react-dom'; -import Enzyme, { mount } from 'enzyme'; -import Adapter from 'enzyme-adapter-react-16'; -import assert from 'power-assert'; import Box from '../index'; import '../style'; -Enzyme.configure({ adapter: new Adapter() }); - -const render = element => { - let inc; - const container = document.createElement('div'); - document.body.appendChild(container); - ReactDOM.render(element, container, function () { - inc = this; - }); - return { - setProps: props => { - const clonedElement = React.cloneElement(element, props); - ReactDOM.render(clonedElement, container); - }, - unmount: () => { - ReactDOM.unmountComponentAtNode(container); - document.body.removeChild(container); - }, - instance: () => { - return inc; - }, - find: selector => { - return container.querySelectorAll(selector); - }, - }; -}; - describe('Box', () => { - let wrapper; - - beforeEach(() => { - const overlay = document.querySelectorAll('.next-overlay-wrapper'); - overlay.forEach(dom => { - document.body.removeChild(dom); - }); - }); - - afterEach(() => { - if (wrapper) { - wrapper.unmount(); - wrapper = null; - } - }); - it('should render', () => { - wrapper = render( + cy.mount( { ); - - assert(wrapper.find('.next-box')); + cy.get('.next-box'); }); it('justify should work when wrap and spacing setted', () => { - wrapper = mount( + cy.mount( @@ -94,9 +46,6 @@ describe('Box', () => { ); - - const style = wrapper.find('.test').at(2).prop('style'); - const { justifyContent } = style; - assert(justifyContent === 'center'); + cy.get('.test').should('have.css', 'justify-content', 'center'); }); }); diff --git a/components/box/index.tsx b/components/box/index.tsx index cc1b6fd663..50a2dd34f8 100644 --- a/components/box/index.tsx +++ b/components/box/index.tsx @@ -1,8 +1,9 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; +import React from 'react'; import cx from 'classnames'; +import PropTypes from 'prop-types'; import ConfigProvider from '../config-provider'; import { obj } from '../util'; +import { BoxProps } from './types'; import createStyle, { getMargin, getChildMargin, @@ -10,20 +11,22 @@ import createStyle, { filterInnerStyle, filterHelperStyle, filterOuterStyle, - getGridChildProps, - // getBoxChildProps, } from '../responsive-grid/create-style'; const { pickOthers } = obj; -const createChildren = (children, { spacing, direction, wrap, device }) => { +type ChildElement = React.ReactElement< + BoxProps, + (string | React.JSXElementConstructor) & { _typeMark: string } +>; +const createChildren = (children: React.ReactNode, { spacing, direction, wrap }: BoxProps) => { const array = React.Children.toArray(children); if (!children) { return null; } return array.map((child, index) => { - let spacingMargin = {}; + let spacingMargin: { [key: string]: string | number } = {}; spacingMargin = getChildMargin(spacing); @@ -50,15 +53,14 @@ const createChildren = (children, { spacing, direction, wrap, device }) => { const { margin: propsMargin } = child.props; const childPropsMargin = getMargin(propsMargin); let gridProps = {}; - if ( ['function', 'object'].indexOf(typeof child.type) > -1 && - child.type._typeMark === 'responsive_grid' + (child as ChildElement).type._typeMark === 'responsive_grid' ) { gridProps = createStyle({ display: 'grid', ...child.props }); } - return React.cloneElement(child, { + return React.cloneElement(child as React.ReactElement, { style: { ...spacingMargin, // ...getBoxChildProps(child.props), @@ -73,20 +75,21 @@ const createChildren = (children, { spacing, direction, wrap, device }) => { }); }; -const getStyle = (style = {}, props) => { +const getStyle = (style: React.CSSProperties | undefined, props: BoxProps) => { return { + // @ts-expect-error fixme: wait responsive-grid refactor to ts ...createStyle({ display: 'flex', ...props }), ...style, }; }; -const getOuterStyle = (style, styleProps) => { +const getOuterStyle: typeof getStyle = (style, styleProps) => { const sheet = getStyle(style, styleProps); return filterOuterStyle(sheet); }; -const getHelperStyle = (style, styleProps) => { +const getHelperStyle: typeof getStyle = (style, styleProps) => { const sheet = getStyle(style, styleProps); return filterHelperStyle({ @@ -95,7 +98,7 @@ const getHelperStyle = (style, styleProps) => { }); }; -const getInnerStyle = (style, styleProps) => { +const getInnerStyle: typeof getStyle = (style, styleProps) => { const sheet = getStyle(style, styleProps); return filterInnerStyle(sheet); @@ -104,7 +107,7 @@ const getInnerStyle = (style, styleProps) => { /** * Box */ -class Box extends Component { +class Box extends React.Component { static propTypes = { prefix: PropTypes.string, style: PropTypes.object, @@ -118,7 +121,7 @@ class Box extends Component { ]), /** * 布局方向,默认为 column ,一个元素占据一整行 - * @default column + * @defaultValue column */ direction: PropTypes.oneOf(['row', 'column', 'row-reverse']), /** @@ -157,7 +160,6 @@ class Box extends Component { */ component: PropTypes.string, }; - static defaultProps = { prefix: 'next-', direction: 'column', @@ -193,7 +195,8 @@ class Box extends Component { padding, margin, }; - const View = component; + const View = component!; + const others = pickOthers(Object.keys(Box.propTypes), this.props); const styleSheet = getStyle(style, styleProps); @@ -233,5 +236,5 @@ class Box extends Component { ); } } - +export type { BoxProps }; export default ConfigProvider.config(Box); diff --git a/components/box/mobile/index.tsx b/components/box/mobile/index.tsx index a7650947fd..cae55946bf 100644 --- a/components/box/mobile/index.tsx +++ b/components/box/mobile/index.tsx @@ -1,3 +1,4 @@ +// @ts-expect-error meet-react does not export Box import { Box as MeetBox } from '@alifd/meet-react'; import NextBox from '../index'; diff --git a/components/box/types.ts b/components/box/types.ts index 1c1013013e..703d4a8cfc 100644 --- a/components/box/types.ts +++ b/components/box/types.ts @@ -1,19 +1,68 @@ -/// - -import React, { HTMLAttributes, ElementType, Component } from 'react'; +import React from 'react'; +import type * as CSS from 'csstype'; import { CommonProps } from '../util'; -export interface BoxProps extends HTMLAttributes, CommonProps { - device?: 'phone' | 'tablet' | 'desktop'; - flex?: number | Array; - direction?: 'row' | 'column' | 'row-reverse'; +/** + * @api + */ +export type Spacing = + | number + | [topAndBottom: number, rightAndLeft: number] + | [top: number, rightAndLeft: number, bottom: number] + | [top: number, right: number, bottom: number, left: number]; + +/** + * @api Box + */ +export interface BoxProps extends React.HTMLAttributes, CommonProps { + /** + * 同 CSS 属性 `flex`,支持数组方式设置 + * @en Same as css attribute `flex`, support array mode setting + */ + flex?: + | CSS.Property.Flex + | [CSS.Property.FlexGrow, CSS.Property.FlexShrink, CSS.Property.FlexBasis]; + /** + * 布局方向,同 CSS 属性 `flex-direction` + * @en Layout direction, same as css attribute `flex-direction` + * @defaultValue 'column' + */ + direction?: CSS.Property.FlexDirection; + /** + * 是否折行 + * @en wrap or not + * @defaultValue false + */ wrap?: boolean; - spacing?: number | Array; - margin?: number | Array; - padding?: number | Array; - justify?: 'flex-start' | 'center' | 'flex-end' | 'space-between' | 'space-around' | string; - align?: 'flex-start' | 'center' | 'flex-end' | 'baseline' | 'stretch' | string; + /** + * 元素之间的间距 + * @en Element spacing + */ + spacing?: Spacing; + /** + * 容器外间距 + * @en Container outer spacing + */ + margin?: Spacing; + /** + * 容器内间距 + * @en Container inner spacing + */ + padding?: Spacing; + /** + * 沿着主轴方向,子元素们的排布关系,同 CSS 属性 `justify-content` + * @en The alignment of items on the main axis, same as css attribute `justify-content` + */ + justify?: CSS.Property.JustifyContent; + /** + * 沿交叉轴方向,子元素们的排布关系,同 CSS 属性 `align-items` + * @en The alignment of items on the cross axis, same as css attribute `align-items` + */ + align?: CSS.Property.AlignItems; + /** + * 定制 JSX 标签名 + * @en Custom JSX tag name + * @defaultValue 'div' + */ component?: keyof React.JSX.IntrinsicElements; } - -export default class Box extends Component {} diff --git a/components/demo-helper/index.tsx b/components/demo-helper/index.tsx index 0c631abd14..490385f20d 100644 --- a/components/demo-helper/index.tsx +++ b/components/demo-helper/index.tsx @@ -36,7 +36,7 @@ export interface DemoFunctionDefineForObject { name?: string; label: string; value: unknown; - enum: Array<{ label: string; value: string }>; + enum: Array<{ label: string; value: string | boolean }>; } const COL = '{Col}'; @@ -127,7 +127,7 @@ function convertObjectToArray(demoFunction: Record { - return e.value; + return String(e.value); }), }); });