Skip to content

Commit

Permalink
refactor(cell): 重构
Browse files Browse the repository at this point in the history
  • Loading branch information
dexterBo committed Aug 2, 2024
1 parent 002c70b commit ca639f9
Show file tree
Hide file tree
Showing 21 changed files with 2,034 additions and 123 deletions.
2 changes: 1 addition & 1 deletion site/mobile/mobile.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export default {
{
title: 'Cell 单元格',
name: 'cell',
component: () => import('tdesign-mobile-react/cell/_example/base.jsx'),
component: () => import('tdesign-mobile-react/cell/_example/base.tsx'),
},
{
title: 'Upload 上传',
Expand Down
52 changes: 52 additions & 0 deletions src/_util/useHover.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { useEffect, useRef } from 'react';

interface HoverOptions {
className: string;
disabled?: string | boolean;
}

function useHover(options: HoverOptions) {
const elRef = useRef(null);
const { className, disabled } = options;
const startTime = 50;
const stayTime = 70;

useEffect(() => {
let currentElement: HTMLElement | null = null;

const handleTouchStart = () => {
if (disabled) {
return;
}
setTimeout(() => {
currentElement?.classList.add(className);
}, startTime);
};

const handleTouchEnd = () => {
if (disabled) {
return;
}
setTimeout(() => {
currentElement?.classList.remove(className);
}, stayTime);
};

if (elRef.current) {
currentElement = elRef.current;
currentElement.addEventListener('touchstart', handleTouchStart, { capture: false, passive: true });
currentElement.addEventListener('touchend', handleTouchEnd, false);
}

return () => {
if (currentElement) {
currentElement.removeEventListener('touchstart', handleTouchStart);
currentElement.removeEventListener('touchend', handleTouchEnd);
}
};
}, [className, disabled]);

return elRef;
}

export default useHover;
27 changes: 17 additions & 10 deletions src/cell-group/CellGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React from 'react';
import classNames from 'classnames';
import React, { useMemo } from 'react';
import classnames from 'classnames';
import useConfig from '../_util/useConfig';
import { TdCellGroupProps } from './type';
import { TdCellGroupProps } from '../cell/type';
import withNativeProps, { NativeProps } from '../_util/withNativeProps';

export type CellGroupProps = TdCellGroupProps & NativeProps;
Expand All @@ -12,19 +12,26 @@ const defaultProps = {
};

const CellGroup: React.FC<CellGroupProps> = (props) => {
const { children, bordered, title } = props;
const { children, bordered, title, theme } = props;
const { classPrefix } = useConfig();
const name = `${classPrefix}-cell-group`;

const classNames = useMemo(
() => [
name,
`${name}--${theme}`,
{
[`${name}--bordered`]: bordered,
},
],
[name, bordered, theme],
);

return withNativeProps(
props,
<div
className={classNames(`${name}`, `${name}__container`, {
'border--top-bottom': bordered,
})}
>
<div>
{title && <div className={`${name}__title`}>{title}</div>}
<div className={`${name}-body`}>{children}</div>
<div className={classnames(classNames)}>{children}</div>
</div>,
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/cell-group/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import _CellGroup from './CellGroup';

import './style';

export * from './type';
// export * from './type';

export const CellGroup = _CellGroup;

Expand Down
2 changes: 1 addition & 1 deletion src/cell-group/style/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/* eslint-disable import/no-relative-packages */
import '../../_common/style/mobile/components/cell-group/_index.less';
import '../../_common/style/mobile/components/cell-group/v2/_index.less';
100 changes: 55 additions & 45 deletions src/cell/Cell.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useMemo, useCallback } from 'react';
import classNames from 'classnames';
import classnames from 'classnames';
import isString from 'lodash/isString';
import { ChevronRightIcon } from 'tdesign-icons-react';

import { TdCellProps } from './type';
import { cellDefaultProps } from './defaultProps';
import withNativeProps, { NativeProps } from '../_util/withNativeProps';
import useHover from '../_util/useHover';
import useConfig from '../_util/useConfig';

export interface CellProps extends TdCellProps, NativeProps {}
Expand All @@ -23,7 +24,6 @@ const Cell: React.FC<CellProps> = (props) => {
required,
rightIcon,
title,
url,
onClick,
children,
} = props;
Expand All @@ -32,20 +32,19 @@ const Cell: React.FC<CellProps> = (props) => {

const name = `${classPrefix}-cell`;

const renderIcon = useMemo(() => {
let content: React.ReactNode | null = null;
if (arrow) {
content = <ChevronRightIcon size={24} />;
} else if (rightIcon) {
content = rightIcon;
}

if (content === null) {
return content;
}
const classNames = useMemo(
() => [
`${name}`,
`${name}--${align}`,
{
[`${name}--borderless`]: !bordered,
},
],
[align, bordered, name],
);

return <div className={`${name}__right-icon`}>{content}</div>;
}, [arrow, rightIcon, name]);
const hoverDisabled = useMemo(() => !hover, [hover]);
const ref = useHover({ className: `${name}--hover`, disabled: hoverDisabled });

const handleClick = useCallback(
(e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
Expand All @@ -54,41 +53,52 @@ const Cell: React.FC<CellProps> = (props) => {
[onClick],
);

const content = (
<div
className={classNames([`${name}`, `${name}--${align}`], {
[`${name}--bordered`]: bordered,
[`${name}--hover`]: hover,
})}
onClick={handleClick}
>
{(leftIcon || image) && (
<div className={`${name}__left-icon`}>
{leftIcon}
{isString(image) ? <img src={image} className={`${name}__image`} /> : image}
</div>
)}
{title && (
<div className={`${name}__title`}>
{title}
{required && <span className={`${name}--required`}>&nbsp;*</span>}
{description && <div className={`${name}__description`}>{description}</div>}
</div>
)}
{(note || children) && <div className={`${name}__note`}>{children ? children : note}</div>}
{renderIcon}
const readerImage = () => {
if (isString(image)) {
return <img src={image} className={`${name}__left-image`} />;
}
return image;
};

const readerLeft = () => (
<div className={`${name}__left`}>
{leftIcon && <div className={`${name}__left-icon`}>{leftIcon}</div>}
{readerImage()}
</div>
);

const readerTitle = () => {
if (!title) {
return null;
}
return (
<div className={`${name}__title`}>
{title}
{required && <span className={`${name}--required`}>&nbsp;*</span>}
{description && <div className={`${name}__description`}>{description}</div>}
</div>
);
};
const readerRight = () => {
const Icon = arrow ? <ChevronRightIcon /> : rightIcon;
if (!Icon) {
return null;
}
return (
<div className={`${name}__right`}>
<div className={`${name}__right-icon`}>{Icon}</div>
</div>
);
};

return withNativeProps(
props,
url ? (
<a style={{ textDecoration: 'none' }} href={url} rel="noreferrer">
{content}
</a>
) : (
content
),
<div ref={ref} className={classnames(classNames)} onClick={handleClick}>
{readerLeft()}
{readerTitle()}
{note ? <div className={`${name}__note`}>{note}</div> : children}
{readerRight()}
</div>,
);
};

Expand Down
46 changes: 46 additions & 0 deletions src/cell/__tests__/cell.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, it, expect, vi, render, fireEvent, screen } from '@test/utils';
import React from 'react';
import Cell from '../Cell';

describe('Cell', () => {
it('renders the correct content', () => {
const { getByText } = render(
<Cell title="Test Title" description="Test Description">
Test Children
</Cell>,
);

expect(getByText('Test Title')).toBeInTheDocument();
expect(getByText('Test Description')).toBeInTheDocument();
expect(getByText('Test Children')).toBeInTheDocument();
});

it('renders left icon and image if provided', () => {
const { getByTestId } = render(
<Cell
leftIcon={<div data-testid="left-icon">Left Icon</div>}
image="https://tdesign.gtimg.com/mobile/demos/avatar1.png"
>
Test Children
</Cell>,
);

expect(getByTestId('left-icon')).toBeInTheDocument();
expect(screen.getByText('Test Children')).toBeInTheDocument();
});

it('renders right icon if provided', () => {
const { getByTestId } = render(<Cell rightIcon={<div data-testid="right-icon">Right Icon</div>} />);

expect(getByTestId('right-icon')).toBeInTheDocument();
});

it('calls onClick when the cell is clicked', () => {
const handleClick = vi.fn();
const { container } = render(<Cell onClick={handleClick} />);

fireEvent.click(container.firstChild);

expect(handleClick).toHaveBeenCalledTimes(1);
});
});
6 changes: 5 additions & 1 deletion src/cell/_example/base.jsx → src/cell/_example/base.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import './style/index.less';

import Single from './single';
import Multiple from './multiple';
import Group from './group';

export default function Base() {
return (
Expand All @@ -16,9 +17,12 @@ export default function Base() {
<TDemoBlock title="01 类型" summary="单行单元格">
<Single />
</TDemoBlock>
<TDemoBlock title="" summary="多行单元格">
<TDemoBlock title="02" summary="多行单元格">
<Multiple />
</TDemoBlock>
<TDemoBlock title="03 组件样式" summary="卡片单元格">
<Group />
</TDemoBlock>
</div>
);
}
13 changes: 13 additions & 0 deletions src/cell/_example/group.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import { CellGroup, Cell } from 'tdesign-mobile-react';
import { LockOnIcon, ServiceIcon, InternetIcon } from 'tdesign-icons-react';

export default function Group() {
return (
<CellGroup theme="card">
<Cell leftIcon={<LockOnIcon />} title="单行标题" arrow />
<Cell leftIcon={<ServiceIcon />} title="单行标题" arrow />
<Cell leftIcon={<InternetIcon />} title="单行标题" arrow />
</CellGroup>
);
}
34 changes: 0 additions & 34 deletions src/cell/_example/multiple.jsx

This file was deleted.

Loading

0 comments on commit ca639f9

Please sign in to comment.