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
9 changes: 9 additions & 0 deletions .changeset/fix-table-selection-types.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@tiny-design/react": minor
---

- Table: use Checkbox/Radio components, fix selection bugs with pagination and sorting, improve type definitions
- List: use semantic `<ul>`/`<li>` elements, add forwardRef to ListItemMeta, fix paginated renderItem index, add ARIA attributes
- Loader: redesign spinner with CSS-only border animation, remove dot elements
- Button: fix icon container shrinking in flex layout
- useVirtualScroll: add `enabled` option
9 changes: 6 additions & 3 deletions packages/react/src/_utils/use-virtual-scroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface UseVirtualScrollOptions {
itemHeight: number;
containerHeight: number;
overscan?: number;
enabled?: boolean;
}

export interface UseVirtualScrollResult {
Expand All @@ -15,12 +16,14 @@ export interface UseVirtualScrollResult {
}

export function useVirtualScroll(options: UseVirtualScrollOptions): UseVirtualScrollResult {
const { itemCount, itemHeight, containerHeight, overscan = 3 } = options;
const { itemCount, itemHeight, containerHeight, overscan = 3, enabled = true } = options;
const [scrollTop, setScrollTop] = useState(0);

const onScroll = useCallback((e: React.UIEvent<HTMLElement>) => {
setScrollTop(e.currentTarget.scrollTop);
}, []);
if (enabled) {
setScrollTop(e.currentTarget.scrollTop);
}
}, [enabled]);

const totalHeight = itemCount * itemHeight;

