Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@
"@rollup/plugin-node-resolve": "^13.0.4",
"@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-url": "^6.0.0",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.4",
"@testing-library/user-event": "^13.5.0",
"@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1",
"@types/lodash-es": "^4.17.12",
"@types/node": "^22.14.1",
"@types/react": "^17.0.2",
"@types/react-dom": "17.0.2",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.1",
"@types/react-transition-group": "^4.4.4",
"@types/rimraf": "^4.0.5",
"@types/tinycolor2": "^1.4.6",
Expand Down Expand Up @@ -138,8 +138,8 @@
"postcss": "^8.4.5",
"prettier": "^3.3.3",
"prismjs": "^1.25.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-router-dom": "^6.2.2",
"rimraf": "^6.0.1",
"rollup": "^2.55.0",
Expand All @@ -159,13 +159,13 @@
"vite": "^6.2.3",
"vite-plugin-pwa": "^1.0.0",
"vite-plugin-tdoc": "^2.0.4",
"vitest": "^2.0.3",
"vitest": "^2.1.9",
"workbox-precaching": "^6.3.0"
},
"dependencies": {
"@popperjs/core": "^2.11.8",
"@use-gesture/react": "^10.2.10",
"ahooks": "^3.1.9",
"ahooks": "^3.8.5",
"classnames": "^2.3.1",
"dayjs": "^1.11.13",
"hoist-non-react-statics": "^3.3.2",
Expand Down
2 changes: 1 addition & 1 deletion script/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import pkg from '../package.json';

const name = 'tdesign';
const externalDeps = Object.keys(pkg.dependencies || {});
const externalPeerDeps = Object.keys(pkg.peerDependencies || {});
const externalPeerDeps = Object.keys(pkg.peerDependencies || {}).concat(['react-dom/client']);
const banner = `/**
* ${name} v${pkg.version}
* (c) ${new Date().getFullYear()} ${pkg.author}
Expand Down
7 changes: 4 additions & 3 deletions site/mobile/main.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React from 'react';
import ReactDOM from 'react-dom';
import ReactDOM from 'react-dom/client';
import App from './App';
import '../style/mobile/index.less';

import '../../src/_common/style/mobile/_reset.less';
// import '../../src/_common/style/mobile/index.less';

ReactDOM.render(
const root = ReactDOM.createRoot(document.getElementById('app'));

root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('app'),
);
8 changes: 5 additions & 3 deletions site/web/main.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import ReactDOM from 'react-dom/client';

import { registerLocaleChange } from 'tdesign-site-components';
import 'tdesign-site-components/lib/styles/prism-theme-dark.less';
Expand All @@ -13,9 +13,11 @@ import App from './App';
import '../style/web/index.less';

registerLocaleChange();
ReactDOM.render(

const root = ReactDOM.createRoot(document.getElementById('app'));

root.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('app'),
);
2 changes: 1 addition & 1 deletion src/_util/forwardRefWithStatics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ export default function forwardRefWithStatics<P, T = any, S = {}>(
component: React.ForwardRefRenderFunction<T, P>,
statics?: S,
): React.FunctionComponent<P & RefAttributes<T>> & S {
return hoistNonReactStatics(forwardRef(component), statics as any) as any;
return hoistNonReactStatics(forwardRef(component as any), statics as any) as any;
}
99 changes: 99 additions & 0 deletions src/_util/react-render.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Implementation reference from: https://github.com/react-component/util/blob/master/src/React/render.ts
// @ts-ignore
import type * as React from 'react';
import * as ReactDOM from 'react-dom';
import { createRoot as createRootClient } from 'react-dom/client';
import type { Root } from 'react-dom/client';

// Let compiler not to search module usage
const fullClone = {
...ReactDOM,
} as typeof ReactDOM & {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?: {
usingClientEntryPoint?: boolean;
};
createRoot?: CreateRoot;
};

type CreateRoot = (container: ContainerType) => Root;

// @ts-ignore
const { version, render: reactRender, unmountComponentAtNode } = fullClone;

let createRoot: CreateRoot;
try {
const mainVersion = Number((version || '').split('.')[0]);
if (mainVersion >= 18 && mainVersion < 19) {
({ createRoot } = fullClone);
} else if (mainVersion >= 19) {
createRoot = createRootClient;
}
} catch (e) {
// Do nothing;
}

function toggleWarning(skip: boolean) {
const { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } = fullClone;

if (
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED &&
typeof __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED === 'object'
) {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.usingClientEntryPoint = skip;
}
}

const MARK = '__td_mobile_react_root__';

// ========================== Render ==========================
type ContainerType = (Element | DocumentFragment) & {
[MARK]?: Root;
};

function modernRender(node: React.ReactElement, container: ContainerType) {
toggleWarning(true);
const root = container[MARK] || createRoot(container);
toggleWarning(false);

root.render(node);

// eslint-disable-next-line
container[MARK] = root;
}

function legacyRender(node: React.ReactElement, container: ContainerType) {
reactRender(node, container);
}

export function render(node: React.ReactElement, container: ContainerType) {
if (createRoot) {
modernRender(node, container);
return;
}

legacyRender(node, container);
}

// ========================= Unmount ==========================
async function modernUnmount(container: ContainerType) {
// Delay to unmount to avoid React 18 sync warning
return Promise.resolve().then(() => {
container[MARK]?.unmount();

// eslint-disable-next-line
delete container[MARK];
});
}

function legacyUnmount(container: ContainerType) {
unmountComponentAtNode(container);
}

export async function unmount(container: ContainerType) {
if (createRoot !== undefined) {
// Delay to unmount to avoid React 18 sync warning
return modernUnmount(container);
}

legacyUnmount(container);
}
6 changes: 3 additions & 3 deletions src/_util/renderToBody.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import ReactDOM from 'react-dom';
import { ReactElement } from 'react';
import { render, unmount as unMount } from './react-render';

export function renderToBody(element: ReactElement) {
const container = document.createElement('div');
document.body.appendChild(container);
function unmount() {
const unmountResult = ReactDOM.unmountComponentAtNode(container);
const unmountResult = unMount(container);
if (unmountResult && container.parentNode) {
container.parentNode.removeChild(container);
}
}
ReactDOM.render(element, container);
render(element, container);
return unmount;
}

Expand Down
2 changes: 1 addition & 1 deletion src/_util/withNativeProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface NativeProps<S extends string = never> {
style?: CSSProperties & Partial<Record<S, string>>;
}

export default function withNativeProps<P extends NativeProps>(props: P, element: ReactElement) {
export default function withNativeProps<P extends NativeProps>(props: P, element: ReactElement<P>) {
const elementProps = element.props;
const nativeProps: NativeProps & Record<string, any> = {};
if (props.className) {
Expand Down
2 changes: 1 addition & 1 deletion src/_util/withStopPropagation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const eventToPropRecord: Record<PropagationEvent, string> = {
[PropagationEvent.SCROLL]: 'onScroll',
};

export function withStopPropagation(events: PropagationEvent[], element: ReactElement) {
export function withStopPropagation(events: PropagationEvent[], element: ReactElement<any>) {
const props: Record<string, any> = { ...element.props };
if (!events.length) return element;

Expand Down
4 changes: 2 additions & 2 deletions src/action-sheet/ActionSheetGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import cx from 'classnames';

import type { ActionSheetProps } from './ActionSheet';
import type { ActionSheetItem } from './type';

import type { TNode } from '../common';
import { Grid, GridItem } from '../grid';
import { Swiper, SwiperProps } from '../swiper';
import { usePrefixClass } from '../hooks/useClass';
Expand Down Expand Up @@ -57,7 +57,7 @@ export function ActionSheetGrid(props: ActionSheetGridProps) {
<Grid gutter={0} column={gridColumn} style={{ width: '100%' }}>
{item.map((it, idx2) => {
let label: string;
let image: React.ReactNode;
let image: TNode;
let badge: ActionSheetItem['badge'];
if (typeof it === 'string') {
label = it;
Expand Down
4 changes: 3 additions & 1 deletion src/avatar/AvatarGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export interface AvatarGroupProps extends TdAvatarGroupProps, StyledProps {
}

function getValidChildren(children: React.ReactNode) {
return React.Children.toArray(children).filter((child) => React.isValidElement(child)) as React.ReactElement[];
return React.Children.toArray(children).filter((child) =>
React.isValidElement(child),
) as React.ReactElement<AvatarGroupProps>[];
}

const AvatarGroup = (props: AvatarGroupProps) => {
Expand Down
21 changes: 17 additions & 4 deletions src/back-top/Backtop.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import React, { useRef, useEffect } from 'react';
import React, { useRef, useEffect, useMemo } from 'react';
import classNames from 'classnames';
import { useScroll, useMount, useBoolean } from 'ahooks';
import smoothscroll from 'smoothscroll-polyfill';
import { isString } from 'lodash-es';
import { Icon } from 'tdesign-icons-react';
import useDefaultProps from '../hooks/useDefaultProps';
import parseTNode from '../_util/parseTNode';
import withNativeProps, { NativeProps } from '../_util/withNativeProps';
import useConfig from '../hooks/useConfig';
import { TdBackTopProps } from './type';
import { backTopDefaultProps } from './defaultProps';

export type ThemeList = 'round' | 'half-round' | 'round-dark' | 'half-round-dark';

Expand All @@ -22,7 +25,10 @@ export const defaultProps = {
};

const BackTop: React.FC<BackTopProps> = (props) => {
const { fixed, icon, target, text, theme, onToTop, visibilityHeight, container } = props;
const { fixed, icon, target, text, theme, onToTop, visibilityHeight, container } = useDefaultProps(
props,
backTopDefaultProps,
);

const [show, { setTrue, setFalse }] = useBoolean(false);

Expand Down Expand Up @@ -53,6 +59,14 @@ const BackTop: React.FC<BackTopProps> = (props) => {

const targetHeight = isWindow(backTopDom.current) ? 0 : backTopDom.current?.offsetTop || 0;

const renderIcon = useMemo(() => {
if (isString(icon)) {
return <Icon className={`${name}__icon`} name={icon} />;
}

return parseTNode(icon);
}, [icon, name]);

useEffect(() => {
// 当滚动条滚动到 设置滚动高度时,显示回到顶部按钮
if (scroll?.top >= visibilityHeight) {
Expand All @@ -76,7 +90,7 @@ const BackTop: React.FC<BackTopProps> = (props) => {
style={{ zIndex: 99999, opacity: show ? 1 : 0 }}
onClick={onClick}
>
{isString(icon) ? <Icon className={`${name}__icon`} name={icon} /> : icon}
{renderIcon}
{text && (
<div className={classNames(`${name}__text--${theme}`)} style={{ width: 'auto', minWidth: 12, maxWidth: 24 }}>
{text}
Expand All @@ -86,7 +100,6 @@ const BackTop: React.FC<BackTopProps> = (props) => {
);
};

BackTop.defaultProps = defaultProps;
BackTop.displayName = 'BackTop';

export default BackTop;
2 changes: 1 addition & 1 deletion src/back-top/_example/base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BackTop, Button } from 'tdesign-mobile-react';
import './style/index.less';

export default function Base({ visible, onClose, container }) {
const [theme, setTheme] = useState({
const [theme, setTheme] = useState<{ theme: 'round' | 'half-round'; text: string }>({
theme: 'round',
text: '顶部',
});
Expand Down
18 changes: 8 additions & 10 deletions src/calendar/CalendarTemplate.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { useEffect, useState, useContext, useMemo, forwardRef } from 'react';
import { CloseIcon } from 'tdesign-icons-react';
import Button from '../button';
import parseTNode from '../_util/parseTNode';
import { Button, ButtonProps } from '../button';
import { TDateType, TCalendarValue } from './type';
import { usePrefixClass } from '../hooks/useClass';
import useDefaultProps from '../hooks/useDefaultProps';
Expand Down Expand Up @@ -229,15 +230,12 @@ const CalendarTemplate = forwardRef<HTMLDivElement, CalendarProps>((_props, ref)
};

const renderConfirmBtn = () => {
if (confirmBtn && typeof confirmBtn !== 'object') {
return confirmBtn;
}
if (confirmBtn && Array.isArray(confirmBtn)) {
return confirmBtn;
}
if (confirmBtn && typeof confirmBtn === 'object') {
return <Button block theme="primary" {...confirmBtn} onClick={handleConfirm} />;
if (!confirmBtn) return;

if (typeof confirmBtn === 'object') {
return <Button block theme="primary" {...(confirmBtn as ButtonProps)} onClick={handleConfirm} />;
}
return parseTNode(confirmBtn);
};

const className = useMemo(
Expand All @@ -248,7 +246,7 @@ const CalendarTemplate = forwardRef<HTMLDivElement, CalendarProps>((_props, ref)

return (
<div ref={ref} className={`${className}`}>
<div className={`${calendarClass}__title`}>{props.title || local.title}</div>
<div className={`${calendarClass}__title`}>{parseTNode(props.title, null, local.title)}</div>
{props.usePopup && <CloseIcon className={`${calendarClass}__close-btn`} size={24} onClick={handleClose} />}
<div className={`${calendarClass}__days`}>
{days.map((item, index) => (
Expand Down
Loading