diff --git a/packages/arcodesign/CHANGELOG.md b/packages/arcodesign/CHANGELOG.md index fb7a0b7e..b54b620e 100644 --- a/packages/arcodesign/CHANGELOG.md +++ b/packages/arcodesign/CHANGELOG.md @@ -3,6 +3,64 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.28.2](https://github.com/arco-design/arco-design-mobile/compare/@arco-design/mobile-react@2.28.1...@arco-design/mobile-react@2.28.2) (2023-08-07) + + +### Bug Fixes + +* `Form` bind this in form-item ([#148](https://github.com/arco-design/arco-design-mobile/issues/148)) ([5616d53](https://github.com/arco-design/arco-design-mobile/commit/5616d537b921b009df61addccf966c5e9363a0cb)) +* `PickerView` flicker issue with picker off and on when momentum scrolling is not over ([#143](https://github.com/arco-design/arco-design-mobile/issues/143)) ([e91557f](https://github.com/arco-design/arco-design-mobile/commit/e91557fc1545dd6179329dc342de4967301a7d73)) +* `Tabs` use boundingRect instead of offset when calc tabs wrap size ([#146](https://github.com/arco-design/arco-design-mobile/issues/146)) ([7ece3a9](https://github.com/arco-design/arco-design-mobile/commit/7ece3a9eb76abb010aa88678af47c15a0bd319dc)) + + + + + +## [2.28.1](https://github.com/arco-design/arco-design-mobile/compare/@arco-design/mobile-react@2.28.0...@arco-design/mobile-react@2.28.1) (2023-08-02) + + +### Bug Fixes + +* `Avatar` make the prop "src" optional ([f99bbc8](https://github.com/arco-design/arco-design-mobile/commit/f99bbc812223ae6c8960b1d712040f1b221efb72)) +* add [@types](https://github.com/types) to peerDependencies ([42f3d5a](https://github.com/arco-design/arco-design-mobile/commit/42f3d5ab19144702d7c371c6cbd1aa031a690abe)) + + + + + +# [2.28.0](https://github.com/arco-design/arco-design-mobile/compare/@arco-design/mobile-react@2.27.5...@arco-design/mobile-react@2.28.0) (2023-07-14) + + +### Bug Fixes + +* `Collapse` update with the latest opened ([#140](https://github.com/arco-design/arco-design-mobile/issues/140)) ([b963787](https://github.com/arco-design/arco-design-mobile/commit/b96378761557f4d90f09f789f662e9d3588c3cbd)) +* `ImagePreview` fix scroll through ([ea3f9bc](https://github.com/arco-design/arco-design-mobile/commit/ea3f9bc5d0980f70c81e2de99084e0a11187b3c1)) +* `Sticky` support the method "updatePlaceholderLayout" ([842b4fe](https://github.com/arco-design/arco-design-mobile/commit/842b4fe6084b3ae282bd54850e381fbe34bd826f)) + + +### Features + +* RTL support for `Badge`, `Button`, `Cell`, `Checkbox`, `Form`, `Radio`, `Rate`, `Switch` and `Tabs` ([#135](https://github.com/arco-design/arco-design-mobile/issues/135)) ([97de976](https://github.com/arco-design/arco-design-mobile/commit/97de976ba514ec0f48103bd4f0c535ebceb8981a)) + + + + + +## [2.27.5](https://github.com/arco-design/arco-design-mobile/compare/@arco-design/mobile-react@2.27.4...@arco-design/mobile-react@2.27.5) (2023-07-04) + + +### Bug Fixes + +* `ImagePreview` fix long picture display problem ([#137](https://github.com/arco-design/arco-design-mobile/issues/137)) ([3df1f71](https://github.com/arco-design/arco-design-mobile/commit/3df1f71a8b1f63ed5e1986284f2746438de59d70)) +* `LoadMore` fix the inaccurate scrollheight when using multiple loadmore ([#130](https://github.com/arco-design/arco-design-mobile/issues/130)) ([da76f12](https://github.com/arco-design/arco-design-mobile/commit/da76f125734579a1921a3ace0964d5ff845cd545)) +* `Stepper` disable status ([#134](https://github.com/arco-design/arco-design-mobile/issues/134)) ([71dabe7](https://github.com/arco-design/arco-design-mobile/commit/71dabe71f3508ac8cb9a800d8ac1be01df509f28)) +* error caught when using "getComputedStyle" ([#129](https://github.com/arco-design/arco-design-mobile/issues/129)) ([daa8f67](https://github.com/arco-design/arco-design-mobile/commit/daa8f67961d9d2751a14c0c3f7759b54fe0579cb)) +* transition in StrictMode ([#131](https://github.com/arco-design/arco-design-mobile/issues/131)) ([084448b](https://github.com/arco-design/arco-design-mobile/commit/084448b0e4b43ea7d8632e232c1665ce00449c3d)) + + + + + ## [2.27.4](https://github.com/arco-design/arco-design-mobile/compare/@arco-design/mobile-react@2.27.3...@arco-design/mobile-react@2.27.4) (2023-05-19) diff --git a/packages/arcodesign/README.en-US.md b/packages/arcodesign/README.en-US.md index 3946b040..af310ce2 100644 --- a/packages/arcodesign/README.en-US.md +++ b/packages/arcodesign/README.en-US.md @@ -59,8 +59,8 @@ React & ReactDOM: **Click here** ``` - - + + ``` ## Full import diff --git a/packages/arcodesign/README.md b/packages/arcodesign/README.md index 2a87342c..1458f73e 100644 --- a/packages/arcodesign/README.md +++ b/packages/arcodesign/README.md @@ -59,8 +59,8 @@ React & ReactDOM: **戳这里获取** ``` - - + + ``` ## 引入全部 diff --git a/packages/arcodesign/components/_helpers/hooks.ts b/packages/arcodesign/components/_helpers/hooks.ts index dc8a4507..86090b6f 100644 --- a/packages/arcodesign/components/_helpers/hooks.ts +++ b/packages/arcodesign/components/_helpers/hooks.ts @@ -4,7 +4,7 @@ * @name_en General Hooks */ import React, { useState, useRef, useEffect, useCallback, useContext } from 'react'; -import { getSystem, scrollWithAnimation } from '@arco-design/mobile-utils'; +import { getSystem, scrollWithAnimation, safeGetComputedStyle } from '@arco-design/mobile-utils'; import { GlobalContext } from '../context-provider'; import { BezierType } from '../progress'; @@ -29,7 +29,12 @@ export function useListenResize(resizeHandler: () => void, deps: any[] = [], nee * @desc {en} Tips: Use in scenarios where asynchronous processing is not completed after unmount. It is not recommended to replace useState without brains * @param initialState 初始状态 * @param initialState {en} Initial State - * @returns 什么东西 + * @example + * ``` + * import { useMountedState } from '@arco-design/mobile-react/esm/_helpers/hooks'; + * + * const [scrollValue, setScrollValue] = useMountedState(value); + * ``` */ export function useMountedState(initialState: S | (() => S)) { const [state, setState] = useState(initialState); @@ -50,6 +55,18 @@ export function useMountedState(initialState: S | (() => S)) { return result; } +export function useSameRefState( + initialValue: T, +): [T, React.MutableRefObject, (data: T) => void] { + const [state, setState] = useState(initialValue); + const stateRef = useRef(state); + const setStateProxy = (data: T) => { + stateRef.current = data; + setState(data); + }; + return [state, stateRef, setStateProxy]; +} + export function useRefState( initialValue: T | (() => T), ): [T, React.MutableRefObject, React.Dispatch>] { @@ -164,7 +181,7 @@ export function usePopupScroll( scrollRef.current = actualEle.reduce( (acc, nowEle) => [ ...acc, - ...(nowEle && window.getComputedStyle(nowEle).overflow !== 'hidden' + ...(nowEle && safeGetComputedStyle(nowEle).overflow !== 'hidden' ? [ { ele: nowEle, @@ -420,13 +437,13 @@ export function usePreventBodyScroll( }, [visible]); } -export const useProgress = ( +export function useProgress( mountedTransition: boolean, percentage: number, duration: number, mountedBezier: BezierType, step: number, -): [number, boolean] => { +): [number, boolean] { const [currentPercentage, setCurrentPercentage] = useState(0); const [transitionControl, setTransitionControl] = useState(false); const [count, setCount] = useState(0); @@ -460,7 +477,7 @@ export const useProgress = ( }, [count, percentage, step]); return [currentPercentage, transitionControl]; -}; +} export function useSingleAndDoubleClick( onClick: (e: React.MouseEvent) => void, diff --git a/packages/arcodesign/components/avatar/README.en-US.md b/packages/arcodesign/components/avatar/README.en-US.md index 69b6f328..94be516b 100644 --- a/packages/arcodesign/components/avatar/README.en-US.md +++ b/packages/arcodesign/components/avatar/README.en-US.md @@ -12,7 +12,7 @@ Avatar component supports two shapes of circle and square, supports pictures or |----------|-------------|------|------| |shape|Shapre|"circle" \| "square"|"circle"| |size|Size|"medium" \| "large" \| "small" \| "smaller" \| "ultra\-small"|"small"| -|src|resource of avatar picture|string|required| +|src|resource of avatar picture|string|-| |imageProps|Image avatar component props, transparently passed to the Image component|Partial\\>|-| |decoration|the decoration for image avatar|ReactNode|null| |textAvatar|Text Avatar, two characters in Chinese, and three characters or less in English are recommended\.|string|""| diff --git a/packages/arcodesign/components/avatar/README.md b/packages/arcodesign/components/avatar/README.md index 9abd4232..4d0b2915 100644 --- a/packages/arcodesign/components/avatar/README.md +++ b/packages/arcodesign/components/avatar/README.md @@ -12,7 +12,7 @@ |----------|-------------|------|------| |shape|形状|"circle" \| "square"|"circle"| |size|尺寸|"medium" \| "large" \| "small" \| "smaller" \| "ultra\-small"|"small"| -|src|图片头像的资源地址|string|必填| +|src|图片头像的资源地址|string|-| |imageProps|图片头像组件参数,透传给Image组件|Partial\\>|-| |decoration|图片头像上的装饰|ReactNode|null| |textAvatar|文字头像,中文建议取两个字,英文建议在三个字以下|string|""| diff --git a/packages/arcodesign/components/avatar/__ast__/index.ast.json b/packages/arcodesign/components/avatar/__ast__/index.ast.json index bc222465..8f333573 100644 --- a/packages/arcodesign/components/avatar/__ast__/index.ast.json +++ b/packages/arcodesign/components/avatar/__ast__/index.ast.json @@ -89,7 +89,7 @@ "fileName": "arcom-github/packages/arcodesign/components/avatar/type.ts", "name": "AvatarProps" }, - "required": true, + "required": false, "type": { "name": "string" } diff --git a/packages/arcodesign/components/avatar/type.ts b/packages/arcodesign/components/avatar/type.ts index 63682255..2e5fefad 100644 --- a/packages/arcodesign/components/avatar/type.ts +++ b/packages/arcodesign/components/avatar/type.ts @@ -19,7 +19,7 @@ export interface AvatarProps extends BaseProp * 图片头像的资源地址 * @en resource of avatar picture */ - src: string; + src?: string; /** * 图片头像组件参数,透传给Image组件 * @en Image avatar component props, transparently passed to the Image component diff --git a/packages/arcodesign/components/badge/index.tsx b/packages/arcodesign/components/badge/index.tsx index e29056c4..b560659c 100644 --- a/packages/arcodesign/components/badge/index.tsx +++ b/packages/arcodesign/components/badge/index.tsx @@ -106,7 +106,7 @@ const Badge = forwardRef((props: BadgeProps, ref: Ref) => { return ( {({ prefixCls }) => ( - +
) const { key: groupKey } = useContext(CollapseKeyContext); const CollapseContext = allContexts[groupKey] || {}; const groupContext = useContext(CollapseContext) || {}; - const opened = useMemo(() => { + const [opened, openedRef, setOpened] = useSameRefState(false); + + useEffect(() => { let show = false; // 优先级: group > children // @en Priority: group > children @@ -54,7 +55,7 @@ export const Collapse = forwardRef((props: CollapseProps, ref: Ref) } else { show = itemActive; } - return show; + setOpened(show); }, [value, active, itemActive, groupContext.isGroup, groupContext.value]); const getContentHeight = () => { @@ -71,8 +72,8 @@ export const Collapse = forwardRef((props: CollapseProps, ref: Ref) setTimeout( () => contentWrapRef.current && - (contentWrapRef.current.style.height = opened ? 'auto' : '0px'), - opened ? transTimeout : 20, + (contentWrapRef.current.style.height = openedRef.current ? 'auto' : '0px'), + openedRef.current ? transTimeout : 20, ); }; @@ -93,10 +94,12 @@ export const Collapse = forwardRef((props: CollapseProps, ref: Ref) return; } const height = getContentHeight(); - contentWrapRef.current.style.height = opened ? `${contentHeightRef.current}px` : '0px'; + contentWrapRef.current.style.height = openedRef.current + ? `${contentHeightRef.current}px` + : '0px'; nextTick(() => { contentHeightRef.current = height; - resetHeight(opened ? `${height}px` : '0px'); + resetHeight(openedRef.current ? `${height}px` : '0px'); }); }; diff --git a/packages/arcodesign/components/form/form-item.tsx b/packages/arcodesign/components/form/form-item.tsx index 4c09b98c..5af065c9 100644 --- a/packages/arcodesign/components/form/form-item.tsx +++ b/packages/arcodesign/components/form/form-item.tsx @@ -59,7 +59,7 @@ class FormItemInner extends PureComponent { this._touched = true; const { shouldUpdate } = this.props; if (typeof shouldUpdate === 'function') { @@ -67,17 +67,17 @@ class FormItemInner extends PureComponent { return this._errors; - } + }; - isFieldTouched() { + isFieldTouched = () => { return this._touched; - } + }; - validateField(): Promise { + validateField = (): Promise => { const { validateMessages } = this.context; const { getFieldValue } = this.context.form; const { field, rules, onValidateStatusChange } = this.props; @@ -107,14 +107,14 @@ class FormItemInner extends PureComponent { const { field } = this.props; const { setFieldValue } = this.context.form; setFieldValue(field, value); this.validateField(); - } + }; innerTriggerFunction = (_, value, ...args) => { this.setFieldData(value); @@ -132,13 +132,13 @@ class FormItemInner extends PureComponent { const { children } = this.props; this.setFieldData(''); if (children.props?.onClear) { children.props?.onClear(...args); } - } + }; renderChildren() { const { diff --git a/packages/arcodesign/components/form/style/index.less b/packages/arcodesign/components/form/style/index.less index a7b4ac23..a0e07f65 100644 --- a/packages/arcodesign/components/form/style/index.less +++ b/packages/arcodesign/components/form/style/index.less @@ -20,12 +20,12 @@ .use-var(font-size, form-item-label-item-font-size); .use-var(line-height, form-item-label-item-line-height); .use-var(color, form-item-label-item-color); - .use-var(padding-right, form-item-label-item-gutter); + .use-var-with-rtl(padding-right, form-item-label-item-gutter); .use-var(width, form-item-label-item-width); &-required-asterisk { position: absolute; - left: -0.6em; + .set-prop-with-rtl(left, -0.6em); top: 0; .use-var(font-size, form-item-label-item-font-size); @@ -60,7 +60,7 @@ } svg { - .rem(margin-right, 4); + .rem-with-rtl(margin-right, 4); } margin-bottom: 16px; } @@ -75,7 +75,7 @@ &-item.@{prefix}-form-item-vertical { display: block; .arco-input-wrap { - padding-left: 0; + .set-prop-with-rtl(padding-left, 0); } .@{prefix}-form-label-item { width: auto; diff --git a/packages/arcodesign/components/image-preview/index.tsx b/packages/arcodesign/components/image-preview/index.tsx index 276e4b2d..5297fb45 100644 --- a/packages/arcodesign/components/image-preview/index.tsx +++ b/packages/arcodesign/components/image-preview/index.tsx @@ -982,7 +982,13 @@ const ImagePreview = forwardRef((props: ImagePreviewProps, ref: Ref ImageProps, ) { return ( - + {(allImages || []).map((image, index) => { const innerNode = (
HTMLElement \| Window|() => window| +|getOffsetNode|When multiple loadmores are on the same page, pass in the offsetHeight \+ offsetTop of the node instead of scrollHeight|() =\> HTMLElement \| Window|-| |trigger|The timing of triggering loading, when it is click, getData will be triggered after clicking|"scroll" \| "click"|"scroll"| |threshold|Scroll to how far from the bottom of the list to trigger getData, valid when the trigger state timing is 'scroll'|number|200| |getData|The request data method, after the asynchronous task ends, the callback can be called according to the task result to modify the internal state of loadmore|(callback: (status: LoadMoreStatus) =\> void) =\> void|-| diff --git a/packages/arcodesign/components/load-more/README.md b/packages/arcodesign/components/load-more/README.md index 5697de9b..c9c95858 100644 --- a/packages/arcodesign/components/load-more/README.md +++ b/packages/arcodesign/components/load-more/README.md @@ -20,6 +20,7 @@ |defaultStatus|组件加载初始状态,传入 "before\-ready" 则先加载组件但不请求数据|"before\-ready" \| "prepare"|"prepare"| |status|当前状态,传入则受控|"before\-ready" \| "prepare" \| "loading" \| "nomore" \| "retry"|-| |getScrollContainer|待计算滚动容器|() =\> HTMLElement \| Window|() => window| +|getOffsetNode|当多个 loadmore 在同一页面时,通过传入节点的 offsetHeight \+ offsetTop 代替 scrollHeight|() =\> HTMLElement \| Window|-| |trigger|触发loading的时机,当为click时,点击后将触发getData|"scroll" \| "click"|"scroll"| |threshold|滚动到离列表底部多远的位置触发getData,触发状态时机为'scroll'时有效|number|200| |getData|请求数据方法,可在异步任务结束后根据任务结果调用callback修改loadmore内部状态|(callback: (status: LoadMoreStatus) =\> void) =\> void|-| diff --git a/packages/arcodesign/components/load-more/__ast__/index.ast.json b/packages/arcodesign/components/load-more/__ast__/index.ast.json index b271d23b..856757a4 100644 --- a/packages/arcodesign/components/load-more/__ast__/index.ast.json +++ b/packages/arcodesign/components/load-more/__ast__/index.ast.json @@ -232,6 +232,23 @@ "name": "() => HTMLElement | Window" } }, + "getOffsetNode": { + "defaultValue": null, + "description": "当多个 loadmore 在同一页面时,通过传入节点的 offsetHeight + offsetTop 代替 scrollHeight\n@en When multiple loadmores are on the same page, pass in the offsetHeight + offsetTop of the node instead of scrollHeight", + "name": "getOffsetNode", + "tags": { + "en": "When multiple loadmores are on the same page, pass in the offsetHeight + offsetTop of the node instead of scrollHeight" + }, + "descWithTags": "当多个 loadmore 在同一页面时,通过传入节点的 offsetHeight + offsetTop 代替 scrollHeight", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/load-more/index.tsx", + "name": "LoadMoreProps" + }, + "required": false, + "type": { + "name": "() => HTMLElement | Window" + } + }, "trigger": { "defaultValue": { "value": "\"scroll\"" diff --git a/packages/arcodesign/components/load-more/index.tsx b/packages/arcodesign/components/load-more/index.tsx index cff20786..3678fe75 100644 --- a/packages/arcodesign/components/load-more/index.tsx +++ b/packages/arcodesign/components/load-more/index.tsx @@ -82,6 +82,11 @@ export interface LoadMoreProps { * @default () => window */ getScrollContainer?: () => HTMLElement | Window | null; + /** + * 当多个 loadmore 在同一页面时,通过传入节点的 offsetHeight + offsetTop 代替 scrollHeight + * @en When multiple loadmores are on the same page, pass in the offsetHeight + offsetTop of the node instead of scrollHeight + */ + getOffsetNode?: () => HTMLElement | Window | null; /** * 触发loading的时机,当为click时,点击后将触发getData * @en The timing of triggering loading, when it is click, getData will be triggered after clicking @@ -167,6 +172,7 @@ const LoadMore = forwardRef((props: LoadMoreProps, ref: Ref) => { defaultStatus = 'prepare', status, getScrollContainer, + getOffsetNode, trigger = 'scroll', threshold = 200, throttle = 0, @@ -272,7 +278,10 @@ const LoadMore = forwardRef((props: LoadMoreProps, ref: Ref) => { ); function checkNeedTrigger(top: number, ths: number) { - const scrollHeight = getScrollContainerAttribute('scrollHeight', getScrollContainer); + const scrollHeight = getOffsetNode + ? getScrollContainerAttribute('offsetHeight', getOffsetNode) + + getScrollContainerAttribute('offsetTop', getOffsetNode) + : getScrollContainerAttribute('scrollHeight', getScrollContainer); const clientHeight = getScrollContainerAttribute('clientHeight', getScrollContainer); return scrollHeight - top - clientHeight <= ths; } diff --git a/packages/arcodesign/components/masking/index.tsx b/packages/arcodesign/components/masking/index.tsx index b8e38b0a..719cc63f 100644 --- a/packages/arcodesign/components/masking/index.tsx +++ b/packages/arcodesign/components/masking/index.tsx @@ -346,6 +346,7 @@ const Masking = forwardRef((props: MaskingProps, ref: Ref) => { type={maskTransitionType} mountOnEnter={mountOnEnter} unmountOnExit={unmountOnExit} + nodeRef={maskRef} >
) => { type={contentTransitionType} mountOnEnter={mountOnEnter} unmountOnExit={unmountOnExit} + nodeRef={contentRef} >
) const rowCount = Math.max(rows % 2 === 0 ? rows + 1 : rows, 3); const isTouchMoveRef = useRef(false); const isTouchStopped = useRef(false); + const unmountCallbackRef = useRef<() => void>(); const timeRef = useRef(null); const colStyle = useMemo( @@ -104,19 +105,23 @@ const PickerCell = forwardRef((props: PickerCellProps, ref: Ref) function _scrollTo(transY: number, transDuration = 0, callback = () => {}) { setTransitionDuration(transDuration ? `${transDuration}ms` : ''); setTransformY(transY); - // 处理连续滑动的情况: - // @en handle the case of continuous sliding: - // 如果上一次callback还未执行,先cancel掉上一次回调,只执行最近的一次回调 - // @en If the last callback has not been executed, cancel the last callback first, and only execute the latest callback + // 处理连续滑动的情况:如果上一次callback还未执行,先cancel掉上一次回调 + // @en handle the case of continuous sliding: If the last callback has not been executed, cancel the last callback first if (latestCallbackTimer.current) { clearTimeout(latestCallbackTimer.current); } - latestCallbackTimer.current = window.setTimeout(() => { + const setNormalStatus = () => { + // 如果timer顺利执行,则在unmount时不再重复执行 + // @en If the timer is successfully executed, it will not be repeated when unmounting + unmountCallbackRef.current = undefined; movingStatusRef.current = 'normal'; setTransitionDuration(''); callback(); - }, transDuration); + }; + + unmountCallbackRef.current = setNormalStatus; + latestCallbackTimer.current = window.setTimeout(setNormalStatus, transDuration); } function _scrollToIndex(itemIndex: number, transDuration = 0, callback = () => {}) { @@ -323,6 +328,16 @@ const PickerCell = forwardRef((props: PickerCellProps, ref: Ref) _scrollToIndexWithChange(itemIndex, 200); } + useEffect(() => { + return () => { + // 卸载组件时,如果timer中还有未执行的onchange操作,则立刻执行该操作并移除timer + // @en When unloading the component, if there is an unexecuted onchange operation in the timer, execute it immediately and remove the timer + const timerId = latestCallbackTimer.current; + unmountCallbackRef.current?.(); + timerId && clearTimeout(timerId); + }; + }, []); + useEffect(() => { if (wrapRef.current) { wrapRef.current.addEventListener('touchstart', _handleColumnTouchStart); diff --git a/packages/arcodesign/components/popover/popover.tsx b/packages/arcodesign/components/popover/popover.tsx index 8238befa..9087abc1 100644 --- a/packages/arcodesign/components/popover/popover.tsx +++ b/packages/arcodesign/components/popover/popover.tsx @@ -50,6 +50,8 @@ export const Popover = forwardRef((props: PopoverProps, ref: Ref) => const wrapperRef = useRef(null); const childRef = useRef(null); const popoverRef = useRef(null); + const transitionNodeRef = useRef(null); + const maskRef = useRef(null); const { direction, position, @@ -176,7 +178,10 @@ export const Popover = forwardRef((props: PopoverProps, ref: Ref) => className={cls(`${theme}-theme`, innerPopoverClassName, { bordered, })} - ref={popoverRef} + ref={ele => { + popoverRef.current = ele; + transitionNodeRef.current = ele?.dom!; + }} direction={direction} minWidth={minWidth} maxWidth={maxWidth} @@ -207,6 +212,7 @@ export const Popover = forwardRef((props: PopoverProps, ref: Ref) => in={visibleState && !isCalcPosition} timeout={transitionTimeout} type={transitionName} + nodeRef={transitionNodeRef} mountOnEnter unmountOnExit > @@ -223,11 +229,16 @@ export const Popover = forwardRef((props: PopoverProps, ref: Ref) => -
+
)} diff --git a/packages/arcodesign/components/radio/style/index.less b/packages/arcodesign/components/radio/style/index.less index c496fb9e..555e1842 100644 --- a/packages/arcodesign/components/radio/style/index.less +++ b/packages/arcodesign/components/radio/style/index.less @@ -29,7 +29,7 @@ } .radio-icon + .radio-text { - .use-var(margin-left, checkbox-icon-margin-right); + .use-var-with-rtl(margin-left, checkbox-icon-margin-right); } &.disabled .radio-text { @@ -48,6 +48,6 @@ .@{prefix}-radio-group { .@{prefix}-radio:not(:last-child, .block) { - .use-var(margin-right, checkbox-group-gutter); + .use-var-with-rtl(margin-right, checkbox-group-gutter); } } diff --git a/packages/arcodesign/components/rate/index.tsx b/packages/arcodesign/components/rate/index.tsx index e8d68f56..b40ec6d2 100644 --- a/packages/arcodesign/components/rate/index.tsx +++ b/packages/arcodesign/components/rate/index.tsx @@ -224,7 +224,7 @@ const Rate = forwardRef((props: RateProps, ref: Ref) => { return ( - {({ prefixCls }) => ( + {({ prefixCls, useRtl }) => (
) => { // 对内的index从1开始,方便计算 // @en The index of the pair starts from 1, which is convenient for calculation const index = i + 1; + const halfIndex = allowHalf ? index - 0.5 : index; const status = getIconStatus(index); return (
) => {
- handleStarIndexChange(allowHalf ? index - 0.5 : index) + handleStarIndexChange(useRtl ? index : halfIndex) } />
handleStarIndexChange(index)} + onClick={() => + handleStarIndexChange(useRtl ? halfIndex : index) + } />
); diff --git a/packages/arcodesign/components/rate/style/index.less b/packages/arcodesign/components/rate/style/index.less index 43cdf6e8..a2d55c71 100644 --- a/packages/arcodesign/components/rate/style/index.less +++ b/packages/arcodesign/components/rate/style/index.less @@ -38,6 +38,10 @@ &.half-active { position: absolute; z-index: 1; + + [dir="rtl"] & { + transform: scaleX(-1); + } } } diff --git a/packages/arcodesign/components/skeleton/README.en-US.md b/packages/arcodesign/components/skeleton/README.en-US.md new file mode 100644 index 00000000..eb0527ba --- /dev/null +++ b/packages/arcodesign/components/skeleton/README.en-US.md @@ -0,0 +1,101 @@ +### Data Display + +# Skeleton + +Display a set of placeholder graphics during content loading + +====== + +> Props + +|Property|Description|Type|DefaultValue| +|----------|-------------|------|------| +|title|Show title placeholder|boolean \| SkeletonTitleProps|true| +|paragraph|Show paragraph placeholder|boolean \| SkeletonParagraphProps|true| +|avatar|Show Avatar placeholder|boolean \| SkeletonAvatarProps|false| +|grid|Show Grid placeholder\. When it's value is present, the paragraph, avatar or title placeholder will not be displayed, and four columns will be displayed by default|boolean \| SkeletonGridProps|false| +|showAnimation|Show loading effect|boolean|true| +|animation|Animation of loading effect, 'gradient' and 'breath' effects are optional|"gradient" \| "breath"|"gradient"| +|animationGradientColor|Highlight color of gradient animation|string|"rgba(0, 0, 0, 0.04)"| +|backgroundColor|Background color of skeleton item|string|"#F7F8FA"| +|children|Children element|ReactNode|null| +|className|Custom classname|string|""| +|style|Custom stylesheet|CSSProperties|{}| + +> Refs + +|Property|Description|Type| +|----------|-------------|------| +|dom|The outer DOM element of the component|HTMLDivElement| + +> Skeleton.Node + +|Property|Description|Type|DefaultValue| +|----------|-------------|------|------| +|children|Custom component content|ReactNode|null| +|className|Custom classname|string|""| +|style|Custom stylesheet|CSSProperties|{}| + +> Skeleton.Title + +|Property|Description|Type|DefaultValue| +|----------|-------------|------|------| +|width|The width of title|ReactText|"40%"| +|className|Custom classname|string|""| +|style|Custom stylesheet|CSSProperties|{}| + +> Skeleton.Paragraph + +|Property|Description|Type|DefaultValue| +|----------|-------------|------|------| +|rows|Number of lines for paragraph|number|3| +|width|The width of paragraph\. If width is an Array, it corresponds to the width of each line, otherwise it indicates the width of the last line|string \| number \| ReactText\[\]|"60%"| +|className|Custom classname|string|""| +|style|Custom stylesheet|CSSProperties|{}| + +> Skeleton.Avatar + +|Property|Description|Type|DefaultValue| +|----------|-------------|------|------| +|className|Custom classname|string|""| +|style|Custom stylesheet|CSSProperties|{}| + +> Skeleton.Grid + +|Property|Description|Type|DefaultValue| +|----------|-------------|------|------| +|columns|columns of grid|number|4| +|className|Custom classname|string|""| +|style|Custom stylesheet|CSSProperties|{}| + +> SkeletonTitleProps + +|Property|Description|Type|DefaultValue| +|----------|-------------|------|------| +|width|The width of title|ReactText|"40%"| +|className|Custom classname|string|""| +|style|Custom stylesheet|CSSProperties|{}| + +> SkeletonParagraphProps + +|Property|Description|Type|DefaultValue| +|----------|-------------|------|------| +|rows|Number of lines for paragraph|number|3| +|width|The width of paragraph\. If width is an Array, it corresponds to the width of each line, otherwise it indicates the width of the last line|string \| number \| ReactText\[\]|"60%"| +|className|Custom classname|string|""| +|style|Custom stylesheet|CSSProperties|{}| + +> SkeletonAvatarProps + +|Property|Description|Type|DefaultValue| +|----------|-------------|------|------| +|className|Custom classname|string|""| +|style|Custom stylesheet|CSSProperties|{}| + +> SkeletonGridProps + +|Property|Description|Type|DefaultValue| +|----------|-------------|------|------| +|columns|columns of grid|number|4| +|className|Custom classname|string|""| +|style|Custom stylesheet|CSSProperties|{}| diff --git a/packages/arcodesign/components/skeleton/README.md b/packages/arcodesign/components/skeleton/README.md new file mode 100644 index 00000000..0a4852aa --- /dev/null +++ b/packages/arcodesign/components/skeleton/README.md @@ -0,0 +1,101 @@ +### 信息展示 + +# 骨架屏 Skeleton + +在内容加载过程中展示一组占位图形。 + +====== + +> 属性/Props + +|参数|描述|类型|默认值| +|----------|-------------|------|------| +|title|是否显示标题占位图|boolean \| SkeletonTitleProps|true| +|paragraph|是否显示段落占位图|boolean \| SkeletonParagraphProps|true| +|avatar|是否显示头像占位图|boolean \| SkeletonAvatarProps|false| +|grid|是否显示金刚位占位图(如该参数非空时,默认展示四列金刚位,且不展示标题/段落/头像占位符)|boolean \| SkeletonGridProps|false| +|showAnimation|是否展示动画效果|boolean|true| +|animation|加载动画效果,可选“扫光”、“呼吸”两种效果|"gradient" \| "breath"|"gradient"| +|animationGradientColor|扫光动效高光颜色|string|"rgba(0, 0, 0, 0.04)"| +|backgroundColor|占位块背景色|string|"#F7F8FA"| +|children|子元素|ReactNode|null| +|className|自定义类名|string|""| +|style|自定义样式|CSSProperties|{}| + +> 引用/Refs + +|参数|描述|类型| +|----------|-------------|------| +|dom|最外层 DOM 元素|HTMLDivElement| + +> Skeleton.Node + +|参数|描述|类型|默认值| +|----------|-------------|------|------| +|children|自定义组件内容|ReactNode|null| +|className|自定义类名|string|""| +|style|自定义样式|CSSProperties|{}| + +> Skeleton.Title + +|参数|描述|类型|默认值| +|----------|-------------|------|------| +|width|标题占位图宽度|ReactText|"40%"| +|className|自定义类名|string|""| +|style|自定义样式|CSSProperties|{}| + +> Skeleton.Paragraph + +|参数|描述|类型|默认值| +|----------|-------------|------|------| +|rows|段落占位图的行数|number|3| +|width|段落占位图宽度,若为数组格式对应每行宽度,否则表示最后一行的宽度|string \| number \| ReactText\[\]|"60%"| +|className|自定义类名|string|""| +|style|自定义样式|CSSProperties|{}| + +> Skeleton.Avatar + +|参数|描述|类型|默认值| +|----------|-------------|------|------| +|className|自定义类名|string|""| +|style|自定义样式|CSSProperties|{}| + +> Skeleton.Grid + +|参数|描述|类型|默认值| +|----------|-------------|------|------| +|columns|金刚位列数|number|4| +|className|自定义类名|string|""| +|style|自定义样式|CSSProperties|{}| + +> SkeletonTitleProps + +|参数|描述|类型|默认值| +|----------|-------------|------|------| +|width|标题占位图宽度|ReactText|"40%"| +|className|自定义类名|string|""| +|style|自定义样式|CSSProperties|{}| + +> SkeletonParagraphProps + +|参数|描述|类型|默认值| +|----------|-------------|------|------| +|rows|段落占位图的行数|number|3| +|width|段落占位图宽度,若为数组格式对应每行宽度,否则表示最后一行的宽度|string \| number \| ReactText\[\]|"60%"| +|className|自定义类名|string|""| +|style|自定义样式|CSSProperties|{}| + +> SkeletonAvatarProps + +|参数|描述|类型|默认值| +|----------|-------------|------|------| +|className|自定义类名|string|""| +|style|自定义样式|CSSProperties|{}| + +> SkeletonGridProps + +|参数|描述|类型|默认值| +|----------|-------------|------|------| +|columns|金刚位列数|number|4| +|className|自定义类名|string|""| +|style|自定义样式|CSSProperties|{}| diff --git a/packages/arcodesign/components/skeleton/__ast__/index.ast.json b/packages/arcodesign/components/skeleton/__ast__/index.ast.json new file mode 100644 index 00000000..2a021601 --- /dev/null +++ b/packages/arcodesign/components/skeleton/__ast__/index.ast.json @@ -0,0 +1,1007 @@ +{ + "description": "在内容加载过程中展示一组占位图形。", + "descriptionTags": { + "en": "Display a set of placeholder graphics during content loading", + "type": "信息展示", + "type_en": "Data Display", + "name": "骨架屏", + "name_en": "Skeleton" + }, + "displayName": "Skeleton", + "methods": [], + "props": { + "title": { + "defaultValue": { + "value": "true" + }, + "description": "是否显示标题占位图\n@en Show title placeholder", + "name": "title", + "tags": { + "en": "Show title placeholder", + "default": "true" + }, + "descWithTags": "是否显示标题占位图", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/skeleton/type.ts", + "name": "SkeletonProps" + }, + "required": false, + "type": { + "name": "boolean | SkeletonTitleProps" + } + }, + "paragraph": { + "defaultValue": { + "value": "true" + }, + "description": "是否显示段落占位图\n@en Show paragraph placeholder", + "name": "paragraph", + "tags": { + "en": "Show paragraph placeholder", + "default": "true" + }, + "descWithTags": "是否显示段落占位图", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/skeleton/type.ts", + "name": "SkeletonProps" + }, + "required": false, + "type": { + "name": "boolean | SkeletonParagraphProps" + } + }, + "avatar": { + "defaultValue": { + "value": "false" + }, + "description": "是否显示头像占位图\n@en Show Avatar placeholder", + "name": "avatar", + "tags": { + "en": "Show Avatar placeholder", + "default": "false" + }, + "descWithTags": "是否显示头像占位图", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/skeleton/type.ts", + "name": "SkeletonProps" + }, + "required": false, + "type": { + "name": "boolean | SkeletonAvatarProps" + } + }, + "grid": { + "defaultValue": { + "value": "false" + }, + "description": "是否显示金刚位占位图(如该参数非空时,默认展示四列金刚位,且不展示标题/段落/头像占位符)\n@en Show Grid placeholder. When it's value is present, the paragraph, avatar or title placeholder will not be displayed, and four columns will be displayed by default", + "name": "grid", + "tags": { + "en": "Show Grid placeholder. When it's value is present, the paragraph, avatar or title placeholder will not be displayed, and four columns will be displayed by default", + "default": "false" + }, + "descWithTags": "是否显示金刚位占位图(如该参数非空时,默认展示四列金刚位,且不展示标题/段落/头像占位符)", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/skeleton/type.ts", + "name": "SkeletonProps" + }, + "required": false, + "type": { + "name": "boolean | SkeletonGridProps" + } + }, + "showAnimation": { + "defaultValue": { + "value": "true" + }, + "description": "是否展示动画效果\n@en Show loading effect", + "name": "showAnimation", + "tags": { + "en": "Show loading effect", + "default": "true" + }, + "descWithTags": "是否展示动画效果", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/skeleton/type.ts", + "name": "SkeletonProps" + }, + "required": false, + "type": { + "name": "boolean" + } + }, + "animation": { + "defaultValue": { + "value": "\"gradient\"" + }, + "description": "加载动画效果,可选“扫光”、“呼吸”两种效果\n@en Animation of loading effect, 'gradient' and 'breath' effects are optional", + "name": "animation", + "tags": { + "en": "Animation of loading effect, 'gradient' and 'breath' effects are optional", + "default": "\"gradient\"" + }, + "descWithTags": "加载动画效果,可选“扫光”、“呼吸”两种效果", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/skeleton/type.ts", + "name": "SkeletonProps" + }, + "required": false, + "type": { + "name": "enum", + "raw": "\"gradient\" | \"breath\"", + "value": [ + { + "value": "\"gradient\"" + }, + { + "value": "\"breath\"" + } + ] + } + }, + "animationGradientColor": { + "defaultValue": { + "value": "\"rgba(0, 0, 0, 0.04)\"" + }, + "description": "扫光动效高光颜色\n@en Highlight color of gradient animation", + "name": "animationGradientColor", + "tags": { + "en": "Highlight color of gradient animation", + "default": "\"rgba(0, 0, 0, 0.04)\"" + }, + "descWithTags": "扫光动效高光颜色", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/skeleton/type.ts", + "name": "SkeletonProps" + }, + "required": false, + "type": { + "name": "string" + } + }, + "backgroundColor": { + "defaultValue": { + "value": "\"#F7F8FA\"" + }, + "description": "占位块背景色\n@en Background color of skeleton item", + "name": "backgroundColor", + "tags": { + "en": "Background color of skeleton item", + "default": "\"#F7F8FA\"" + }, + "descWithTags": "占位块背景色", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/skeleton/type.ts", + "name": "SkeletonProps" + }, + "required": false, + "type": { + "name": "string" + } + }, + "children": { + "defaultValue": { + "value": "null" + }, + "description": "子元素\n@en Children element", + "name": "children", + "tags": { + "en": "Children element", + "default": "null" + }, + "descWithTags": "子元素", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/skeleton/type.ts", + "name": "SkeletonProps" + }, + "required": false, + "type": { + "name": "ReactNode" + } + }, + "className": { + "defaultValue": { + "value": "\"\"" + }, + "description": "自定义类名\n@en Custom classname", + "name": "className", + "tags": { + "en": "Custom classname", + "default": "\"\"" + }, + "descWithTags": "自定义类名", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/_helpers/type.ts", + "name": "SimpleBaseProps" + }, + "required": false, + "type": { + "name": "string" + } + }, + "style": { + "defaultValue": { + "value": "{}" + }, + "description": "自定义样式\n@en Custom stylesheet", + "name": "style", + "tags": { + "en": "Custom stylesheet", + "default": "{}" + }, + "descWithTags": "自定义样式", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/_helpers/type.ts", + "name": "SimpleBaseProps" + }, + "required": false, + "type": { + "name": "CSSProperties" + } + }, + "ref": { + "defaultValue": null, + "description": "", + "name": "ref", + "tags": {}, + "descWithTags": "", + "parent": { + "fileName": "arcom-github/node_modules/@types/react/index.d.ts", + "name": "RefAttributes" + }, + "required": false, + "type": { + "name": "Ref" + } + } + }, + "deps": { + "SkeletonTitleProps": { + "width": { + "name": "width", + "required": false, + "description": "标题占位图宽度\n@en The width of title", + "defaultValue": { + "value": "\"40%\"" + }, + "type": { + "name": "ReactText" + }, + "tags": { + "en": "The width of title", + "default": "\"40%\"" + }, + "descWithTags": "标题占位图宽度" + }, + "className": { + "name": "className", + "required": false, + "description": "自定义类名\n@en Custom classname", + "defaultValue": { + "value": "\"\"" + }, + "type": { + "name": "string" + }, + "tags": { + "en": "Custom classname", + "default": "\"\"" + }, + "descWithTags": "自定义类名" + }, + "style": { + "name": "style", + "required": false, + "description": "自定义样式\n@en Custom stylesheet", + "defaultValue": { + "value": "{}" + }, + "type": { + "name": "CSSProperties" + }, + "tags": { + "en": "Custom stylesheet", + "default": "{}" + }, + "descWithTags": "自定义样式" + } + }, + "SkeletonParagraphProps": { + "rows": { + "name": "rows", + "required": false, + "description": "段落占位图的行数\n@en Number of lines for paragraph", + "defaultValue": { + "value": "3" + }, + "type": { + "name": "number" + }, + "tags": { + "en": "Number of lines for paragraph", + "default": "3" + }, + "descWithTags": "段落占位图的行数" + }, + "width": { + "name": "width", + "required": false, + "description": "段落占位图宽度,若为数组格式对应每行宽度,否则表示最后一行的宽度\n@en The width of paragraph. If width is an Array, it corresponds to the width of each line, otherwise it indicates the width of the last line", + "defaultValue": { + "value": "\"60%\"" + }, + "type": { + "name": "string | number | ReactText[]" + }, + "tags": { + "en": "The width of paragraph. If width is an Array, it corresponds to the width of each line, otherwise it indicates the width of the last line", + "default": "\"60%\"" + }, + "descWithTags": "段落占位图宽度,若为数组格式对应每行宽度,否则表示最后一行的宽度" + }, + "className": { + "name": "className", + "required": false, + "description": "自定义类名\n@en Custom classname", + "defaultValue": { + "value": "\"\"" + }, + "type": { + "name": "string" + }, + "tags": { + "en": "Custom classname", + "default": "\"\"" + }, + "descWithTags": "自定义类名" + }, + "style": { + "name": "style", + "required": false, + "description": "自定义样式\n@en Custom stylesheet", + "defaultValue": { + "value": "{}" + }, + "type": { + "name": "CSSProperties" + }, + "tags": { + "en": "Custom stylesheet", + "default": "{}" + }, + "descWithTags": "自定义样式" + } + }, + "SkeletonAvatarProps": { + "className": { + "name": "className", + "required": false, + "description": "自定义类名\n@en Custom classname", + "defaultValue": { + "value": "\"\"" + }, + "type": { + "name": "string" + }, + "tags": { + "en": "Custom classname", + "default": "\"\"" + }, + "descWithTags": "自定义类名" + }, + "style": { + "name": "style", + "required": false, + "description": "自定义样式\n@en Custom stylesheet", + "defaultValue": { + "value": "{}" + }, + "type": { + "name": "CSSProperties" + }, + "tags": { + "en": "Custom stylesheet", + "default": "{}" + }, + "descWithTags": "自定义样式" + } + }, + "SkeletonGridProps": { + "columns": { + "name": "columns", + "required": false, + "description": "金刚位列数\n@en columns of grid", + "defaultValue": { + "value": "4" + }, + "type": { + "name": "number" + }, + "tags": { + "en": "columns of grid", + "default": "4" + }, + "descWithTags": "金刚位列数" + }, + "className": { + "name": "className", + "required": false, + "description": "自定义类名\n@en Custom classname", + "defaultValue": { + "value": "\"\"" + }, + "type": { + "name": "string" + }, + "tags": { + "en": "Custom classname", + "default": "\"\"" + }, + "descWithTags": "自定义类名" + }, + "style": { + "name": "style", + "required": false, + "description": "自定义样式\n@en Custom stylesheet", + "defaultValue": { + "value": "{}" + }, + "type": { + "name": "CSSProperties" + }, + "tags": { + "en": "Custom stylesheet", + "default": "{}" + }, + "descWithTags": "自定义样式" + } + }, + "SkeletonRef": { + "dom": { + "name": "dom", + "required": true, + "description": "最外层 DOM 元素\n@en The outer DOM element of the component", + "defaultValue": null, + "type": { + "name": "HTMLDivElement" + }, + "tags": { + "en": "The outer DOM element of the component" + }, + "descWithTags": "最外层 DOM 元素" + } + } + }, + "depComps": { + "Node": { + "description": "", + "descriptionTags": {}, + "displayName": "SkeletonNode", + "methods": [], + "props": { + "children": { + "defaultValue": { + "value": "null" + }, + "description": "自定义组件内容\n@en Custom component content", + "name": "children", + "tags": { + "en": "Custom component content", + "default": "null" + }, + "descWithTags": "自定义组件内容", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/_helpers/type.ts", + "name": "BaseProps" + }, + "required": false, + "type": { + "name": "ReactNode" + } + }, + "className": { + "defaultValue": { + "value": "\"\"" + }, + "description": "自定义类名\n@en Custom classname", + "name": "className", + "tags": { + "en": "Custom classname", + "default": "\"\"" + }, + "descWithTags": "自定义类名", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/_helpers/type.ts", + "name": "SimpleBaseProps" + }, + "required": false, + "type": { + "name": "string" + } + }, + "style": { + "defaultValue": { + "value": "{}" + }, + "description": "自定义样式\n@en Custom stylesheet", + "name": "style", + "tags": { + "en": "Custom stylesheet", + "default": "{}" + }, + "descWithTags": "自定义样式", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/_helpers/type.ts", + "name": "SimpleBaseProps" + }, + "required": false, + "type": { + "name": "CSSProperties" + } + }, + "ref": { + "defaultValue": null, + "description": "", + "name": "ref", + "tags": {}, + "descWithTags": "", + "parent": { + "fileName": "arcom-github/node_modules/@types/react/index.d.ts", + "name": "RefAttributes" + }, + "required": false, + "type": { + "name": "Ref" + } + } + }, + "deps": { + "SkeletonRef": { + "dom": { + "name": "dom", + "required": true, + "description": "最外层 DOM 元素\n@en The outer DOM element of the component", + "defaultValue": null, + "type": { + "name": "HTMLDivElement" + }, + "tags": { + "en": "The outer DOM element of the component" + }, + "descWithTags": "最外层 DOM 元素" + } + } + }, + "depComps": {}, + "typeNameInfo": { + "props": "BaseProps", + "ref": "SkeletonRef" + } + }, + "Title": { + "description": "", + "descriptionTags": {}, + "displayName": "SkeletonTitle", + "methods": [], + "props": { + "width": { + "defaultValue": { + "value": "\"40%\"" + }, + "description": "标题占位图宽度\n@en The width of title", + "name": "width", + "tags": { + "en": "The width of title", + "default": "\"40%\"" + }, + "descWithTags": "标题占位图宽度", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/skeleton/type.ts", + "name": "SkeletonTitleProps" + }, + "required": false, + "type": { + "name": "ReactText" + } + }, + "className": { + "defaultValue": { + "value": "\"\"" + }, + "description": "自定义类名\n@en Custom classname", + "name": "className", + "tags": { + "en": "Custom classname", + "default": "\"\"" + }, + "descWithTags": "自定义类名", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/_helpers/type.ts", + "name": "SimpleBaseProps" + }, + "required": false, + "type": { + "name": "string" + } + }, + "style": { + "defaultValue": { + "value": "{}" + }, + "description": "自定义样式\n@en Custom stylesheet", + "name": "style", + "tags": { + "en": "Custom stylesheet", + "default": "{}" + }, + "descWithTags": "自定义样式", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/_helpers/type.ts", + "name": "SimpleBaseProps" + }, + "required": false, + "type": { + "name": "CSSProperties" + } + }, + "ref": { + "defaultValue": null, + "description": "", + "name": "ref", + "tags": {}, + "descWithTags": "", + "parent": { + "fileName": "arcom-github/node_modules/@types/react/index.d.ts", + "name": "RefAttributes" + }, + "required": false, + "type": { + "name": "Ref" + } + } + }, + "deps": { + "SkeletonRef": { + "dom": { + "name": "dom", + "required": true, + "description": "最外层 DOM 元素\n@en The outer DOM element of the component", + "defaultValue": null, + "type": { + "name": "HTMLDivElement" + }, + "tags": { + "en": "The outer DOM element of the component" + }, + "descWithTags": "最外层 DOM 元素" + } + } + }, + "depComps": {}, + "typeNameInfo": { + "props": "SkeletonTitleProps", + "ref": "SkeletonRef" + } + }, + "Paragraph": { + "description": "", + "descriptionTags": {}, + "displayName": "SkeletonParagraph", + "methods": [], + "props": { + "rows": { + "defaultValue": { + "value": "3" + }, + "description": "段落占位图的行数\n@en Number of lines for paragraph", + "name": "rows", + "tags": { + "en": "Number of lines for paragraph", + "default": "3" + }, + "descWithTags": "段落占位图的行数", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/skeleton/type.ts", + "name": "SkeletonParagraphProps" + }, + "required": false, + "type": { + "name": "number" + } + }, + "width": { + "defaultValue": { + "value": "\"60%\"" + }, + "description": "段落占位图宽度,若为数组格式对应每行宽度,否则表示最后一行的宽度\n@en The width of paragraph. If width is an Array, it corresponds to the width of each line, otherwise it indicates the width of the last line", + "name": "width", + "tags": { + "en": "The width of paragraph. If width is an Array, it corresponds to the width of each line, otherwise it indicates the width of the last line", + "default": "\"60%\"" + }, + "descWithTags": "段落占位图宽度,若为数组格式对应每行宽度,否则表示最后一行的宽度", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/skeleton/type.ts", + "name": "SkeletonParagraphProps" + }, + "required": false, + "type": { + "name": "string | number | ReactText[]" + } + }, + "className": { + "defaultValue": { + "value": "\"\"" + }, + "description": "自定义类名\n@en Custom classname", + "name": "className", + "tags": { + "en": "Custom classname", + "default": "\"\"" + }, + "descWithTags": "自定义类名", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/_helpers/type.ts", + "name": "SimpleBaseProps" + }, + "required": false, + "type": { + "name": "string" + } + }, + "style": { + "defaultValue": { + "value": "{}" + }, + "description": "自定义样式\n@en Custom stylesheet", + "name": "style", + "tags": { + "en": "Custom stylesheet", + "default": "{}" + }, + "descWithTags": "自定义样式", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/_helpers/type.ts", + "name": "SimpleBaseProps" + }, + "required": false, + "type": { + "name": "CSSProperties" + } + }, + "ref": { + "defaultValue": null, + "description": "", + "name": "ref", + "tags": {}, + "descWithTags": "", + "parent": { + "fileName": "arcom-github/node_modules/@types/react/index.d.ts", + "name": "RefAttributes" + }, + "required": false, + "type": { + "name": "Ref" + } + } + }, + "deps": { + "SkeletonRef": { + "dom": { + "name": "dom", + "required": true, + "description": "最外层 DOM 元素\n@en The outer DOM element of the component", + "defaultValue": null, + "type": { + "name": "HTMLDivElement" + }, + "tags": { + "en": "The outer DOM element of the component" + }, + "descWithTags": "最外层 DOM 元素" + } + } + }, + "depComps": {}, + "typeNameInfo": { + "props": "SkeletonParagraphProps", + "ref": "SkeletonRef" + } + }, + "Avatar": { + "description": "", + "descriptionTags": {}, + "displayName": "SkeletonAvatar", + "methods": [], + "props": { + "className": { + "defaultValue": { + "value": "\"\"" + }, + "description": "自定义类名\n@en Custom classname", + "name": "className", + "tags": { + "en": "Custom classname", + "default": "\"\"" + }, + "descWithTags": "自定义类名", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/_helpers/type.ts", + "name": "SimpleBaseProps" + }, + "required": false, + "type": { + "name": "string" + } + }, + "style": { + "defaultValue": { + "value": "{}" + }, + "description": "自定义样式\n@en Custom stylesheet", + "name": "style", + "tags": { + "en": "Custom stylesheet", + "default": "{}" + }, + "descWithTags": "自定义样式", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/_helpers/type.ts", + "name": "SimpleBaseProps" + }, + "required": false, + "type": { + "name": "CSSProperties" + } + }, + "ref": { + "defaultValue": null, + "description": "", + "name": "ref", + "tags": {}, + "descWithTags": "", + "parent": { + "fileName": "arcom-github/node_modules/@types/react/index.d.ts", + "name": "RefAttributes" + }, + "required": false, + "type": { + "name": "Ref" + } + } + }, + "deps": { + "SkeletonRef": { + "dom": { + "name": "dom", + "required": true, + "description": "最外层 DOM 元素\n@en The outer DOM element of the component", + "defaultValue": null, + "type": { + "name": "HTMLDivElement" + }, + "tags": { + "en": "The outer DOM element of the component" + }, + "descWithTags": "最外层 DOM 元素" + } + } + }, + "depComps": {}, + "typeNameInfo": { + "props": "SimpleBaseProps", + "ref": "SkeletonRef" + } + }, + "Grid": { + "description": "", + "descriptionTags": {}, + "displayName": "SkeletonGrid", + "methods": [], + "props": { + "columns": { + "defaultValue": { + "value": "4" + }, + "description": "金刚位列数\n@en columns of grid", + "name": "columns", + "tags": { + "en": "columns of grid", + "default": "4" + }, + "descWithTags": "金刚位列数", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/skeleton/type.ts", + "name": "SkeletonGridProps" + }, + "required": false, + "type": { + "name": "number" + } + }, + "className": { + "defaultValue": { + "value": "\"\"" + }, + "description": "自定义类名\n@en Custom classname", + "name": "className", + "tags": { + "en": "Custom classname", + "default": "\"\"" + }, + "descWithTags": "自定义类名", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/_helpers/type.ts", + "name": "SimpleBaseProps" + }, + "required": false, + "type": { + "name": "string" + } + }, + "style": { + "defaultValue": { + "value": "{}" + }, + "description": "自定义样式\n@en Custom stylesheet", + "name": "style", + "tags": { + "en": "Custom stylesheet", + "default": "{}" + }, + "descWithTags": "自定义样式", + "parent": { + "fileName": "arcom-github/packages/arcodesign/components/_helpers/type.ts", + "name": "SimpleBaseProps" + }, + "required": false, + "type": { + "name": "CSSProperties" + } + }, + "ref": { + "defaultValue": null, + "description": "", + "name": "ref", + "tags": {}, + "descWithTags": "", + "parent": { + "fileName": "arcom-github/node_modules/@types/react/index.d.ts", + "name": "RefAttributes" + }, + "required": false, + "type": { + "name": "Ref" + } + } + }, + "deps": { + "SkeletonRef": { + "dom": { + "name": "dom", + "required": true, + "description": "最外层 DOM 元素\n@en The outer DOM element of the component", + "defaultValue": null, + "type": { + "name": "HTMLDivElement" + }, + "tags": { + "en": "The outer DOM element of the component" + }, + "descWithTags": "最外层 DOM 元素" + } + } + }, + "depComps": {}, + "typeNameInfo": { + "props": "SkeletonGridProps", + "ref": "SkeletonRef" + } + } + }, + "typeNameInfo": { + "props": "SkeletonProps", + "ref": "SkeletonRef" + }, + "isDefaultExport": true +} \ No newline at end of file diff --git a/packages/arcodesign/components/skeleton/__test__/__snapshots__/index.spec.js.snap b/packages/arcodesign/components/skeleton/__test__/__snapshots__/index.spec.js.snap new file mode 100644 index 00000000..149464ba --- /dev/null +++ b/packages/arcodesign/components/skeleton/__test__/__snapshots__/index.spec.js.snap @@ -0,0 +1,574 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`skeleton demo test skeleton demo: animation.md renders correctly 1`] = ` +
+
+
+
+
+
+
+ show animation +
+
+
+
+
+
+
+
+
+
+
+
+
+ type +
+
+
+
+ + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`skeleton demo test skeleton demo: avatar.md renders correctly 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`skeleton demo test skeleton demo: custom.md renders correctly 1`] = ` +
+
+
+
+
+
+
+ loading +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`skeleton demo test skeleton demo: grid.md renders correctly 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; + +exports[`skeleton demo test skeleton demo: index.md renders correctly 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`; diff --git a/packages/arcodesign/components/skeleton/__test__/index.spec.js b/packages/arcodesign/components/skeleton/__test__/index.spec.js new file mode 100644 index 00000000..c47d753d --- /dev/null +++ b/packages/arcodesign/components/skeleton/__test__/index.spec.js @@ -0,0 +1,78 @@ +import React from 'react'; +import { mount } from 'enzyme'; +import demoTest from '../../../tests/demoTest'; +import mountTest from '../../../tests/mountTest'; +import { defaultContext } from '../../context-provider'; +import Skeleton from '..'; + +demoTest('skeleton'); + +mountTest(Skeleton, 'Skeleton'); + +const prefix = `${defaultContext.prefixCls}-skeleton`; + +describe('Skeleton', () => { + it('should render correctly when set ref', () => { + const ref = React.createRef(); + const titleRef = React.createRef(); + const paragraphRef = React.createRef(); + const avatarRef = React.createRef(); + const nodeRef = React.createRef(); + mount( + + + + + +
+ + , + ); + expect(ref.current.dom).not.toBeUndefined(); + expect(titleRef.current.dom).not.toBeUndefined(); + expect(paragraphRef.current.dom).not.toBeUndefined(); + expect(avatarRef.current.dom).not.toBeUndefined(); + expect(nodeRef.current.dom).not.toBeUndefined(); + }); + it('should render correctly when set avatar', () => { + const comp = mount(); + expect(comp.find(`.${prefix}-avatar`).length).toBe(1); + }); + it('should render correctly when set grid', () => { + const comp = mount(); + expect(comp.find(`.${prefix}-grid-item`).length).toBe(5); + }); + it('should render correctly when set children', () => { + const comp = mount( + + +
+ + , + ); + expect(comp.find(`.${prefix}-node`).length).toBe(1); + expect(comp.find(`.${prefix}-title`).length).toBe(1); + expect(comp.find(`.${prefix}-paragraph`).length).toBe(1); + }); + it('should render correctly when set animation', () => { + const comp = mount( + + +
+ + , + ); + expect(comp.find(`.${prefix}-animation-gradient`).length).toBe(5); + expect(comp.find(`.${prefix}-animation-breath`).length).toBe(0); + comp.setProps({ + animation: 'breath', + }); + expect(comp.find(`.${prefix}-animation-gradient`).length).toBe(0); + expect(comp.find(`.${prefix}-animation-breath`).length).toBe(5); + comp.setProps({ + showAnimation: false, + }); + expect(comp.find(`.${prefix}-animation-gradient`).length).toBe(0); + expect(comp.find(`.${prefix}-animation-breath`).length).toBe(0); + }); +}); diff --git a/packages/arcodesign/components/skeleton/demo/animation.md b/packages/arcodesign/components/skeleton/demo/animation.md new file mode 100644 index 00000000..12abd610 --- /dev/null +++ b/packages/arcodesign/components/skeleton/demo/animation.md @@ -0,0 +1,39 @@ +## 动效 @en{Animation} + +#### 5 + +```js +import { Cell, Checkbox, Radio, Skeleton, Switch } from '@arco-design/mobile-react'; + +const options = [ + { label: 'gradient', value: 'gradient' }, + { label: 'breath', value: 'breath' }, +]; + +const color = 'rgba(0, 0, 0, 4%)'; + +export default function SkeletonDemo() { + const [type, setType] = React.useState('gradient'); + const [checked, setChecked] = React.useState(true); + return ( +
+ + + + + {checked && ( + + + + )} + + +
+ ); +} +``` diff --git a/packages/arcodesign/components/skeleton/demo/avatar.md b/packages/arcodesign/components/skeleton/demo/avatar.md new file mode 100644 index 00000000..05165490 --- /dev/null +++ b/packages/arcodesign/components/skeleton/demo/avatar.md @@ -0,0 +1,11 @@ +## 含头像 @en{With avatar} + +#### 2 + +```js +import { Skeleton } from '@arco-design/mobile-react'; + +export default function SkeletonDemo() { + return ; +} +``` diff --git a/packages/arcodesign/components/skeleton/demo/custom.md b/packages/arcodesign/components/skeleton/demo/custom.md new file mode 100644 index 00000000..a79d378f --- /dev/null +++ b/packages/arcodesign/components/skeleton/demo/custom.md @@ -0,0 +1,46 @@ +## 独立节点自由组合 @en{Custom} + +#### 5 + +```js +import { Cell, Skeleton, Switch } from '@arco-design/mobile-react'; + +export default function SkeletonDemo() { + const [loading, setLoading] = React.useState(true); + return ( +
+ + + + + + {loading ? ( + + +
+ + +
+ +
+ + + + ) : ( +
actual content
+ )} +
+ ); +} +``` diff --git a/packages/arcodesign/components/skeleton/demo/grid.md b/packages/arcodesign/components/skeleton/demo/grid.md new file mode 100644 index 00000000..7bbb9fe8 --- /dev/null +++ b/packages/arcodesign/components/skeleton/demo/grid.md @@ -0,0 +1,11 @@ +## 金刚区 @en{Grid} + +#### 3 + +```js +import { Skeleton } from '@arco-design/mobile-react'; + +export default function SkeletonDemo() { + return ; +} +``` diff --git a/packages/arcodesign/components/skeleton/demo/index.md b/packages/arcodesign/components/skeleton/demo/index.md new file mode 100644 index 00000000..3d033861 --- /dev/null +++ b/packages/arcodesign/components/skeleton/demo/index.md @@ -0,0 +1,11 @@ +## 基础用法 @en{Basic} + +#### 1 + +```js +import { Skeleton } from '@arco-design/mobile-react'; + +export default function SkeletonDemo() { + return ; +} +``` diff --git a/packages/arcodesign/components/skeleton/demo/style/mobile.less b/packages/arcodesign/components/skeleton/demo/style/mobile.less new file mode 100644 index 00000000..366ec254 --- /dev/null +++ b/packages/arcodesign/components/skeleton/demo/style/mobile.less @@ -0,0 +1,13 @@ +@import '../../../../style/mixin.less'; + +#demo-skeleton { + .arcodesign-mobile-demo-content { + .arco-cell-group { + margin-bottom: 0.32rem; + } + .arco-cell { + margin-left: 0; + padding-right: 0; + } + } +} diff --git a/packages/arcodesign/components/skeleton/elements.tsx b/packages/arcodesign/components/skeleton/elements.tsx new file mode 100644 index 00000000..48dfa65d --- /dev/null +++ b/packages/arcodesign/components/skeleton/elements.tsx @@ -0,0 +1,278 @@ +import React, { + Ref, + forwardRef, + useContext, + useEffect, + useImperativeHandle, + useRef, + useState, +} from 'react'; +import { cls, isArray, nextTick } from '@arco-design/mobile-utils'; +import { useListenResize } from '../_helpers'; +import { GlobalContext } from '../context-provider'; +import { + SkeletonAvatarProps, + SkeletonGridProps, + SkeletonNodeProps, + SkeletonParagraphProps, + SkeletonRef, + SkeletonTitleProps, +} from './type'; +import { SkeletonContext } from './skeleton-context'; + +const calcOffset = (dom?: HTMLElement | null, useRtl?: boolean) => { + if (!dom) { + return 0; + } + if (useRtl) { + return dom.offsetLeft - dom.offsetTop; + } + return dom.offsetLeft + dom.offsetTop; +}; + +function useOffset | T[]>( + dom?: K, + useRtl?: boolean, +) { + const [offset, setOffset] = useState(); + const calcLayout = () => { + const isList = Array.isArray(dom); + if ( + !(isList ? (dom as T[]).length > 0 : (dom as React.MutableRefObject)?.current) + ) { + return; + } + setOffset( + isList + ? (dom as T[]).map(item => calcOffset(item, useRtl)) + : calcOffset((dom as React.MutableRefObject).current, useRtl), + ); + }; + + useEffect(() => { + nextTick(() => { + calcLayout(); + }); + }, [dom, useRtl]); + + useListenResize(calcLayout); + return offset as K extends T[] ? number[] : number; +} + +export const SkeletonNode = forwardRef((props: SkeletonNodeProps, ref: Ref) => { + const { className = '', style, children } = props; + const { useRtl, prefixCls } = useContext(GlobalContext); + const { backgroundColor, showAnimation, animation } = useContext(SkeletonContext); + const domRef = useRef(null); + const isGradientAnimation = showAnimation && animation === 'gradient'; + const offset = useOffset(isGradientAnimation ? domRef : undefined, useRtl); + + useImperativeHandle(ref, () => ({ + dom: domRef.current, + })); + + return ( +
+ {children} + {isGradientAnimation && offset !== undefined && ( +
+ )} +
+ ); +}); + +export const SkeletonTitle = forwardRef((props: SkeletonTitleProps, ref: Ref) => { + const { className = '', style, width = '40%' } = props; + const { useRtl, prefixCls } = useContext(GlobalContext); + const { backgroundColor, showAnimation, animation } = useContext(SkeletonContext); + const domRef = useRef(null); + const isGradientAnimation = showAnimation && animation === 'gradient'; + const offset = useOffset(isGradientAnimation ? domRef : undefined, useRtl); + + useImperativeHandle(ref, () => ({ + dom: domRef.current, + })); + + return ( +
+ {isGradientAnimation && offset !== undefined && ( +
+ )} +
+ ); +}); + +export const SkeletonParagraph = forwardRef( + (props: SkeletonParagraphProps, ref: Ref) => { + const { className = '', style, rows = 3, width = '60%' } = props; + const { useRtl, prefixCls } = useContext(GlobalContext); + const { backgroundColor, showAnimation, animation } = useContext(SkeletonContext); + const domRef = useRef(null); + const lineDomRefs = useRef([]); + const isGradientAnimation = showAnimation && animation === 'gradient'; + const offsets = useOffset(isGradientAnimation ? lineDomRefs.current : undefined, useRtl); + + useImperativeHandle(ref, () => ({ + dom: domRef.current, + })); + + const getWidth = (idx: number) => { + if (isArray(width)) { + return width[idx]; + } + if (rows - 1 === idx) { + return width; + } + return undefined; + }; + + return ( +
+ {Array.from(new Array(rows)).map((_, idx) => ( +
el && (lineDomRefs.current[idx] = el)} + > + {isGradientAnimation && offsets !== undefined && ( +
+ )} +
+ ))} +
+ ); + }, +); + +export const SkeletonAvatar = forwardRef((props: SkeletonAvatarProps, ref: Ref) => { + const { className = '', style } = props; + const { useRtl, prefixCls } = useContext(GlobalContext); + const { backgroundColor, showAnimation, animation } = useContext(SkeletonContext); + const domRef = useRef(null); + const isGradientAnimation = showAnimation && animation === 'gradient'; + const offset = useOffset(isGradientAnimation ? domRef : undefined, useRtl); + + useImperativeHandle(ref, () => ({ + dom: domRef.current, + })); + + return ( +
+ {isGradientAnimation && offset !== undefined && ( +
+ )} +
+ ); +}); + +export const SkeletonGrid = forwardRef((props: SkeletonGridProps, ref: Ref) => { + const { className = '', style, columns = 4 } = props; + const { useRtl, prefixCls } = useContext(GlobalContext); + const { backgroundColor, showAnimation, animation } = useContext(SkeletonContext); + const domRef = useRef(null); + const iconDomRefs = useRef([]); + const textDomRefs = useRef([]); + const isGradientAnimation = showAnimation && animation === 'gradient'; + const iconOffsets = useOffset(isGradientAnimation ? iconDomRefs.current : undefined, useRtl); + const textOffsets = useOffset(isGradientAnimation ? textDomRefs.current : undefined, useRtl); + + useImperativeHandle(ref, () => ({ + dom: domRef.current, + })); + + return ( +
+ {Array.from(new Array(columns)).map((_, idx) => ( +
+
el && (iconDomRefs.current[idx] = el)} + > + {isGradientAnimation && iconOffsets !== undefined && ( +
+ )} +
+
el && (textDomRefs.current[idx] = el)} + > + {isGradientAnimation && textOffsets !== undefined && ( +
+ )} +
+
+ ))} +
+ ); +}); diff --git a/packages/arcodesign/components/skeleton/index.tsx b/packages/arcodesign/components/skeleton/index.tsx new file mode 100644 index 00000000..a1f3e683 --- /dev/null +++ b/packages/arcodesign/components/skeleton/index.tsx @@ -0,0 +1,94 @@ +import React, { useRef, forwardRef, Ref, useImperativeHandle, useContext } from 'react'; +import { cls, componentWrapper } from '@arco-design/mobile-utils'; +import { GlobalContext } from '../context-provider'; +import { SkeletonProps, SkeletonRef } from './type'; +import { + SkeletonAvatar as Avatar, + SkeletonGrid as Grid, + SkeletonNode as Node, + SkeletonParagraph as Paragraph, + SkeletonTitle as Title, +} from './elements'; +import { SkeletonContext } from './skeleton-context'; + +function getComponentProps(prop?: T | boolean): T | {} { + if (prop && typeof prop === 'object') { + return prop; + } + return {}; +} + +const Skeleton = forwardRef((props: SkeletonProps, ref: Ref) => { + const { + className = '', + style, + children, + title = true, + paragraph = true, + avatar = false, + grid, + showAnimation = true, + animation = 'gradient', + animationGradientColor, + backgroundColor, + } = props; + const domRef = useRef(null); + const { prefixCls } = useContext(GlobalContext); + + useImperativeHandle(ref, () => ({ + dom: domRef.current, + })); + + const isGrid = !!grid; + const hasTitle = !!title; + const hasParagraph = !!paragraph; + const hasAvatar = !!avatar; + const content = isGrid ? ( + + ) : ( + <> + {hasAvatar && } + {(hasTitle || hasParagraph) && ( +
+ {hasTitle && } + {hasParagraph && <Paragraph {...getComponentProps(paragraph)} />} + </div> + )} + </> + ); + + return ( + <div + className={cls( + `${prefixCls}-skeleton`, + { + [`${prefixCls}-skeleton-with-avatar`]: hasAvatar, + }, + className, + )} + style={{ color: animationGradientColor, ...style }} + ref={domRef} + > + <SkeletonContext.Provider value={{ showAnimation, animation, backgroundColor }}> + {content} + {children} + </SkeletonContext.Provider> + </div> + ); +}); + +/** + * 在内容加载过程中展示一组占位图形。 + * @en Display a set of placeholder graphics during content loading + * @type 信息展示 + * @type_en Data Display + * @name 骨架屏 + * @name_en Skeleton + */ +export default componentWrapper(Skeleton, { + Node, + Title, + Paragraph, + Avatar, + Grid, +}); diff --git a/packages/arcodesign/components/skeleton/skeleton-context.tsx b/packages/arcodesign/components/skeleton/skeleton-context.tsx new file mode 100644 index 00000000..81cb0107 --- /dev/null +++ b/packages/arcodesign/components/skeleton/skeleton-context.tsx @@ -0,0 +1,7 @@ +import React from 'react'; +import { SkeletonContextParams, SkeletonProps } from './type'; + +export const SkeletonContext = React.createContext<SkeletonContextParams>({ + showAnimation: true, + animation: 'gradient' as SkeletonProps['animation'], +}); diff --git a/packages/arcodesign/components/skeleton/style/index.less b/packages/arcodesign/components/skeleton/style/index.less new file mode 100644 index 00000000..1a6788fc --- /dev/null +++ b/packages/arcodesign/components/skeleton/style/index.less @@ -0,0 +1,147 @@ +@import '../../../style/mixin.less'; + +.@{prefix}-skeleton { + position: relative; + .use-var(color, skeleton-gradient-animation-color); +} + +.@{prefix}-skeleton-title { + .use-var(height, skeleton-title-height); +} + +.@{prefix}-skeleton-paragraph { + &-line { + width: 100%; + .use-var(height, skeleton-paragraph-height); + } + + &-line + &-line { + .use-var(margin-top, skeleton-paragraph-margin-top); + } +} + +.@{prefix}-skeleton-avatar.@{prefix}-skeleton-item { + .use-var(width, skeleton-avatar-size); + .use-var(height, skeleton-avatar-size); + border-radius: 100%; + transform: translateZ(0); +} + +.@{prefix}-skeleton-content { + width: 100%; + + .@{prefix}-skeleton-title + .@{prefix}-skeleton-paragraph { + .use-var(margin-top, skeleton-large-gutter); + } +} + +.@{prefix}-skeleton-with-avatar { + display: flex; + align-items: flex-start; + + .@{prefix}-skeleton-avatar { + flex: none; + } + + .@{prefix}-skeleton-content { + .@{prefix}-skeleton-title { + .use-var(margin-top, skeleton-medium-gutter); + } + } + + .@{prefix}-skeleton-avatar + .@{prefix}-skeleton-content { + .use-var-with-rtl(margin-left, skeleton-medium-gutter); + } +} + +.@{prefix}-skeleton-grid { + display: flex; + justify-content: space-between; + width: 100%; + + &-item { + display: flex; + flex-direction: column; + align-items: center; + } + &-icon { + .use-var(width, skeleton-grid-icon-size); + .use-var(height, skeleton-grid-icon-size); + } + &-text { + .use-var(margin-top, skeleton-medium-gutter); + .use-var(width, skeleton-grid-text-width); + .use-var(height, skeleton-grid-text-height); + } +} + +.@{prefix}-skeleton-node { + display: inline-block; +} + +.@{prefix}-skeleton-item { + position: relative; + overflow: hidden; + .use-var(border-radius, skeleton-border-radius); + .use-var(background-color, skeleton-background-color); +} + +.@{prefix}-skeleton-animation-item { + position: absolute; + width: 100vw; + height: 100%; + top: 0; + left: 0; + background-image: linear-gradient( + 90deg, + rgba(255, 255, 255, 0) 35%, + currentColor 50%, + rgba(255, 255, 255, 0) 65% + ); + transform-origin: top left; + animation-name: skeleton-gradient; + animation-iteration-count: infinite; + .use-var(animation-timing-function, skeleton-gradient-animation-timing-function); + .use-var(animation-duration, skeleton-gradient-animation-duration); + [dir="rtl"] & { + animation-name: skeleton-gradient-reverse; + } +} +.@{prefix}-skeleton-animation-breath { + animation: skeleton-breath linear infinite; + .use-var(animation-duration, skeleton-breath-animation-duration); +} + +@keyframes skeleton-gradient { + 0% { + transform: translateX(-65%) skewX(-45deg); + } + + 100% { + transform: translateX(135%) skewX(-45deg); + } +} + +@keyframes skeleton-gradient-reverse { + 0% { + transform: translateX(65%) skewX(45deg); + } + + 100% { + transform: translateX(-135%) skewX(45deg); + } +} + +@keyframes skeleton-breath { + 0% { + opacity: 1; + } + + 50% { + .use-var(opacity, skeleton-breath-opacity); + } + + 100% { + opacity: 1; + } +} diff --git a/packages/arcodesign/components/skeleton/style/index.ts b/packages/arcodesign/components/skeleton/style/index.ts new file mode 100644 index 00000000..d33b50ec --- /dev/null +++ b/packages/arcodesign/components/skeleton/style/index.ts @@ -0,0 +1,3 @@ +import '../../../style/public.less'; +import '../../avatar/style'; +import './index.less'; diff --git a/packages/arcodesign/components/skeleton/type.ts b/packages/arcodesign/components/skeleton/type.ts new file mode 100644 index 00000000..ad6bad5b --- /dev/null +++ b/packages/arcodesign/components/skeleton/type.ts @@ -0,0 +1,109 @@ +import { BaseProps, SimpleBaseProps } from '../_helpers'; + +export interface SkeletonProps extends SimpleBaseProps { + /** + * 是否显示标题占位图 + * @en Show title placeholder + * @default true + */ + title?: boolean | SkeletonTitleProps; + /** + * 是否显示段落占位图 + * @en Show paragraph placeholder + * @default true + */ + paragraph?: boolean | SkeletonParagraphProps; + /** + * 是否显示头像占位图 + * @en Show Avatar placeholder + * @default false + */ + avatar?: boolean | SkeletonAvatarProps; + /** + * 是否显示金刚位占位图(如该参数非空时,默认展示四列金刚位,且不展示标题/段落/头像占位符) + * @en Show Grid placeholder. When it's value is present, the paragraph, avatar or title placeholder will not be displayed, and four columns will be displayed by default + * @default false + */ + grid?: boolean | SkeletonGridProps; + /** + * 是否展示动画效果 + * @en Show loading effect + * @default true + */ + showAnimation?: boolean; + /** + * 加载动画效果,可选“扫光”、“呼吸”两种效果 + * @en Animation of loading effect, 'gradient' and 'breath' effects are optional + * @default "gradient" + */ + animation?: 'gradient' | 'breath'; + /** + * 扫光动效高光颜色 + * @en Highlight color of gradient animation + * @default "rgba(0, 0, 0, 0.04)" + */ + animationGradientColor?: string; + /** + * 占位块背景色 + * @en Background color of skeleton item + * @default "#F7F8FA" + */ + backgroundColor?: string; + /** + * 子元素 + * @en Children element + * @default null + */ + children?: React.ReactNode; +} + +export interface SkeletonTitleProps extends SimpleBaseProps { + /** + * 标题占位图宽度 + * @en The width of title + * @default "40%" + */ + width?: number | string; +} + +export interface SkeletonParagraphProps extends SimpleBaseProps { + /** + * 段落占位图的行数 + * @en Number of lines for paragraph + * @default 3 + */ + rows?: number; + /** + * 段落占位图宽度,若为数组格式对应每行宽度,否则表示最后一行的宽度 + * @en The width of paragraph. If width is an Array, it corresponds to the width of each line, otherwise it indicates the width of the last line + * @default "60%" + */ + width?: number | string | Array<number | string>; +} + +export interface SkeletonAvatarProps extends SimpleBaseProps {} + +export interface SkeletonGridProps extends SimpleBaseProps { + /** + * 金刚位列数 + * @en columns of grid + * @default 4 + */ + columns?: number; +} + +export interface SkeletonNodeProps extends BaseProps {} + +export interface SkeletonRef { + /** + * 最外层 DOM 元素 + * @en The outer DOM element of the component + */ + dom: HTMLDivElement | null; +} + +export interface SkeletonContextParams { + backgroundColor?: string; + showAnimation: boolean; + animation: SkeletonProps['animation']; +} diff --git a/packages/arcodesign/components/slider/popover.tsx b/packages/arcodesign/components/slider/popover.tsx index 2c566e5b..07c79594 100644 --- a/packages/arcodesign/components/slider/popover.tsx +++ b/packages/arcodesign/components/slider/popover.tsx @@ -1,4 +1,4 @@ -import React, { useContext, ReactNode } from 'react'; +import React, { useContext, ReactNode, useRef } from 'react'; import Transition from '../transition'; import { GlobalContext } from '../context-provider'; @@ -12,11 +12,22 @@ export function Popover({ children: ReactNode; }) { const { prefixCls = '' } = useContext(GlobalContext); + const domRef = useRef<HTMLDivElement | null>(null); return ( <div className={`${prefixCls}-slider-popover-wrapper`}> - <Transition in={visible} timeout={300} type="fade" mountOnEnter> - <div className={`${prefixCls}-slider-popover${content ? '' : ' no-content'}`}> + <Transition + in={visible} + timeout={300} + type="fade" + mountOnEnter + unmountOnExit + nodeRef={domRef} + > + <div + className={`${prefixCls}-slider-popover${content ? '' : ' no-content'}`} + ref={domRef} + > <div className={`${prefixCls}-slider-popover-content`}>{content}</div> <div className={`${prefixCls}-slider-popover-arrow`} /> </div> diff --git a/packages/arcodesign/components/stepper/demo/default.md b/packages/arcodesign/components/stepper/demo/default.md index 59ccd4e3..b09bbc50 100644 --- a/packages/arcodesign/components/stepper/demo/default.md +++ b/packages/arcodesign/components/stepper/demo/default.md @@ -15,7 +15,7 @@ export default function StepperDemo() { <Stepper defaultValue={1} min={-1} max={2} step={1} digits={1}/> </Cell> <Cell label="禁用状态"> - <Stepper disable/> + <Stepper disabled/> </Cell> </Cell.Group> ); diff --git a/packages/arcodesign/components/stepper/hooks/useButtonClick.tsx b/packages/arcodesign/components/stepper/hooks/useButtonClick.tsx index 625c71f2..a9d97a81 100644 --- a/packages/arcodesign/components/stepper/hooks/useButtonClick.tsx +++ b/packages/arcodesign/components/stepper/hooks/useButtonClick.tsx @@ -31,12 +31,10 @@ export default function useButtonClick( onAddButtonClick, onMinusButtonClick, } = params; - const [minusButtonDisable, setMinusButtonDisable] = useState( - () => actualInputValue === min || disabled, - ); - const [addButtonDisable, setAddButtonDisable] = useState( - () => actualInputValue === max || disabled, - ); + const minusDisable = actualInputValue <= min || disabled; + const [minusButtonDisable, setMinusButtonDisable] = useState(minusDisable); + const addDisable = actualInputValue >= max || disabled; + const [addButtonDisable, setAddButtonDisable] = useState(addDisable); const handleMinusButtonClick = (e: React.MouseEvent) => { if (minusButtonDisable) { @@ -63,9 +61,9 @@ export default function useButtonClick( // 当前值改变时,更新按钮状态 // Changes button status when value changed useEffect(() => { - setMinusButtonDisable(actualInputValue <= min); - setAddButtonDisable(actualInputValue >= max); - }, [actualInputValue]); + setMinusButtonDisable(minusDisable); + setAddButtonDisable(addDisable); + }, [minusDisable, addDisable]); return { minusButtonDisable, diff --git a/packages/arcodesign/components/sticky/README.en-US.md b/packages/arcodesign/components/sticky/README.en-US.md index 4ddf0679..20379aeb 100644 --- a/packages/arcodesign/components/sticky/README.en-US.md +++ b/packages/arcodesign/components/sticky/README.en-US.md @@ -33,6 +33,7 @@ Sticky layout component, The sticky-to-top effect of the element relative to the |----------|-------------|------| |dom|The outermost element DOM|HTMLDivElement| |recalculatePosition|In the local scrolling mode, if there is nested scrolling outside the container, this method can be actively called to make the sticky element actively update the fixed position|() =\> void| +|updatePlaceholderLayout|Manually update the height of the placeholder|() =\> void| > StickyEventPayload diff --git a/packages/arcodesign/components/sticky/README.md b/packages/arcodesign/components/sticky/README.md index e4f42590..48d88e6e 100644 --- a/packages/arcodesign/components/sticky/README.md +++ b/packages/arcodesign/components/sticky/README.md @@ -33,6 +33,7 @@ |----------|-------------|------| |dom|最外层元素 DOM|HTMLDivElement| |recalculatePosition|局部滚动模式下,如果容器外部还有嵌套滚动,可主动调用此方法,让 sticky 的元素主动更新 fixed 位置|() =\> void| +|updatePlaceholderLayout|手动更新占位模块的高度|() =\> void| > StickyEventPayload diff --git a/packages/arcodesign/components/sticky/__ast__/index.ast.json b/packages/arcodesign/components/sticky/__ast__/index.ast.json index ca103a61..9a8baa18 100644 --- a/packages/arcodesign/components/sticky/__ast__/index.ast.json +++ b/packages/arcodesign/components/sticky/__ast__/index.ast.json @@ -423,6 +423,19 @@ "en": "In the local scrolling mode, if there is nested scrolling outside the container, this method can be actively called to make the sticky element actively update the fixed position" }, "descWithTags": "局部滚动模式下,如果容器外部还有嵌套滚动,可主动调用此方法,让 sticky 的元素主动更新 fixed 位置" + }, + "updatePlaceholderLayout": { + "name": "updatePlaceholderLayout", + "required": true, + "description": "手动更新占位模块的高度\n@en Manually update the height of the placeholder", + "defaultValue": null, + "type": { + "name": "() => void" + }, + "tags": { + "en": "Manually update the height of the placeholder" + }, + "descWithTags": "手动更新占位模块的高度" } } }, diff --git a/packages/arcodesign/components/sticky/index.tsx b/packages/arcodesign/components/sticky/index.tsx index a55ba1c4..861171fe 100644 --- a/packages/arcodesign/components/sticky/index.tsx +++ b/packages/arcodesign/components/sticky/index.tsx @@ -31,6 +31,11 @@ export interface StickyRef { * @en In the local scrolling mode, if there is nested scrolling outside the container, this method can be actively called to make the sticky element actively update the fixed position */ recalculatePosition: () => void; + /** + * 手动更新占位模块的高度 + * @en Manually update the height of the placeholder + */ + updatePlaceholderLayout: () => void; } export interface StickyEventPayload { @@ -300,6 +305,23 @@ const Sticky = forwardRef((props: StickyProps, ref: Ref<StickyRef>) => { framePendingRef.current = true; }, [containerEventHandler]); + const updatePlaceholderLayoutInner = useCallback(() => { + if (placeholderRef.current) { + const contentHeight = contentCalculateHeightRef.current; + // 当元素吸顶时,默认有一个占位的元素占住该元素的位置,避免布局产生抖动 + // @en When an element is sticky to the top, a placeholder element occupies the element's position by default to avoid jitter in the layout + placeholderRef.current.style.height = `${isStickyRef.current ? contentHeight : 0}px`; + } + }, []); + + const updatePlaceholderLayout = useCallback(() => { + if (contentRef.current) { + const contentClientRect = contentRef.current.getBoundingClientRect(); + contentCalculateHeightRef.current = contentClientRect.height; + } + updatePlaceholderLayoutInner(); + }, []); + useEffect(() => { const containerEle = getActualContainer(getContainer) as HTMLElement; @@ -326,13 +348,7 @@ const Sticky = forwardRef((props: StickyProps, ref: Ref<StickyRef>) => { }, [getContainer, getScrollContainer, recalculatePosition]); useEffect(() => { - if (placeholderRef.current) { - // 当元素吸顶时,默认有一个占位的元素占住该元素的位置,避免布局产生抖动 - // @en When an element is sticky to the top, a placeholder element occupies the element's position by default to avoid jitter in the layout - placeholderRef.current.style.height = `${ - isStickyRef.current ? contentCalculateHeightRef.current : 0 - }px`; - } + updatePlaceholderLayoutInner(); }, [isSticky, wasSticky]); useImperativeHandle( @@ -340,8 +356,9 @@ const Sticky = forwardRef((props: StickyProps, ref: Ref<StickyRef>) => { () => ({ dom: contentRef.current, recalculatePosition, + updatePlaceholderLayout, }), - [recalculatePosition], + [recalculatePosition, updatePlaceholderLayout], ); const computedStyle = useMemo( diff --git a/packages/arcodesign/components/style.ts b/packages/arcodesign/components/style.ts index b486c534..bdb044de 100644 --- a/packages/arcodesign/components/style.ts +++ b/packages/arcodesign/components/style.ts @@ -1,35 +1,32 @@ import '../style/public.less'; -import './button/style'; +import './tabs/style'; +import './sticky/style'; +import './load-more/style'; +import './cell/style'; import './action-sheet/style'; import './avatar/style'; import './badge/style'; +import './button/style'; import './carousel/style'; -import './cell/style'; import './checkbox/style'; import './circle-progress/style'; import './collapse/style'; import './context-provider/style'; import './count-down/style'; import './date-picker/style'; -import './divider/style'; import './dialog/style'; +import './divider/style'; import './dropdown/style'; import './dropdown-menu/style'; import './ellipsis/style'; import './form/style'; -import './input/style'; -import './textarea/style'; -import './radio/style'; -import './image-picker/style'; -import './rate/style'; -import './slider/style'; import './grid/style'; import './image/style'; -import './show-monitor/style'; +import './image-picker/style'; import './image-preview/style'; import './index-bar/style'; +import './input/style'; import './keyboard/style'; -import './load-more/style'; import './loading/style'; import './masking/style'; import './nav-bar/style'; @@ -44,15 +41,19 @@ import './popup-swiper/style'; import './portal/style'; import './progress/style'; import './pull-refresh/style'; +import './radio/style'; +import './rate/style'; import './search-bar/style'; +import './skeleton/style'; +import './show-monitor/style'; +import './slider/style'; import './stepper/style'; import './steps/style'; -import './sticky/style'; import './swipe-action/style'; import './swipe-load/style'; import './switch/style'; import './tab-bar/style'; -import './tabs/style'; import './tag/style'; +import './textarea/style'; import './toast/style'; import './transition/style'; diff --git a/packages/arcodesign/components/switch/index.tsx b/packages/arcodesign/components/switch/index.tsx index f38de812..60517fa2 100644 --- a/packages/arcodesign/components/switch/index.tsx +++ b/packages/arcodesign/components/switch/index.tsx @@ -1,6 +1,14 @@ -import React, { useState, useEffect, forwardRef, Ref, useImperativeHandle, useRef } from 'react'; +import React, { + useState, + useEffect, + forwardRef, + Ref, + useImperativeHandle, + useRef, + useContext, +} from 'react'; import { cls, componentWrapper } from '@arco-design/mobile-utils'; -import { ContextLayout } from '../context-provider'; +import { ContextLayout, GlobalContext } from '../context-provider'; import { useSystem } from '../_helpers'; interface SwitchText { @@ -98,6 +106,7 @@ export interface SwitchRef { } const Switch = forwardRef((props: SwitchProps, ref: Ref<SwitchRef>) => { + const { useRtl } = useContext(GlobalContext); const system = useSystem(); const { className, @@ -153,15 +162,17 @@ const Switch = forwardRef((props: SwitchProps, ref: Ref<SwitchRef>) => { } const touchEndX = e.changedTouches && e.changedTouches[0] ? e.changedTouches[0].clientX : 0; const distance = touchEndX - touchStartX; + const swipeRight = useRtl ? distance < 0 : distance > 0; + const swipeLeft = useRtl ? distance > 0 : distance < 0; let newChecked = false; // 右滑打开 // @en Swipe right to open - if (distance > 0) { + if (swipeRight) { newChecked = true; // 左滑关闭 // @en Swipe left to close - } else if (distance < 0) { + } else if (swipeLeft) { newChecked = false; // 点击时取反 // @en Invert on clicking diff --git a/packages/arcodesign/components/switch/style/index.less b/packages/arcodesign/components/switch/style/index.less index a4e62cdf..54bc4d15 100644 --- a/packages/arcodesign/components/switch/style/index.less +++ b/packages/arcodesign/components/switch/style/index.less @@ -33,17 +33,23 @@ &.checked { .use-var(background-color, switch-android-checked-background); .@{prefix}-switch-text { - right: auto; - .use-var(left, switch-android-text-gap-size); + .set-prop-with-rtl(right, auto); + .use-var-with-rtl(left, switch-android-text-gap-size); } .@{prefix}-switch-inner { .use-var(transform, switch-android-checked-inner-transform); + [dir="rtl"] & { + .use-var(transform, switch-android-checked-inner-transform, "rotate(180deg)"); + } } } .@{prefix}-switch-inner { .use-var(width, switch-android-inner-diameter-size); .use-var(height, switch-android-inner-diameter-size); .use-var(box-shadow, switch-android-inner-box-shadow); + [dir="rtl"] & { + transform: rotate(180deg); + } } &.disabled { @@ -58,7 +64,7 @@ } .@{prefix}-switch-text { .use-var(font-size, switch-android-text-font-size); - .use-var(right, switch-android-text-gap-size); + .use-var-with-rtl(right, switch-android-text-gap-size); } } @@ -80,12 +86,15 @@ &.checked { .use-var(background-color, switch-ios-checked-background); .@{prefix}-switch-text { - right: auto; - .use-var(left, switch-ios-text-gap-size); + .set-prop-with-rtl(right, auto); + .use-var-with-rtl(left, switch-ios-text-gap-size); } .@{prefix}-switch-inner { .use-var(transform, switch-ios-checked-inner-transform); .use-var(box-shadow, switch-ios-inner-box-shadow); + [dir="rtl"] & { + .use-var(transform, switch-ios-checked-inner-transform, "rotate(180deg)"); + } } } @@ -94,12 +103,15 @@ } .@{prefix}-switch-text { .use-var(font-size, switch-ios-text-font-size); - .use-var(right, switch-ios-text-gap-size); + .use-var-with-rtl(right, switch-ios-text-gap-size); } .@{prefix}-switch-inner { .use-var(width, switch-ios-inner-diameter-size); .use-var(height, switch-ios-inner-diameter-size); .onepx-border-var(all, switch-ios-inner-border-color, 50%); + [dir="rtl"] & { + transform: rotate(180deg); + } } &.disabled { @@ -127,7 +139,7 @@ &-inner { position: relative; - left: 0; + .set-prop-with-rtl(left, 0); top: 0; display: flex; align-items: center; diff --git a/packages/arcodesign/components/tabs/FAQ.md b/packages/arcodesign/components/tabs/FAQ.md index 2d7a1fdb..9dfdfd4c 100644 --- a/packages/arcodesign/components/tabs/FAQ.md +++ b/packages/arcodesign/components/tabs/FAQ.md @@ -6,4 +6,4 @@ onAfterChange 的调用是发生在动画执行后,很多 state 的更新在 ## tabs 怎么配合 sticky 组件实现一个复杂的交互页面 -可以参考 [sticky-tabs](/#/components/sticky-tabs) 复合组件使用 +可以参考 [sticky-tabs](#/composite-components/sticky-tabs) 复合组件使用 diff --git a/packages/arcodesign/components/tabs/demo/style/mobile.less b/packages/arcodesign/components/tabs/demo/style/mobile.less index dd2cd57d..4af7084d 100644 --- a/packages/arcodesign/components/tabs/demo/style/mobile.less +++ b/packages/arcodesign/components/tabs/demo/style/mobile.less @@ -24,7 +24,7 @@ .demo-tabs-add-extra { position: absolute; - right: 0; + .set-prop-with-rtl(right, 0); top: 0; background: linear-gradient(270deg, #fff 66.04%, rgba(255, 255, 255, 0) 105%); .rem(width, 64); @@ -36,6 +36,10 @@ align-items: center; justify-content: flex-end; font-weight: bold; + [dir="rtl"] & { + justify-content: flex-start; + transform: rotate(180deg); + } } .demo-tab-custom-bar { diff --git a/packages/arcodesign/components/tabs/index.tsx b/packages/arcodesign/components/tabs/index.tsx index e90b9860..a9f2c8fb 100644 --- a/packages/arcodesign/components/tabs/index.tsx +++ b/packages/arcodesign/components/tabs/index.tsx @@ -7,9 +7,10 @@ import React, { useState, useEffect, useCallback, + useContext, } from 'react'; -import { cls, nextTick } from '@arco-design/mobile-utils'; -import { ContextLayout } from '../context-provider'; +import { cls, nextTick, getOffset } from '@arco-design/mobile-utils'; +import { ContextLayout, GlobalContext } from '../context-provider'; import TabCell from './tab-cell'; import TabPane from './tab-pane'; import { @@ -110,6 +111,7 @@ const Tabs = forwardRef((props: TabsProps, ref: Ref<TabsRef>) => { tabBarStopPropagation = true, swipeEnergySaving = false, } = props; + const { useRtl } = useContext(GlobalContext); const domRef = useRef<HTMLDivElement | null>(null); const cellRef = useRef<TabCellRef | null>(null); const paneRef = useRef<TabPaneRef | null>(null); @@ -142,6 +144,8 @@ const Tabs = forwardRef((props: TabsProps, ref: Ref<TabsRef>) => { swipeable && tabDirection === 'vertical' && tabs.length > 1; + const horizontalUseRtl = tabDirection === 'vertical' && useRtl; + const rtlRatio = horizontalUseRtl ? -1 : 1; useImperativeHandle(ref, () => ({ dom: domRef.current, @@ -264,6 +268,7 @@ const Tabs = forwardRef((props: TabsProps, ref: Ref<TabsRef>) => { const posDisY = touchMoveY - touchStartYRef.current; const absDisX = Math.abs(posDisX); const absDisY = Math.abs(posDisY); + const comparedDis = posDisX * rtlRatio; if (scrollingRef.current === null) { scrollingRef.current = absDisX < absDisY; } @@ -278,12 +283,12 @@ const Tabs = forwardRef((props: TabsProps, ref: Ref<TabsRef>) => { return; } if ( - (activeIndexRef.current === 0 && posDisX > 0) || - (activeIndexRef.current === tabs.length - 1 && posDisX < 0) + (activeIndexRef.current === 0 && comparedDis > 0) || + (activeIndexRef.current === tabs.length - 1 && comparedDis < 0) ) { if (!touchStoppedRef.current && absDisX > stopTouchThreshold) { touchStoppedRef.current = true; - onTouchStopped && onTouchStopped(posDisX >= 0 ? -1 : 1); + onTouchStopped && onTouchStopped(comparedDis >= 0 ? -1 : 1); } setDistance(0); return; @@ -339,9 +344,10 @@ const Tabs = forwardRef((props: TabsProps, ref: Ref<TabsRef>) => { (Math.abs(dis) > maxSlice && Math.abs(dis) > distanceToChangeTab) || Math.abs(speed) > speedToChangeTab; let newIndex = index; - if (dis > 0 && needJump) { + const comparedDis = dis * rtlRatio; + if (comparedDis > 0 && needJump) { newIndex = index - 1; - } else if (dis < 0 && needJump) { + } else if (comparedDis < 0 && needJump) { newIndex = index + 1; } nextTick(() => { @@ -363,9 +369,10 @@ const Tabs = forwardRef((props: TabsProps, ref: Ref<TabsRef>) => { } function updateLayout() { + const { width, height } = getOffset(domRef.current); cellRef.current && cellRef.current.resetUnderlineStyle(); - setWrapWidth(domRef.current ? domRef.current.offsetWidth : 0); - setWrapHeight(domRef.current ? domRef.current.offsetHeight : 0); + setWrapWidth(width || domRef.current?.offsetWidth || 0); + setWrapHeight(height || domRef.current?.offsetHeight || 0); paneRef.current && paneRef.current.setCurrentHeight(); } @@ -483,6 +490,7 @@ const Tabs = forwardRef((props: TabsProps, ref: Ref<TabsRef>) => { autoHeight={autoHeight} onScroll={onScroll} swipeEnergySaving={swipeEnergySaving} + rtlRatio={rtlRatio} {...commonProps} /> </div> diff --git a/packages/arcodesign/components/tabs/style/index.less b/packages/arcodesign/components/tabs/style/index.less index f964f72b..440c68d5 100644 --- a/packages/arcodesign/components/tabs/style/index.less +++ b/packages/arcodesign/components/tabs/style/index.less @@ -310,12 +310,8 @@ &:not(.custom) { - &.line { - .use-var(margin-right, tabs-tab-bar-line-gutter); - - &.last { - margin-right: 0; - } + &.line:not(.last) { + .use-var-with-rtl(margin-right, tabs-tab-bar-line-gutter); } &.line, @@ -337,17 +333,17 @@ } &:not(:last-child) { - .use-var(border-right, tabs-tab-bar-card-color, 1PX solid); + .use-var-with-rtl(border-right, tabs-tab-bar-card-color, 1PX solid); } &:first-of-type { - .use-var(border-top-left-radius, tabs-tab-bar-card-border-radius); - .use-var(border-bottom-left-radius, tabs-tab-bar-card-border-radius); + .use-var-with-rtl(border-top-left-radius, tabs-tab-bar-card-border-radius); + .use-var-with-rtl(border-bottom-left-radius, tabs-tab-bar-card-border-radius); } &:last-of-type { - .use-var(border-top-right-radius, tabs-tab-bar-card-border-radius); - .use-var(border-bottom-right-radius, tabs-tab-bar-card-border-radius); + .use-var-with-rtl(border-top-right-radius, tabs-tab-bar-card-border-radius); + .use-var-with-rtl(border-bottom-right-radius, tabs-tab-bar-card-border-radius); } } @@ -361,14 +357,13 @@ &.tag, &.tag-divide { - .use-var(margin-right, tabs-tab-bar-tag-gutter); .use-var(padding, tabs-tab-bar-tag-padding); .rem(border-radius, 100); .use-var(background, tabs-tab-bar-tag-background); .use-var(color, tabs-tab-bar-tag-text-color); - &.last { - margin-right: 0; + &:not(.last) { + .use-var-with-rtl(margin-right, tabs-tab-bar-tag-gutter); } &.active { diff --git a/packages/arcodesign/components/tabs/tab-cell-underline.tsx b/packages/arcodesign/components/tabs/tab-cell-underline.tsx index c448b3c0..9ca31fe5 100644 --- a/packages/arcodesign/components/tabs/tab-cell-underline.tsx +++ b/packages/arcodesign/components/tabs/tab-cell-underline.tsx @@ -2,6 +2,7 @@ import React, { CSSProperties, forwardRef, Ref, + useContext, useEffect, useImperativeHandle, useMemo, @@ -11,6 +12,7 @@ import React, { import { addCssKeyframes, cls, nextTick, removeCssStyleDom } from '@arco-design/mobile-utils'; import { TabCellUnderlineProps, TabCellUnderlineRef, UnderlineStyle } from './type'; import { getStyleWithVendor, useRefState, useSystem } from '../_helpers'; +import { GlobalContext } from '../context-provider'; const TabCellUnderline = forwardRef( (props: TabCellUnderlineProps, ref: Ref<TabCellUnderlineRef>) => { @@ -37,6 +39,7 @@ const TabCellUnderline = forwardRef( getTabRect, getTabCenterLeft, } = props; + const { useRtl } = useContext(GlobalContext); const [underlineStyle, setUnderlineStyle] = useState<UnderlineStyle>({}); const [caterpillar, caterpillarRef, setCaterpillar] = useRefState(false); const [caterpillarDelay, setCaterpillarDelay] = useState(0); @@ -46,6 +49,7 @@ const TabCellUnderline = forwardRef( const isVertical = tabDirection === 'vertical'; const maxScaleWithDefault = caterpillarMaxScale || 2; const translateZStr = translateZ ? ' translateZ(0)' : ''; + const rtlRatio = isVertical && useRtl ? -1 : 1; useImperativeHandle( ref, @@ -159,10 +163,11 @@ const TabCellUnderline = forwardRef( } function getDescIndex() { - if (distance > 0) { + const comparedDis = distance * rtlRatio; + if (comparedDis > 0) { return activeIndex - 1; } - if (distance < 0) { + if (comparedDis < 0) { return activeIndex + 1; } return activeIndex; @@ -182,7 +187,8 @@ const TabCellUnderline = forwardRef( const currentLeft = getLineLeft(activeIndex); const descIndex = getDescIndex(); const descLeft = getLineLeft(descIndex); - const moveRatio = wrapWidth ? distance / wrapWidth : 0; + const comparedDis = distance * rtlRatio; + const moveRatio = wrapWidth ? comparedDis / wrapWidth : 0; const leftOffset = moveRatio * (currentLeft - descLeft); const direc = isVertical ? 'X' : 'Y'; const transStyle: CSSProperties = @@ -206,7 +212,7 @@ const TabCellUnderline = forwardRef( const widthOffset = moveRatio * (currentWidth - descWidth); adaptiveStyle = { [isVertical ? 'width' : 'height']: - distance > 0 ? currentWidth - widthOffset : currentWidth + widthOffset, + comparedDis > 0 ? currentWidth - widthOffset : currentWidth + widthOffset, willChange: 'width', }; adaptiveOuterStyle = isVertical ? { width: 'auto' } : { height: 'auto' }; @@ -223,7 +229,7 @@ const TabCellUnderline = forwardRef( return { outer: { transform: `translate${direc}(${ - distance > 0 ? currentLeft - leftOffset : currentLeft + leftOffset + comparedDis > 0 ? currentLeft - leftOffset : currentLeft + leftOffset }px)${translateZStr}`, ...outerSize, ...adaptiveOuterStyle, diff --git a/packages/arcodesign/components/tabs/tab-cell.tsx b/packages/arcodesign/components/tabs/tab-cell.tsx index f9ad1da9..e8052c4b 100644 --- a/packages/arcodesign/components/tabs/tab-cell.tsx +++ b/packages/arcodesign/components/tabs/tab-cell.tsx @@ -6,11 +6,13 @@ import React, { useState, useEffect, CSSProperties, + useContext, } from 'react'; import { cls, scrollWithAnimation, nextTick } from '@arco-design/mobile-utils'; import { TabData, TabCellProps, TabCellRef, TabCellUnderlineRef, OffsetRect } from './type'; import { useSystem } from '../_helpers'; import TabCellUnderline from './tab-cell-underline'; +import { GlobalContext } from '../context-provider'; const TabCell = forwardRef((props: TabCellProps, ref: Ref<TabCellRef>) => { const { @@ -62,6 +64,7 @@ const TabCell = forwardRef((props: TabCellProps, ref: Ref<TabCellRef>) => { tabBarStopPropagation, } = props; const prefix = `${prefixCls}-tab-cell`; + const { useRtl } = useContext(GlobalContext); const domRef = useRef<HTMLDivElement | null>(null); const underlineRef = useRef<TabCellUnderlineRef>(null); const allCellRectRef = useRef<OffsetRect[]>([]); @@ -252,14 +255,16 @@ const TabCell = forwardRef((props: TabCellProps, ref: Ref<TabCellRef>) => { if (!isVertical) { return {}; } + const marginStart = useRtl ? 'marginRight' : 'marginLeft'; + const marginEnd = useRtl ? 'marginLeft' : 'marginRight'; if (index === 0) { return { - marginRight: cellGutter, - marginLeft: getCellPadding('left'), + [marginEnd]: cellGutter, + [marginStart]: getCellPadding('left'), }; } return { - marginRight: index === tabs.length - 1 ? void 0 : cellGutter, + [marginEnd]: index === tabs.length - 1 ? void 0 : cellGutter, }; } diff --git a/packages/arcodesign/components/tabs/tab-pane.tsx b/packages/arcodesign/components/tabs/tab-pane.tsx index 6befab76..5369213b 100644 --- a/packages/arcodesign/components/tabs/tab-pane.tsx +++ b/packages/arcodesign/components/tabs/tab-pane.tsx @@ -45,6 +45,7 @@ const TabPane = forwardRef((props: TabPaneProps, ref: Ref<TabPaneRef>) => { swipeEnergySaving, changeIndex, onScroll, + rtlRatio, } = props; const domRef = useRef<HTMLDivElement | null>(null); const panesRef = useRef<(HTMLDivElement | null)[]>([]); @@ -224,7 +225,7 @@ const TabPane = forwardRef((props: TabPaneProps, ref: Ref<TabPaneRef>) => { ? { width: `${100 * panes.length}%`, transform: `translateX(${ - distance - wrapWidth * activeIndex + distance - wrapWidth * activeIndex * rtlRatio }px)${translateStr}`, } : { @@ -263,7 +264,7 @@ const TabPane = forwardRef((props: TabPaneProps, ref: Ref<TabPaneRef>) => { tabDirection === 'vertical' ? { transform: `translateX(${ - distance - wrapWidth * (activeIndex - index) + distance - wrapWidth * (activeIndex - index) * rtlRatio }px)${translateStr}`, } : { diff --git a/packages/arcodesign/components/tabs/type.ts b/packages/arcodesign/components/tabs/type.ts index 9c86f124..a801f50d 100644 --- a/packages/arcodesign/components/tabs/type.ts +++ b/packages/arcodesign/components/tabs/type.ts @@ -654,6 +654,7 @@ export interface TabPaneProps paneTrans: boolean; swipeable: boolean; changeIndex: (newIndex: number, from?: string) => void; + rtlRatio: number; } export interface TabPaneRef { diff --git a/packages/arcodesign/components/toast/index.tsx b/packages/arcodesign/components/toast/index.tsx index 45bb0324..e02922b1 100644 --- a/packages/arcodesign/components/toast/index.tsx +++ b/packages/arcodesign/components/toast/index.tsx @@ -147,6 +147,7 @@ const Toast = forwardRef((props: ToastProps, ref: Ref<ToastRef>) => { } = props; const closeTimerRef = useRef<number>(); const domRef = useRef<HTMLDivElement | null>(null); + const wrapDomRef = useRef<HTMLDivElement | null>(null); const isInitialMount = useRef(false); const hasType = type && type !== 'info'; @@ -214,6 +215,7 @@ const Toast = forwardRef((props: ToastProps, ref: Ref<ToastRef>) => { 'no-event': !disableBodyTouch, })} onClick={handleClickMask} + ref={wrapDomRef} > <div className={cls(`${prefixClass}-inner`, layout, { @@ -258,7 +260,12 @@ const Toast = forwardRef((props: ToastProps, ref: Ref<ToastRef>) => { className={cls(`${prefixCls}-toast`, 'all-border-box', className)} ref={domRef} > - <Transition in={visible} timeout={transitionDuration} type="fade"> + <Transition + in={visible} + timeout={transitionDuration} + type="fade" + nodeRef={wrapDomRef} + > {renderComponent(`${prefixCls}-toast`)} </Transition> </div> diff --git a/packages/arcodesign/components/transition/demo/index.md b/packages/arcodesign/components/transition/demo/index.md index fab332b5..e43f2ba1 100644 --- a/packages/arcodesign/components/transition/demo/index.md +++ b/packages/arcodesign/components/transition/demo/index.md @@ -7,6 +7,7 @@ import { Transition, Cell } from '@arco-design/mobile-react'; export default function TransitionDemo() { const [visible, setVisible] = React.useState(false); + const domRef = React.useRef(); return (<> <Cell.Group bordered={false}> <Cell label="Open custom mask" showArrow onClick={() => setVisible(true)} /> @@ -17,10 +18,12 @@ export default function TransitionDemo() { type="fade" mountOnEnter={true} unmountOnExit={true} + nodeRef={domRef} > <div className="demo-transition-mask" onClick={() => setVisible(false)} + ref={domRef} ></div> </Transition> </>); diff --git a/packages/arcodesign/package-lock.json b/packages/arcodesign/package-lock.json index 274c5e7b..00df8547 100644 --- a/packages/arcodesign/package-lock.json +++ b/packages/arcodesign/package-lock.json @@ -1,12 +1,12 @@ { "name": "@arco-design/mobile-react", - "version": "2.27.4", + "version": "2.28.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@arco-design/mobile-react", - "version": "2.25.3", + "version": "2.27.4", "license": "ISC", "dependencies": { "@arco-design/transformable": "^1.0.0", diff --git a/packages/arcodesign/package.json b/packages/arcodesign/package.json index dfbf6444..a2f90246 100644 --- a/packages/arcodesign/package.json +++ b/packages/arcodesign/package.json @@ -1,6 +1,6 @@ { "name": "@arco-design/mobile-react", - "version": "2.27.4", + "version": "2.28.2", "description": "", "main": "cjs/index.js", "module": "esm/index.js", @@ -15,7 +15,7 @@ "author": "taoyiyue@bytedance.com", "license": "ISC", "dependencies": { - "@arco-design/mobile-utils": "2.15.4", + "@arco-design/mobile-utils": "2.16.2", "@arco-design/transformable": "^1.0.0", "lodash.throttle": "^4.1.1", "resize-observer-polyfill": "^1.5.1" @@ -28,12 +28,26 @@ "jest": "^25.3.0" }, "peerDependencies": { + "@types/react": ">=16.9.0", + "@types/react-dom": ">=16.9.0", + "@types/react-transition-group": ">=4.3.0", "react": ">=16.9.0", "react-dom": ">=16.9.0", "react-transition-group": ">=4.3.0" }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "@types/react-transition-group": { + "optional": true + } + }, "publishConfig": { "access": "public" }, - "gitHead": "1ae5c477f0f78e51fd883214b25923a4dcfea8bb" + "gitHead": "f923a5d573d8af95e6b158614f8e7d8184afc481" } diff --git a/packages/arcodesign/tokens/app/arcodesign/default/css-variables.less b/packages/arcodesign/tokens/app/arcodesign/default/css-variables.less index 9f16beae..4b1ea3e3 100644 --- a/packages/arcodesign/tokens/app/arcodesign/default/css-variables.less +++ b/packages/arcodesign/tokens/app/arcodesign/default/css-variables.less @@ -793,4 +793,20 @@ --divider-right-width: ~`pxtorem(28)`; --divider-content-padding: ~`pxtorem(12)`; --divider-padding: ~`pxtorem(16)`; + --skeleton-border-radius: ~`pxtorem(0)`; + --skeleton-background-color: #F2F3F5; + --skeleton-gradient-animation-color: rgba(255, 255, 255, 0.6); + --skeleton-breath-opacity: 0.4; + --skeleton-gradient-animation-timing-function: cubic-bezier(0.42, 0, 0.58, 1); + --skeleton-gradient-animation-duration: 1.5s; + --skeleton-breath-animation-duration: 1.5s; + --skeleton-title-height: ~`pxtorem(16)`; + --skeleton-paragraph-height: ~`pxtorem(16)`; + --skeleton-paragraph-margin-top: ~`pxtorem(12)`; + --skeleton-avatar-size: ~`pxtorem(32)`; + --skeleton-grid-icon-size: ~`pxtorem(32)`; + --skeleton-grid-text-width: ~`pxtorem(64)`; + --skeleton-grid-text-height: ~`pxtorem(16)`; + --skeleton-medium-gutter: ~`pxtorem(8)`; + --skeleton-large-gutter: ~`pxtorem(20)`; } diff --git a/packages/arcodesign/tokens/app/arcodesign/default/index.d.ts b/packages/arcodesign/tokens/app/arcodesign/default/index.d.ts index 69b0c649..5f111f56 100644 --- a/packages/arcodesign/tokens/app/arcodesign/default/index.d.ts +++ b/packages/arcodesign/tokens/app/arcodesign/default/index.d.ts @@ -792,6 +792,22 @@ export interface ArcodesignToken extends Record<string, string> { 'divider-right-width': string; 'divider-content-padding': string; 'divider-padding': string; + 'skeleton-border-radius': string; + 'skeleton-background-color': string; + 'skeleton-gradient-animation-color': string; + 'skeleton-breath-opacity': string; + 'skeleton-gradient-animation-timing-function': string; + 'skeleton-gradient-animation-duration': string; + 'skeleton-breath-animation-duration': string; + 'skeleton-title-height': string; + 'skeleton-paragraph-height': string; + 'skeleton-paragraph-margin-top': string; + 'skeleton-avatar-size': string; + 'skeleton-grid-icon-size': string; + 'skeleton-grid-text-width': string; + 'skeleton-grid-text-height': string; + 'skeleton-medium-gutter': string; + 'skeleton-large-gutter': string; } declare const tokens: ArcodesignToken; export default tokens; \ No newline at end of file diff --git a/packages/arcodesign/tokens/app/arcodesign/default/index.js b/packages/arcodesign/tokens/app/arcodesign/default/index.js index f48a7f69..bdbb81a6 100644 --- a/packages/arcodesign/tokens/app/arcodesign/default/index.js +++ b/packages/arcodesign/tokens/app/arcodesign/default/index.js @@ -803,7 +803,23 @@ var tokens = { "divider-left-width": "0.56rem", "divider-right-width": "0.56rem", "divider-content-padding": "0.24rem", - "divider-padding": "0.32rem" + "divider-padding": "0.32rem", + "skeleton-border-radius": "0", + "skeleton-background-color": "#F2F3F5", + "skeleton-gradient-animation-color": "rgba(255, 255, 255, 0.6)", + "skeleton-breath-opacity": "0.4", + "skeleton-gradient-animation-timing-function": "cubic-bezier(0.42, 0, 0.58, 1)", + "skeleton-gradient-animation-duration": "1.5s", + "skeleton-breath-animation-duration": "1.5s", + "skeleton-title-height": "0.32rem", + "skeleton-paragraph-height": "0.32rem", + "skeleton-paragraph-margin-top": "0.24rem", + "skeleton-avatar-size": "0.64rem", + "skeleton-grid-icon-size": "0.64rem", + "skeleton-grid-text-width": "1.28rem", + "skeleton-grid-text-height": "0.32rem", + "skeleton-medium-gutter": "0.16rem", + "skeleton-large-gutter": "0.4rem" }; var _default = tokens; exports["default"] = _default; \ No newline at end of file diff --git a/packages/arcodesign/tokens/app/arcodesign/default/index.json b/packages/arcodesign/tokens/app/arcodesign/default/index.json index 43a012d4..a1f18333 100644 --- a/packages/arcodesign/tokens/app/arcodesign/default/index.json +++ b/packages/arcodesign/tokens/app/arcodesign/default/index.json @@ -6629,6 +6629,196 @@ "en": "Size of the rounded corners of the square SearchBar" } }, + "skeletonAvatarSize": { + "cssKey": "skeleton-avatar-size", + "desc": "骨架屏头像大小", + "override": "", + "value": "~`pxtorem(32)`", + "jsValue": "@getRem@32", + "staticValue": "0.64rem", + "localeDesc": { + "ch": "骨架屏头像大小", + "en": "Skeleton avatar size" + } + }, + "skeletonBackgroundColor": { + "cssKey": "skeleton-background-color", + "desc": "骨架屏元素背景色", + "override": "", + "value": "#F2F3F5", + "jsValue": "#F2F3F5", + "staticValue": "#F2F3F5", + "localeDesc": { + "ch": "骨架屏元素背景色", + "en": "Skeleton element background color" + } + }, + "skeletonBorderRadius": { + "cssKey": "skeleton-border-radius", + "desc": "骨架屏元素圆角", + "override": "", + "value": "~`pxtorem(0)`", + "jsValue": "@getRem@0", + "staticValue": "0", + "localeDesc": { + "ch": "骨架屏元素圆角", + "en": "Skeleton element border radius" + } + }, + "skeletonBreathAnimationDuration": { + "cssKey": "skeleton-breath-animation-duration", + "desc": "骨架屏呼吸动效时间", + "override": "", + "value": "1.5s", + "jsValue": "1.5s", + "staticValue": "1.5s", + "localeDesc": { + "ch": "骨架屏呼吸动效时间", + "en": "Skeleton element breath animation duration" + } + }, + "skeletonBreathOpacity": { + "cssKey": "skeleton-breath-opacity", + "desc": "骨架屏呼吸动效透明度", + "override": "", + "value": "0.4", + "jsValue": "0.4", + "staticValue": "0.4", + "localeDesc": { + "ch": "骨架屏呼吸动效透明度", + "en": "Skeleton element breath animation opacity" + } + }, + "skeletonGradientAnimationColor": { + "cssKey": "skeleton-gradient-animation-color", + "desc": "骨架屏扫光动效高亮色", + "override": "", + "value": "rgba(255, 255, 255, 0.6)", + "jsValue": "rgba(255, 255, 255, 0.6)", + "staticValue": "rgba(255, 255, 255, 0.6)", + "localeDesc": { + "ch": "骨架屏扫光动效高亮色", + "en": "Skeleton element gradient animation highlight color" + } + }, + "skeletonGradientAnimationDuration": { + "cssKey": "skeleton-gradient-animation-duration", + "desc": "骨架屏扫光动效时间", + "override": "", + "value": "1.5s", + "jsValue": "1.5s", + "staticValue": "1.5s", + "localeDesc": { + "ch": "骨架屏扫光动效时间", + "en": "Skeleton element gradient animation duration" + } + }, + "skeletonGradientAnimationTimingFunction": { + "cssKey": "skeleton-gradient-animation-timing-function", + "desc": "骨架屏扫光动效曲线", + "override": "", + "value": "cubic-bezier(0.42, 0, 0.58, 1)", + "jsValue": "cubic-bezier(0.42, 0, 0.58, 1)", + "staticValue": "cubic-bezier(0.42, 0, 0.58, 1)", + "localeDesc": { + "ch": "骨架屏扫光动效曲线", + "en": "Skeleton element gradient animation timing function" + } + }, + "skeletonGridIconSize": { + "cssKey": "skeleton-grid-icon-size", + "desc": "骨架屏金刚位图标区宽度", + "override": "", + "value": "~`pxtorem(32)`", + "jsValue": "@getRem@32", + "staticValue": "0.64rem", + "localeDesc": { + "ch": "骨架屏金刚位图标区宽度", + "en": "Skeleton grid item icon width" + } + }, + "skeletonGridTextHeight": { + "cssKey": "skeleton-grid-text-height", + "desc": "骨架屏金刚位文字区高度", + "override": "", + "value": "~`pxtorem(16)`", + "jsValue": "@getRem@16", + "staticValue": "0.32rem", + "localeDesc": { + "ch": "骨架屏金刚位文字区高度", + "en": "Skeleton grid item text height" + } + }, + "skeletonGridTextWidth": { + "cssKey": "skeleton-grid-text-width", + "desc": "骨架屏金刚位文字区宽度", + "override": "", + "value": "~`pxtorem(64)`", + "jsValue": "@getRem@64", + "staticValue": "1.28rem", + "localeDesc": { + "ch": "骨架屏金刚位文字区宽度", + "en": "Skeleton grid item text width" + } + }, + "skeletonLargeGutter": { + "cssKey": "skeleton-large-gutter", + "desc": "骨架屏元素外边距,大尺寸", + "override": "", + "value": "~`pxtorem(20)`", + "jsValue": "@getRem@20", + "staticValue": "0.4rem", + "localeDesc": { + "ch": "骨架屏元素外边距,大尺寸" + } + }, + "skeletonMediumGutter": { + "cssKey": "skeleton-medium-gutter", + "desc": "骨架屏元素外边距,中尺寸", + "override": "", + "value": "~`pxtorem(8)`", + "jsValue": "@getRem@8", + "staticValue": "0.16rem", + "localeDesc": { + "ch": "骨架屏元素外边距,中尺寸" + } + }, + "skeletonParagraphHeight": { + "cssKey": "skeleton-paragraph-height", + "desc": "骨架屏段落行高度", + "override": "", + "value": "~`pxtorem(16)`", + "jsValue": "@getRem@16", + "staticValue": "0.32rem", + "localeDesc": { + "ch": "骨架屏段落行高度", + "en": "Skeleton paragraph line height" + } + }, + "skeletonParagraphMarginTop": { + "cssKey": "skeleton-paragraph-margin-top", + "desc": "骨架屏各段落行间距", + "override": "", + "value": "~`pxtorem(12)`", + "jsValue": "@getRem@12", + "staticValue": "0.24rem", + "localeDesc": { + "ch": "骨架屏各段落行间距", + "en": "Margin top between skeleton paragraph lines" + } + }, + "skeletonTitleHeight": { + "cssKey": "skeleton-title-height", + "desc": "骨架屏标题高度", + "override": "", + "value": "~`pxtorem(16)`", + "jsValue": "@getRem@16", + "staticValue": "0.32rem", + "localeDesc": { + "ch": "骨架屏标题高度", + "en": "Skeleton title height" + } + }, "sliderHasMarkPaddingBottom": { "cssKey": "slider-has-mark-padding-bottom", "desc": "slider 有标记时的底部内边距", diff --git a/packages/arcodesign/tokens/app/arcodesign/default/index.less b/packages/arcodesign/tokens/app/arcodesign/default/index.less index 9c3860b9..dc2ab27c 100644 --- a/packages/arcodesign/tokens/app/arcodesign/default/index.less +++ b/packages/arcodesign/tokens/app/arcodesign/default/index.less @@ -792,4 +792,20 @@ @divider-right-width: ~`pxtorem(28)`; @divider-content-padding: ~`pxtorem(12)`; @divider-padding: ~`pxtorem(16)`; +@skeleton-border-radius: ~`pxtorem(0)`; +@skeleton-background-color: #F2F3F5; +@skeleton-gradient-animation-color: rgba(255, 255, 255, 0.6); +@skeleton-breath-opacity: 0.4; +@skeleton-gradient-animation-timing-function: cubic-bezier(0.42, 0, 0.58, 1); +@skeleton-gradient-animation-duration: 1.5s; +@skeleton-breath-animation-duration: 1.5s; +@skeleton-title-height: ~`pxtorem(16)`; +@skeleton-paragraph-height: ~`pxtorem(16)`; +@skeleton-paragraph-margin-top: ~`pxtorem(12)`; +@skeleton-avatar-size: ~`pxtorem(32)`; +@skeleton-grid-icon-size: ~`pxtorem(32)`; +@skeleton-grid-text-width: ~`pxtorem(64)`; +@skeleton-grid-text-height: ~`pxtorem(16)`; +@skeleton-medium-gutter: ~`pxtorem(8)`; +@skeleton-large-gutter: ~`pxtorem(20)`; diff --git a/packages/arcodesign/tokens/src/arcodesign/default/index.js b/packages/arcodesign/tokens/src/arcodesign/default/index.js index 1fcd953f..1a4d7d2d 100644 --- a/packages/arcodesign/tokens/src/arcodesign/default/index.js +++ b/packages/arcodesign/tokens/src/arcodesign/default/index.js @@ -4018,6 +4018,84 @@ function getCompTokens() { * @en Top and Bottom padding of divider */ dividerPadding: getRem(16), + /** + * 骨架屏元素圆角 + * @en Skeleton element border radius + */ + skeletonBorderRadius: getRem(0), + /** + * 骨架屏元素背景色 + * @en Skeleton element background color + */ + skeletonBackgroundColor: '#F2F3F5', + /** + * 骨架屏扫光动效高亮色 + * @en Skeleton element gradient animation highlight color + */ + skeletonGradientAnimationColor: 'rgba(255, 255, 255, 0.6)', + /** + * 骨架屏呼吸动效透明度 + * @en Skeleton element breath animation opacity + */ + skeletonBreathOpacity: '0.4', + /** + * 骨架屏扫光动效曲线 + * @en Skeleton element gradient animation timing function + */ + skeletonGradientAnimationTimingFunction: 'cubic-bezier(0.42, 0, 0.58, 1)', + /** + * 骨架屏扫光动效时间 + * @en Skeleton element gradient animation duration + */ + skeletonGradientAnimationDuration: '1.5s', + /** + * 骨架屏呼吸动效时间 + * @en Skeleton element breath animation duration + */ + skeletonBreathAnimationDuration: '1.5s', + /** + * 骨架屏标题高度 + * @en Skeleton title height + */ + skeletonTitleHeight: getRem(16), + /** + * 骨架屏段落行高度 + * @en Skeleton paragraph line height + */ + skeletonParagraphHeight: getRem(16), + /** + * 骨架屏各段落行间距 + * @en Margin top between skeleton paragraph lines + */ + skeletonParagraphMarginTop: getRem(12), + /** + * 骨架屏头像大小 + * @en Skeleton avatar size + */ + skeletonAvatarSize: getRem(32), + /** + * 骨架屏金刚位图标区宽度 + * @en Skeleton grid item icon width + */ + skeletonGridIconSize: getRem(32), + /** + * 骨架屏金刚位文字区宽度 + * @en Skeleton grid item text width + */ + skeletonGridTextWidth: getRem(64), + /** + * 骨架屏金刚位文字区高度 + * @en Skeleton grid item text height + */ + skeletonGridTextHeight: getRem(16), + /** + * 骨架屏元素外边距,中尺寸 + */ + skeletonMediumGutter: getRem(8), + /** + * 骨架屏元素外边距,大尺寸 + */ + skeletonLargeGutter: getRem(20), }; } diff --git a/packages/common-widgets/CHANGELOG.md b/packages/common-widgets/CHANGELOG.md index 10860c5e..3ef250b3 100644 --- a/packages/common-widgets/CHANGELOG.md +++ b/packages/common-widgets/CHANGELOG.md @@ -3,6 +3,56 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [2.16.2](https://github.com/arco-design/arco-design-mobile/compare/@arco-design/mobile-utils@2.16.1...@arco-design/mobile-utils@2.16.2) (2023-08-07) + + +### Bug Fixes + +* `Form` bind this in form-item ([#148](https://github.com/arco-design/arco-design-mobile/issues/148)) ([5616d53](https://github.com/arco-design/arco-design-mobile/commit/5616d537b921b009df61addccf966c5e9363a0cb)) + + + + + +## [2.16.1](https://github.com/arco-design/arco-design-mobile/compare/@arco-design/mobile-utils@2.16.0...@arco-design/mobile-utils@2.16.1) (2023-08-02) + + +### Bug Fixes + +* add [@types](https://github.com/types) to peerDependencies ([42f3d5a](https://github.com/arco-design/arco-design-mobile/commit/42f3d5ab19144702d7c371c6cbd1aa031a690abe)) + + + + + +# [2.16.0](https://github.com/arco-design/arco-design-mobile/compare/@arco-design/mobile-utils@2.15.5...@arco-design/mobile-utils@2.16.0) (2023-07-14) + + +### Bug Fixes + +* `Collapse` update with the latest opened ([#140](https://github.com/arco-design/arco-design-mobile/issues/140)) ([b963787](https://github.com/arco-design/arco-design-mobile/commit/b96378761557f4d90f09f789f662e9d3588c3cbd)) +* `ImagePreview` fix scroll through ([ea3f9bc](https://github.com/arco-design/arco-design-mobile/commit/ea3f9bc5d0980f70c81e2de99084e0a11187b3c1)) + + +### Features + +* RTL support for `Badge`, `Button`, `Cell`, `Checkbox`, `Form`, `Radio`, `Rate`, `Switch` and `Tabs` ([#135](https://github.com/arco-design/arco-design-mobile/issues/135)) ([97de976](https://github.com/arco-design/arco-design-mobile/commit/97de976ba514ec0f48103bd4f0c535ebceb8981a)) + + + + + +## [2.15.5](https://github.com/arco-design/arco-design-mobile/compare/@arco-design/mobile-utils@2.15.4...@arco-design/mobile-utils@2.15.5) (2023-07-04) + + +### Bug Fixes + +* error caught when using "getComputedStyle" ([#129](https://github.com/arco-design/arco-design-mobile/issues/129)) ([daa8f67](https://github.com/arco-design/arco-design-mobile/commit/daa8f67961d9d2751a14c0c3f7759b54fe0579cb)) + + + + + ## [2.15.4](https://github.com/arco-design/arco-design-mobile/compare/@arco-design/mobile-utils@2.15.3...@arco-design/mobile-utils@2.15.4) (2023-05-19) **Note:** Version bump only for package @arco-design/mobile-utils diff --git a/packages/common-widgets/package-lock.json b/packages/common-widgets/package-lock.json index 4764a776..c476511d 100644 --- a/packages/common-widgets/package-lock.json +++ b/packages/common-widgets/package-lock.json @@ -1,12 +1,12 @@ { "name": "@arco-design/mobile-utils", - "version": "2.15.4", + "version": "2.16.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@arco-design/mobile-utils", - "version": "2.15.4", + "version": "2.16.0", "license": "ISC", "dependencies": { "es6-promise": "^4.2.8" diff --git a/packages/common-widgets/package.json b/packages/common-widgets/package.json index 849d17a5..b6b2167a 100644 --- a/packages/common-widgets/package.json +++ b/packages/common-widgets/package.json @@ -1,6 +1,6 @@ { "name": "@arco-design/mobile-utils", - "version": "2.15.4", + "version": "2.16.2", "description": "", "main": "cjs/index.js", "module": "esm/index.js", @@ -13,7 +13,7 @@ "publishConfig": { "access": "public" }, - "gitHead": "1ae5c477f0f78e51fd883214b25923a4dcfea8bb", + "gitHead": "f923a5d573d8af95e6b158614f8e7d8184afc481", "dependencies": { "es6-promise": "^4.2.8" } diff --git a/packages/common-widgets/style/mixin.less b/packages/common-widgets/style/mixin.less index b36cee72..64559abc 100644 --- a/packages/common-widgets/style/mixin.less +++ b/packages/common-widgets/style/mixin.less @@ -258,6 +258,12 @@ @margin-right: margin-left; @padding-left: padding-right; @padding-right: padding-left; + @border-right: border-left; + @border-left: border-right; + @border-top-left-radius: border-top-right-radius; + @border-bottom-left-radius: border-bottom-right-radius; + @border-top-right-radius: border-top-left-radius; + @border-bottom-right-radius: border-bottom-left-radius; } @property-name: @property-map[@@origin-property]; } @@ -331,7 +337,6 @@ .process-title-color-with-config { color: @color; } - .finish-tail-color-with-config::before, .finish-tail-color-with-config::after, .process-tail-color-with-config::before, diff --git a/packages/common-widgets/utils/dom.ts b/packages/common-widgets/utils/dom.ts index b9a2079c..a2f5afc2 100644 --- a/packages/common-widgets/utils/dom.ts +++ b/packages/common-widgets/utils/dom.ts @@ -43,7 +43,7 @@ export function freeEleScroll( * @param parentEl 父节点 * @param childrenEl 子节点 */ -export const isContains = (parentEl: HTMLElement | null, childrenEl: HTMLElement | null) => { +export function isContains(parentEl: HTMLElement | null, childrenEl: HTMLElement | null) { if (!parentEl || !childrenEl) return false; if (parentEl.contains) { return parentEl.contains(childrenEl); @@ -56,7 +56,7 @@ export const isContains = (parentEl: HTMLElement | null, childrenEl: HTMLElement parent = parent.parentNode as HTMLElement; } return false; -}; +} export function execRAF(fn) { try { @@ -74,7 +74,7 @@ export function scrollWithAnimation( bezier: [number, number, number, number] = [0.34, 0.69, 0.1, 1], type: 'by' | 'to' = 'to', ) { - const targetTop = Math.max(0, type === 'by' ? initTop + target : target); + const targetTop = type === 'by' ? initTop + target : target; const start = Date.now(); const fn = () => { const p = (Date.now() - start) / duration; @@ -429,3 +429,11 @@ export function convertCssDuration(ele: HTMLElement, property: string) { } return 0; } + +export function safeGetComputedStyle(element: HTMLElement) { + try { + return window.getComputedStyle(element); + } catch (e) { + return {} as CSSStyleDeclaration; + } +} diff --git a/packages/common-widgets/utils/is.ts b/packages/common-widgets/utils/is.ts index e547d6ea..38787365 100644 --- a/packages/common-widgets/utils/is.ts +++ b/packages/common-widgets/utils/is.ts @@ -34,7 +34,7 @@ export function isEmptyArray(obj: Array<unknown>): boolean { return isArray(obj) && !obj?.length; } -export const isDeepEqual = (obj: any, sub: any): boolean => { +export function isDeepEqual(obj: any, sub: any): boolean { if (typeof obj !== 'object' || typeof sub !== 'object' || obj === null || sub === null) { return obj === sub; } @@ -49,4 +49,4 @@ export const isDeepEqual = (obj: any, sub: any): boolean => { if (!isDeepEqual(obj[key], sub[key])) return false; } return true; -}; +} diff --git a/scripts/sites/plugins/DemoGeneratePlugin/generate-demo.js b/scripts/sites/plugins/DemoGeneratePlugin/generate-demo.js index 36a01ccb..5368907f 100644 --- a/scripts/sites/plugins/DemoGeneratePlugin/generate-demo.js +++ b/scripts/sites/plugins/DemoGeneratePlugin/generate-demo.js @@ -11,7 +11,7 @@ const compositeCompFolder = 'sites/mobile/pages/composite-comp'; const compositeFolder = 'sites/composite-comp'; const srcFolder = 'packages/arcodesign'; const packageName = '@arco-design/mobile-react'; -const compFolder = path.join(srcFolder, 'components'); +const compFolder = path.posix.join(srcFolder, 'components'); const compPath = path.join(rootPath, compFolder); const sitePath = path.join(rootPath, siteFolder); const compositeCompPath = path.join(rootPath, compositeCompFolder); @@ -25,7 +25,7 @@ function renderSource({ comp, demoName, depsCompSet, language, compileEnv, demoP const reg = new RegExp(packageName, 'g'); let order = 0; - + renderer.code = code => { const filename = `_${utils.getCompName(demoName)}`; const content = `import React from 'react'; @@ -165,9 +165,9 @@ function generateSiteDemo({ return new Promise((resolve) => { // 内部工具js不处理 if (/^_/.test(comp)) { - return resolve() + return resolve(); } - + depsCompSet.add(comp); const docPath = path.join(sitePath, comp); @@ -181,7 +181,7 @@ function generateSiteDemo({ } const demoSource = []; let importStr = `import React from 'react';\n`; - + demos.forEach(name => { if (name.indexOf('.md') < 0) { return resolve(); @@ -256,7 +256,7 @@ function generateSiteDemo({ }); Promise.all(promises).then(() => { - console.log(`>>> Generate ${language} demo files finished`); + console.log(`>>> Generate ${language} demo files finished`); }); if (!compileComps.length) { @@ -343,9 +343,9 @@ function generateSiteCompositeDemo({ demoCompSet.add(comp); } }); - + demoSource.sort((a, b) => a.order - b.order); - + const demoStylePath = path.join(demoPath, 'style'); if (fs.existsSync(demoStylePath)) { const styles = fs.readdirSync(demoStylePath); @@ -379,7 +379,7 @@ function generateSiteCompositeDemo({ }); Promise.all(promises).then(() => { - console.log(`>>> Generate ${language} composite demo files finished`) + console.log(`>>> Generate ${language} composite demo files finished`); }); [...demoCompSet].map(e => { @@ -389,7 +389,7 @@ function generateSiteCompositeDemo({ compDocsImportStr += `import ${importName} from './${e}${tsxFileSuffix ? `/index${tsxFileSuffix}` : ''}';\n`; compDocsStr += ` '${route}': ${importName},\n`; }); -const docEntryStr = `${compDocsImportStr} + const docEntryStr = `${compDocsImportStr} const docs = {\n${compDocsStr}}; export default docs; @@ -431,4 +431,4 @@ function generateDemo({ } } -module.exports = generateDemo; \ No newline at end of file +module.exports = generateDemo; diff --git a/sites/composite-comp/sticky-tabs/sticky-tab-css.md b/sites/composite-comp/sticky-tabs/sticky-tab-css.md new file mode 100644 index 00000000..33697291 --- /dev/null +++ b/sites/composite-comp/sticky-tabs/sticky-tab-css.md @@ -0,0 +1,57 @@ +## 使用 position: sticky 替换 Sticky @en{Replace Sticky with position: sticky} + +当使用 sticky tabs 复合组件时,安卓机型经常会出现 tabBar 闪动的情况,可以通过 css 实现 sticky 的方式去优化性能 +注意:sticky css 属性兼容性有待考证;arco 已验证安卓5.1.1 vivoX7 支持;iPhone6plus ios 11.2.5不支持 + +```js +import { Tabs, Sticky, Portal } from '@arco-design/mobile-react'; + +const tabData = [ + { title: 'Example 1' }, + { title: 'Example 2' }, + { title: 'Example 3' }, +]; + +export default function StickyTabsCss() { + const tabBarRef = React.useRef(null); + + const supportsCSS = (attribute, value) => { + if (window && window.CSS) { + if (typeof value === 'undefined') { + return window.CSS.supports(attribute); + } + return window.CSS.supports(attribute, value); + } + + const elem = document.createElement('div'); + if (attribute in elem.style) { + elem.style[attribute] = value; + return elem.style[attribute] === value; + } + return false; + }; + + return ( + <div id='sticky-tabs-wrapper-css'> + <div className='placeholder'> + placeholder placeholder placeholder placeholder placeholder placeholder placeholder placeholder placeholder placeholder placeholder placeholder + </div> + <div className="sticky-tabs-wrapper-css-nav-bar" ref={tabBarRef}/> + <Tabs + className='sticky-tabs' + tabs={tabData} + renderTabBar={(TabBar) => + supportsCSS('position', 'sticky') ? ( + <Portal getContainer={() => tabBarRef.current}>{TabBar}</Portal> + ) : ( + <Sticky getScrollContainer={() => document.getElementById('sticky-tabs-wrapper-css')} topOffset={0}>{TabBar}</Sticky> + )} + > + <div className="demo-tab-content"> content 1</div> + <div className="demo-tab-content"> content 2 </div> + <div className="demo-tab-content"> content 3 </div> + </Tabs> + </div> + ); +} +``` diff --git a/sites/composite-comp/sticky-tabs/style/mobile.less b/sites/composite-comp/sticky-tabs/style/mobile.less index 59abda11..a2248e51 100644 --- a/sites/composite-comp/sticky-tabs/style/mobile.less +++ b/sites/composite-comp/sticky-tabs/style/mobile.less @@ -1,7 +1,8 @@ @import "../../../../packages/arcodesign/style/mixin.less"; #sticky-tabs-wrapper-position, -#sticky-tabs-wrapper-hide { +#sticky-tabs-wrapper-hide, +#sticky-tabs-wrapper-css { height: 500px; overflow: scroll; @@ -17,3 +18,12 @@ } } +#sticky-tabs-wrapper-css { + overflow: visible; + + .sticky-tabs-wrapper-css-nav-bar { + position: sticky; + background: #fff; + top: 44px; + } +} diff --git a/sites/pc/static/md/qa.en-US.md b/sites/pc/static/md/qa.en-US.md index dc4fa424..aebcc1df 100644 --- a/sites/pc/static/md/qa.en-US.md +++ b/sites/pc/static/md/qa.en-US.md @@ -41,4 +41,12 @@ import { ContextProvider } from '@arco-design/mobile-react'; return ( <ContextProvider system="android"> <Tabs ... /> </ContextProvider>)" -``` \ No newline at end of file +``` + +## Q:When using methods such as Toast.toast to call components, the configuration passed to ContextProvider cannot be received? + +The component called by the method is not a subcomponent under the root node of the page, so the configuration of ContextProvider needs to be passed to the method, such as: `Toast.toast({ content: 'Tips' }, { prefixCls: 'aa' })`. (Supported after `2.24.0`) + +## Q: AutoFocus does not work when using Input/Textarea/SearchBar + +autoFocus is not supported on some models, the bottom layer of the component can only try focus, but whether it can focus or not depends on the model diff --git a/sites/pc/static/md/qa.md b/sites/pc/static/md/qa.md index 298ea3ad..8992d801 100644 --- a/sites/pc/static/md/qa.md +++ b/sites/pc/static/md/qa.md @@ -45,4 +45,8 @@ return ( <ContextProvider system="android"> ## Q:用 Toast.toast 等方法调用组件时,接不到传给 ContextProvider 的配置? -使用方法调用的组件不是页面根节点下的子组件,因此需将 ContextProvider 的配置传给方法,如:`Toast.toast({ content: 'Tips' }, { prefixCls: 'aa' })`。(`2.24.0`之后支持) \ No newline at end of file +使用方法调用的组件不是页面根节点下的子组件,因此需将 ContextProvider 的配置传给方法,如:`Toast.toast({ content: 'Tips' }, { prefixCls: 'aa' })`。(`2.24.0`之后支持) + +## Q:使用 Input/Textarea/SearchBar 的 autoFocus 不生效 + +autoFocus 在一些机型上是不支持的,组件底层只能尝试 focus,但是到底能不能聚焦还得看机型