Expand Down
4 changes: 2 additions & 2 deletions packages/react/src/button/demo/Loading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Button, Flex } from '@tiny-design/react';
export default function LoadingDemo() {
return (
<Flex gap="sm">
<Button loading>Default Button</Button>
<Button loading btnType="primary">Primary Button</Button>
<Button loading>Default</Button>
<Button loading btnType="primary">Primary</Button>
<Button loading btnType="link">Link</Button>
</Flex>
);
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/button/style/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ $btn-prefix: #{$prefix}-btn;

&__icon-container, &__loader {
display: inline-block;
flex-shrink: 0;
pointer-events: none;
line-height: $btn-line-height;
vertical-align: middle;
Expand Down
57 changes: 31 additions & 26 deletions packages/react/src/list/__tests__/__snapshots__/list.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,55 @@
exports[`<List /> should match the snapshot 1`] = `
<DocumentFragment>
<div
aria-busy="false"
class="ty-list ty-list_md ty-list_split"
>
<div
class="ty-list__body"
>
<div
class="ty-list-item"
<ul
class="ty-list__items"
>
<div
class="ty-list-item__main"
<li
class="ty-list-item"
>
<div
class="ty-list-item__content"
class="ty-list-item__main"
>
Item 1
<div
class="ty-list-item__content"
>
Item 1
</div>
</div>
</div>
</div>
<div
class="ty-list-item"
>
<div
class="ty-list-item__main"
</li>
<li
class="ty-list-item"
>
<div
class="ty-list-item__content"
class="ty-list-item__main"
>
Item 2
<div
class="ty-list-item__content"
>
Item 2
</div>
</div>
</div>
</div>
<div
class="ty-list-item"
>
<div
class="ty-list-item__main"
</li>
<li
class="ty-list-item"
>
<div
class="ty-list-item__content"
class="ty-list-item__main"
>
Item 3
<div
class="ty-list-item__content"
>
Item 3
</div>
</div>
</div>
</div>
</li>
</ul>
</div>
</div>
</DocumentFragment>
Expand Down
7 changes: 6 additions & 1 deletion packages/react/src/list/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import React from 'react';
import List from './list';
import ListItem from './list-item';
import ListItemMeta from './list-item-meta';
import { ListProps } from './types';

type IList = typeof List & {
type IList = (<T = any>(
props: ListProps<T> & React.RefAttributes<HTMLDivElement>
) => React.ReactElement | null) & {
Item: typeof ListItem;
ItemMeta: typeof ListItemMeta;
displayName?: string;
};

const DefaultList = List as IList;
Expand Down
16 changes: 12 additions & 4 deletions packages/react/src/list/list-item-meta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,30 @@ import { ConfigContext } from '../config-provider/config-context';
import { getPrefixCls } from '../_utils/general';
import { ListItemMetaProps } from './types';

const ListItemMeta = (props: ListItemMetaProps): React.ReactElement => {
const { avatar, title, description, prefixCls: customisedCls, className, style } = props;
const ListItemMeta = React.forwardRef<HTMLDivElement, ListItemMetaProps>((props, ref) => {
const {
avatar,
title,
description,
prefixCls: customisedCls,
className,
style,
...otherProps
} = props;
const configContext = useContext(ConfigContext);
const prefixCls = getPrefixCls('list-item-meta', configContext.prefixCls, customisedCls);
const cls = classNames(prefixCls, className);

return (
<div className={cls} style={style}>
<div {...otherProps} ref={ref} className={cls} style={style}>
{avatar && <div className={`${prefixCls}__avatar`}>{avatar}</div>}
<div className={`${prefixCls}__content`}>
{title && <div className={`${prefixCls}__title`}>{title}</div>}
{description && <div className={`${prefixCls}__description`}>{description}</div>}
</div>
</div>
);
};
});

ListItemMeta.displayName = 'ListItemMeta';
export default ListItemMeta;
8 changes: 4 additions & 4 deletions packages/react/src/list/list-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ConfigContext } from '../config-provider/config-context';
import { getPrefixCls } from '../_utils/general';
import { ListItemProps } from './types';

const ListItem = React.forwardRef<HTMLDivElement, ListItemProps>((props, ref) => {
const ListItem = React.forwardRef<HTMLLIElement, ListItemProps>((props, ref) => {
const {
extra,
actions,
Expand All @@ -20,11 +20,11 @@ const ListItem = React.forwardRef<HTMLDivElement, ListItemProps>((props, ref) =>
const cls = classNames(prefixCls, className);

return (
<div {...otherProps} ref={ref} className={cls} style={style}>
<li {...otherProps} ref={ref} className={cls} style={style}>
<div className={`${prefixCls}__main`}>
<div className={`${prefixCls}__content`}>{children}</div>
{actions && actions.length > 0 && (
<ul className={`${prefixCls}__actions`}>
<ul className={`${prefixCls}__actions`} aria-label="Actions">
{actions.map((action, i) => (
<li key={i} className={`${prefixCls}__action`}>
{action}
Expand All @@ -34,7 +34,7 @@ const ListItem = React.forwardRef<HTMLDivElement, ListItemProps>((props, ref) =>
)}
</div>
{extra && <div className={`${prefixCls}__extra`}>{extra}</div>}
</div>
</li>
);
});

Expand Down
37 changes: 25 additions & 12 deletions packages/react/src/list/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ const List = React.forwardRef<HTMLDivElement, ListProps>((props, ref) => {
itemCount: dataSource.length,
itemHeight,
containerHeight: height ?? 0,
enabled: isVirtual,
});

const cls = classNames(prefixCls, className, {
Expand All @@ -78,7 +79,7 @@ const List = React.forwardRef<HTMLDivElement, ListProps>((props, ref) => {
if (isVirtual) {
if (dataSource.length === 0) {
return (
<div className={`${prefixCls}__empty`}>
<div className={`${prefixCls}__empty`} role="status">
{locale?.emptyText ?? 'No Data'}
</div>
);
Expand All @@ -89,10 +90,16 @@ const List = React.forwardRef<HTMLDivElement, ListProps>((props, ref) => {
<React.Fragment key={start + i}>{renderItem(item, start + i)}</React.Fragment>
));
return (
<div style={{ height: totalHeight, position: 'relative' }}>
<div style={{ position: 'absolute', top: 0, left: 0, right: 0, transform: `translateY(${offsetY}px)` }}>
<div
style={{ height: totalHeight, position: 'relative' }}
aria-rowcount={dataSource.length}
>
<ul
className={`${prefixCls}__items`}
style={{ position: 'absolute', top: 0, left: 0, right: 0, transform: `translateY(${offsetY}px)` }}
>
{visibleItems}
</div>
</ul>
</div>
);
}
Expand All @@ -102,18 +109,22 @@ const List = React.forwardRef<HTMLDivElement, ListProps>((props, ref) => {
const items = paginatedData();
if (items.length === 0 && !children) {
return (
<div className={`${prefixCls}__empty`}>
<div className={`${prefixCls}__empty`} role="status">
{locale?.emptyText ?? 'No Data'}
</div>
);
}
if (renderItem) {
const page = pagination ? (pagination.current ?? currentPage) : 1;
const startIndex = pagination ? (page - 1) * pageSize : 0;
const rendered = items.map((item, index) => (
<React.Fragment key={index}>{renderItem(item, index)}</React.Fragment>
<React.Fragment key={startIndex + index}>
{renderItem(item, startIndex + index)}
</React.Fragment>
));
if (grid) {
return (
<div
<ul
className={`${prefixCls}__grid`}
style={{
display: 'grid',
Expand All @@ -122,10 +133,10 @@ const List = React.forwardRef<HTMLDivElement, ListProps>((props, ref) => {
}}
>
{rendered}
</div>
</ul>
);
}
return rendered;
return <ul className={`${prefixCls}__items`}>{rendered}</ul>;
}
return children;
};
Expand All @@ -144,11 +155,11 @@ const List = React.forwardRef<HTMLDivElement, ListProps>((props, ref) => {
: undefined;

return (
<div {...otherProps} ref={ref} className={cls} style={style}>
<div {...otherProps} ref={ref} aria-busy={loading} className={cls} style={style}>
{header && <div className={`${prefixCls}__header`}>{header}</div>}
<div className={bodyCls} style={bodyStyle} onScroll={isVirtual ? onScroll : undefined}>
{loading ? (
<div className={`${prefixCls}__loading`}>Loading...</div>
<div className={`${prefixCls}__loading`} role="status" aria-live="polite">Loading...</div>
) : (
renderItems()
)}
Expand All @@ -171,4 +182,6 @@ const List = React.forwardRef<HTMLDivElement, ListProps>((props, ref) => {
});

List.displayName = 'List';
export default List;
export default List as <T = any>(
props: ListProps<T> & React.RefAttributes<HTMLDivElement>
) => React.ReactElement | null;
7 changes: 7 additions & 0 deletions packages/react/src/list/style/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@
}
}

&__items,
&__grid {
list-style: none;
margin: 0;
padding: 0;
}

&__empty {
padding: 24px;
text-align: center;
Expand Down
6 changes: 4 additions & 2 deletions packages/react/src/list/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ export interface ListPaginationProps extends Pick<PaginationProps, 'size' | 'ali

export interface ListItemProps
extends BaseProps,
React.PropsWithoutRef<JSX.IntrinsicElements['div']> {
React.PropsWithoutRef<JSX.IntrinsicElements['li']> {
extra?: React.ReactNode;
actions?: React.ReactNode[];
children?: React.ReactNode;
}

export interface ListItemMetaProps extends BaseProps {
export interface ListItemMetaProps
extends BaseProps,
Omit<React.PropsWithoutRef<JSX.IntrinsicElements['div']>, 'title'> {
avatar?: React.ReactNode;
title?: React.ReactNode;
description?: React.ReactNode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,7 @@ exports[`<Loader /> should match the snapshot 1`] = `
>
<div
class="ty-loader__indicator ty-loader__indicator_md"
>
<span
class="ty-loader__indicator-dot"
/>
<span
class="ty-loader__indicator-dot"
/>
<span
class="ty-loader__indicator-dot"
/>
<span
class="ty-loader__indicator-dot"
/>
</div>
/>
</div>
</div>
</DocumentFragment>
Expand Down
9 changes: 1 addition & 8 deletions packages/react/src/loader/indicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,7 @@ type Props = {

const Indicator = (props: Props): React.ReactElement => {
const { size, className } = props;
return (
<div className={`${className} ${className}_${size}`}>
<span className={`${className}-dot`} />
<span className={`${className}-dot`} />
<span className={`${className}-dot`} />
<span className={`${className}-dot`} />
</div>
);
return <div className={`${className} ${className}_${size}`} />;
};

export default Indicator;
Loading
Loading